Repository navigation
Guest identity + upload size limit for everyone (1/3) - #14
Merged
Merged
Conversation
Guests get an httpOnly cookie guest_<id>.<exp>.<HMAC-SHA256> (24 h) signed with GUEST_COOKIE_SECRET; auth() returns the guest_ id as the user id, so it is the team_id and existing tenant queries need no change. Clerk users always win over a guest cookie. The middleware accepts a valid guest cookie on protected routes (Clerk and test mode). Web Crypto only, so it runs on edge. POST /api/guest records the session in guest_sessions (migration 003) with an HMAC of the client IP, capped at 5 per IP and 200 overall per 24 h in one INSERT ... SELECT so concurrent requests cannot overshoot. Optional Turnstile check when TURNSTILE_SECRET_KEY is set (fails closed on network errors). Everything is off while GUEST_COOKIE_SECRET is unset. Rejected: a JWT library (one HMAC is enough and keeps the edge bundle small) and storing sessions only in the cookie (no way to cap per IP).
A presigned PUT cannot limit the body size, so /api/upload now returns a presigned POST whose policy pins the key and content type and carries content-length-range (100 MB, MAX_UPLOAD_MB; guests 2 MB). The route refuses a declared size over the limit with 413 before creating a run, GET returns the caller's limit so the form refuses a large file before creating a pipeline, and the profiler checks ContentLength before reading the object and fails the run (backstop for clients that bypass the form; LocalStack does not enforce POST policies, so tests cover this path). Deploy note: the raw bucket CORS must allow POST and the profiler trigger must include s3:ObjectCreated:Post.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This was referenced Oct 2, 2026
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stack: 1 of 3. This PR targets
main. #15 (guest limits) is stacked on this branch, and #16 (guest demo) is stacked on PR2. Merge them in order.What
/api/uploadnow returns a presigned POST instead of a PUT. The policy pins the key and content type and includescontent-length-range: 100 MB by default (MAX_UPLOAD_MB), 2 MB for guests. The route answers 413 when the declaredsizeis over the limit, andGET /api/uploadreturns the caller's limit so the form can refuse a large file before it creates a pipeline. The profiler checksContentLengthbefore reading the object and fails the run with "File is X; the upload limit is Y per file."src/lib/guest.tsissues and verifies an httpOnlycs_guestcookie of the formguest_<22>.<exp>.<HMAC-SHA256>, valid for 24 h. It uses Web Crypto only, so it is edge-safe.auth()falls back to the guest id only when no Clerk user is signed in, and the guest id becomes theteam_id. The middleware lets a valid guest cookie through on protected routes./api/guest. POST creates a session, GET returns its status, DELETE ends it. Each session is recorded inguest_sessions(migration003_guest_sessions.sql) with an HMAC of the IP. Caps are 5 per IP and 200 overall per 24 h, enforced in a singleINSERT ... SELECT. A Turnstile check runs whenTURNSTILE_SECRET_KEYis set.GUEST_COOKIE_SECRET(≥ 32 chars; guest access is off while it is unset),TURNSTILE_SECRET_KEY/NEXT_PUBLIC_TURNSTILE_SITE_KEY(optional), andMAX_UPLOAD_MB, all insrc/lib/env.tsand.env.example.Why
Guests need an identity that works with existing tenant isolation, and every upload path needs a size limit that S3 enforces, not only the client.
Do not set
GUEST_COOKIE_SECRETin production until PR2 is merged. PR2 adds guest quotas, AI caps and the blocked features.Look hardest at
auth()ordering: Clerk first, then the guest cookie.src/app/api/guest/route.ts.Deploy prerequisites (AWS, by hand)
POST.s3:ObjectCreated:Post(or*). WithPutonly, browser uploads never trigger profiling.Verified (locally)
npx tsc --noEmit,npm run lint(0 errors),npx vitest run: 175 passed.npm run test:integration: 12 passed, including the newguest-access.test.ts(cookie +guest_sessionsrow, 429 on the 6th session from one IP, a guest's 2 MB policy, and an oversized object rejected by the profiler).CLEANSTACK_INTEGRATION=1 pytest lambdas/tests/integration: 6 passed.npm run test:e2e: 10 passed. Newguest.spec.tscovers guest → upload → review, and a 2.1 MB file refused in the form and with 413. The guard servers now also reject a forgedcs_guestcookie.Caveat
LocalStack 3.8 does not enforce POST policies, so the S3-side rejection is covered by the policy unit tests rather than by a real S3 round trip.