feat(api): migrate POST /api/sandbox/upload to /api/sandboxes/staged-files#541
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Rate limit exceeded
You’ve run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (2)
📝 WalkthroughWalkthroughAdds a Next.js API route and handler to issue Vercel Blob client upload tokens: parses request JSON, validates auth for token generation, enforces a 100MB max with random suffix option, returns token JSON with CORS headers, and handles errors with logged 500 responses. ChangesUpload Token Endpoint Implementation
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
lib/sandbox/postSandboxesUploadTokensHandler.ts (2)
20-48: ⚡ Quick winConsider extracting sub-functions to reduce length.
This function is 27 lines, exceeding the 20-line guideline. While the logic is straightforward, consider extracting the token validation logic and error response construction into separate functions for better testability and readability.
Example structure:
validateClientPayload(clientPayload)→ validates and returns parsed payloadcreateErrorResponse(error)→ determines status code and formats error response
As per coding guidelines: "Flag functions longer than 20 lines" and "Keep functions small and focused."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/sandbox/postSandboxesUploadTokensHandler.ts` around lines 20 - 48, The handler postSandboxesUploadTokensHandler is over the 20-line guideline—extract the client payload parsing/token validation and the error response creation into small helpers: implement validateClientPayload(clientPayload) used inside the onBeforeGenerateToken callback to parse JSON and throw a clear Authentication error when payload?.token is missing, and implement createErrorResponse(error) to return the object and status used in the catch block (message and appropriate status); replace the inline parsing/throw and the catch return with calls to these helpers to shorten and improve testability of postSandboxesUploadTokensHandler.
42-46: ⚡ Quick winReturn appropriate HTTP status codes for different error types.
The catch block returns
400 Bad Requestfor all errors, but different error types warrant different status codes:
- Authentication errors →
401 Unauthorized- File size limit exceeded →
413 Payload Too Large- Server/Vercel Blob errors →
500 Internal Server Error- Malformed request →
400 Bad RequestDifferentiate error types to provide clearer signals to clients and improve observability.
♻️ Proposed error handling improvement
} catch (error) { + const message = error instanceof Error ? error.message : "Upload failed"; + let status = 400; + + if (message.includes("Authentication") || message.includes("Unauthorized")) { + status = 401; + } else if (message.includes("size") || message.includes("too large")) { + status = 413; + } else if (message.includes("internal") || message.includes("server")) { + status = 500; + } + return NextResponse.json( - { error: error instanceof Error ? error.message : "Upload failed" }, - { status: 400, headers: getCorsHeaders() }, + { error: message }, + { status, headers: getCorsHeaders() }, ); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/sandbox/postSandboxesUploadTokensHandler.ts` around lines 42 - 46, The catch in postSandboxesUploadTokensHandler currently always returns 400; update it to map specific error types to appropriate HTTP statuses by inspecting the thrown error (e.g., if error is an instance of AuthenticationError or contains auth-related marker -> 401 Unauthorized; if error is a PayloadTooLargeError or message indicates file size -> 413 Payload Too Large; if error is a VercelBlobError/server-side error -> 500 Internal Server Error; otherwise keep 400 for malformed requests), and return NextResponse.json with the error message and the selected status while preserving getCorsHeaders(); use the existing function name postSandboxesUploadTokensHandler and the current catch block as the location to implement this mapping.app/api/sandboxes/staged-files/route.ts (1)
42-44: 🏗️ Heavy liftConsider authenticating before delegating to the handler.
While delegation keeps the route thin, the coding guidelines require API routes to use
validateAuthContext(). Consider authenticating the request here before callingpostSandboxesUploadTokensHandler, then passing the validated account context through:export async function POST(request: Request): Promise<Response> { const authContext = await validateAuthContext(request); return postSandboxesUploadTokensHandler(request, authContext); }This would:
- Comply with the guideline "Always use
validateAuthContext()for authentication in API routes"- Allow the handler to receive pre-validated authentication context
- Eliminate the need for the non-standard
clientPayload.tokenworkaroundIf the Vercel Blob library constraint prevents this, document why
validateAuthContext()cannot be used and ensure the handler implements equivalent validation.
As per coding guidelines: "Always use
validateAuthContext()for authentication in API routes; it supports bothx-api-keyheader andAuthorization: Bearertoken authentication."🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@app/api/sandboxes/staged-files/route.ts` around lines 42 - 44, The route currently delegates to postSandboxesUploadTokensHandler without authenticating; call validateAuthContext(request) inside POST to get the authContext and pass it into postSandboxesUploadTokensHandler (update postSandboxesUploadTokensHandler signature to accept the authContext and remove the clientPayload.token workaround), or if Vercel Blob truly prevents using validateAuthContext(), add a short comment explaining why and implement equivalent validation inside postSandboxesUploadTokensHandler (or a shared helper) so authentication is always performed before upload token logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@lib/sandbox/postSandboxesUploadTokensHandler.ts`:
- Around line 27-31: The onBeforeGenerateToken handler currently only checks
payload?.token presence; replace that with real server-side validation: call a
verifyPrivyToken(token) helper (implement using Privy server SDK or JWT public
key verification) from onBeforeGenerateToken in
postSandboxesUploadTokensHandler.ts, ensure verifyPrivyToken returns a decoded
payload and extract the account/user id (e.g., decoded.sub or decoded.accountId)
and reject (throw Error) if verification fails or token is expired, then use the
extracted user id for downstream audit/authorization; if full verification
cannot be implemented immediately, at minimum log the incoming token and client
context for audit and wire up token/IP rate-limiting keyed by that token.
---
Nitpick comments:
In `@app/api/sandboxes/staged-files/route.ts`:
- Around line 42-44: The route currently delegates to
postSandboxesUploadTokensHandler without authenticating; call
validateAuthContext(request) inside POST to get the authContext and pass it into
postSandboxesUploadTokensHandler (update postSandboxesUploadTokensHandler
signature to accept the authContext and remove the clientPayload.token
workaround), or if Vercel Blob truly prevents using validateAuthContext(), add a
short comment explaining why and implement equivalent validation inside
postSandboxesUploadTokensHandler (or a shared helper) so authentication is
always performed before upload token logic.
In `@lib/sandbox/postSandboxesUploadTokensHandler.ts`:
- Around line 20-48: The handler postSandboxesUploadTokensHandler is over the
20-line guideline—extract the client payload parsing/token validation and the
error response creation into small helpers: implement
validateClientPayload(clientPayload) used inside the onBeforeGenerateToken
callback to parse JSON and throw a clear Authentication error when
payload?.token is missing, and implement createErrorResponse(error) to return
the object and status used in the catch block (message and appropriate status);
replace the inline parsing/throw and the catch return with calls to these
helpers to shorten and improve testability of postSandboxesUploadTokensHandler.
- Around line 42-46: The catch in postSandboxesUploadTokensHandler currently
always returns 400; update it to map specific error types to appropriate HTTP
statuses by inspecting the thrown error (e.g., if error is an instance of
AuthenticationError or contains auth-related marker -> 401 Unauthorized; if
error is a PayloadTooLargeError or message indicates file size -> 413 Payload
Too Large; if error is a VercelBlobError/server-side error -> 500 Internal
Server Error; otherwise keep 400 for malformed requests), and return
NextResponse.json with the error message and the selected status while
preserving getCorsHeaders(); use the existing function name
postSandboxesUploadTokensHandler and the current catch block as the location to
implement this mapping.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 699570e0-1317-4c01-a4a7-5d2ff0f4deba
⛔ Files ignored due to path filters (1)
lib/sandbox/__tests__/postSandboxesUploadTokensHandler.test.tsis excluded by!**/*.test.*,!**/__tests__/**and included bylib/**
📒 Files selected for processing (2)
app/api/sandboxes/staged-files/route.tslib/sandbox/postSandboxesUploadTokensHandler.ts
There was a problem hiding this comment.
3 issues found across 3 files
Confidence score: 2/5
- Two high-severity, high-confidence findings in
lib/sandbox/postSandboxesUploadTokensHandler.tscreate meaningful runtime and security risk, so this is not a low-risk merge as-is. - The catch block currently returns raw exception details and HTTP 400 for all failures, which can leak internal error information and misclassify unexpected server faults that should be handled as generic 500 responses.
onBeforeGenerateTokenonly checks for token presence and does not validate thatpayload.tokenis a valid Privy access token, which could allow invalid or unauthorized token use in upload token generation flows.- Pay close attention to
lib/sandbox/postSandboxesUploadTokensHandler.tsandlib/sandbox/__tests__/postSandboxesUploadTokensHandler.test.ts- harden auth/error handling behavior and then align the new test file with repository length/style limits.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="lib/sandbox/postSandboxesUploadTokensHandler.ts">
<violation number="1" location="lib/sandbox/postSandboxesUploadTokensHandler.ts:29">
P1: The `onBeforeGenerateToken` callback only performs a presence check on `payload.token` without actually validating it as a Privy access token. Per the [Vercel Blob docs](https://vercel.com/docs/storage/vercel-blob/client-upload), you must authenticate and authorize users in this callback — without proper validation, anyone can upload files to your Blob store by passing any truthy string as `token`. Consider verifying the JWT signature here (e.g., using Privy's server SDK) to prevent unauthorized uploads to the staging area.</violation>
<violation number="2" location="lib/sandbox/postSandboxesUploadTokensHandler.ts:43">
P1: Do not return raw exception messages and HTTP 400 for all failures in this catch block; unexpected errors should be logged server-side and returned as a generic 500 response.
(Based on your team's feedback about hiding internal exception details in API error responses.) [FEEDBACK_USED]</violation>
</file>
<file name="lib/sandbox/__tests__/postSandboxesUploadTokensHandler.test.ts">
<violation number="1" location="lib/sandbox/__tests__/postSandboxesUploadTokensHandler.test.ts:1">
P2: Custom agent: **Enforce Clear Code Style and Maintainability Practices**
New test file exceeds the repository’s 100-line file-length limit.</violation>
</file>
Architecture diagram
sequenceDiagram
participant UI as Browser (chat.recoupable.com)
participant Next as Next.js API Route
participant Handler as postSandboxesUploadTokensHandler
participant Cors as getCorsHeaders
participant BlobSDK as @vercel/blob/client
participant VercelBlob as Vercel Blob Service
Note over UI,VercelBlob: NEW: POST /api/sandboxes/staged-files
alt OPTIONS preflight
UI->>Next: OPTIONS /api/sandboxes/staged-files
Next->>Cors: get empty CORS headers
Cors-->>Next: Access-Control-Allow-Origin: *
Next-->>UI: 200 (empty body) + CORS headers
end
Note over UI,VercelBlob: Phase 1: Token Handshake (from @vercel/blob/client.upload())
UI->>Next: POST /api/sandboxes/staged-files
Note over UI,Next: Body: HandleUploadBody<br/>No Authorization header allowed by SDK
Next->>Handler: postSandboxesUploadTokensHandler(request)
Handler->>Handler: Parse request body as HandleUploadBody
Handler->>BlobSDK: handleUpload({ body, request, onBeforeGenerateToken, onUploadCompleted })
BlobSDK->>Handler: Invoke onBeforeGenerateToken(pathname, clientPayload)
Handler->>Handler: Parse clientPayload JSON
alt clientPayload has token field
Handler-->>BlobSDK: Return { maximumSizeInBytes: 104857600, addRandomSuffix: true }
else clientPayload missing or no token
Handler-->>BlobSDK: Throw Error("Authentication required")
BlobSDK-->>Handler: Reject
Handler->>Handler: Catch error
Handler->>Cors: getCorsHeaders()
Cors-->>Handler: CORS headers
Handler-->>Next: 400 { error: "Authentication required" }
Next-->>UI: 400 + CORS headers
end
Note over VercelBlob,BlobSDK: Phase 1a: Blob generates client token
BlobSDK->>VercelBlob: Request presigned upload token
VercelBlob-->>BlobSDK: { clientToken: "tkn_xxx" }
BlobSDK-->>Handler: { type: "blob.generate-client-token", ... }
Handler->>Cors: getCorsHeaders()
Cors-->>Handler: CORS headers
Handler-->>Next: 200 JSON response + CORS headers
Next-->>UI: 200 { clientToken: "tkn_xxx" }
Note over UI,VercelBlob: Phase 2: Direct Upload (browser to Blob)
UI->>VercelBlob: PUT file with clientToken
VercelBlob-->>UI: 200 { url: "https://blob.vercel-storage.com/..." }
Note over VercelBlob,BlobSDK: Phase 2a: Blob calls back with completion
VercelBlob->>BlobSDK: POST callback (completion event)
BlobSDK->>Handler: Invoke onUploadCompleted({ blob: { url, pathname } })
Handler->>Handler: Currently no-op (awaiting downstream commit)
Note over UI,Next: Phase 3: Commit (separate request)
UI->>Next: POST /api/sandboxes/files (with Bearer token)
Note over UI,Next: Downstream route re-authenticates via Authorization header
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
| @@ -0,0 +1,110 @@ | |||
| import { describe, it, expect, vi, beforeEach } from "vitest"; | |||
There was a problem hiding this comment.
P2: Custom agent: Enforce Clear Code Style and Maintainability Practices
New test file exceeds the repository’s 100-line file-length limit.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At lib/sandbox/__tests__/postSandboxesUploadTokensHandler.test.ts, line 1:
<comment>New test file exceeds the repository’s 100-line file-length limit.</comment>
<file context>
@@ -0,0 +1,110 @@
+import { describe, it, expect, vi, beforeEach } from "vitest";
+import { handleUpload } from "@vercel/blob/client";
+
</file context>
Preview smoke test —
|
| Test | Expected | Actual |
|---|---|---|
OPTIONS preflight (origin: chat.recoupable.com) |
200 + permissive CORS | ✅ 200; allow-origin: *, allow-headers: Content-Type, Authorization, X-Requested-With, x-api-key |
POST handshake, no auth |
401 from validateAuthContext |
✅ 401 "Exactly one of x-api-key or Authorization must be provided" |
POST handshake, with x-api-key |
200 + clientToken |
✅ 200, returned a real vercel_blob_client_… token |
POST callback, empty body (no auth) |
500 generic — signature verify fails | ✅ 500 "Failed to issue upload token" (raw exception hidden, server-side console.error per review fix) |
POST junk body.type |
500 generic | ✅ 500 "Failed to issue upload token" |
End-to-end via @vercel/blob/client.upload()
Ran from the api worktree pointed at the preview, with x-api-key forwarded via the library's headers option:
starting upload, 25 bytes
blob.url: https://dxfamqbi5zyezrs5.public.blob.vercel-storage.com/smoke-1778349079998-…txt
blob.pathname: smoke-1778349079998-…txt
download status: 200
download body: "smoke test 1778349079998\n"
match: true
Confirms the full handshake → presigned token → direct PUT to Vercel Blob → upload-completed callback path works end-to-end against this preview, with the auth surface aligned to api convention.
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
lib/sandbox/postSandboxesUploadTokensHandler.ts (2)
30-30: ⚡ Quick winRemove the empty callback or document why it's needed.
The
onUploadCompletedcallback is empty and serves no purpose. If the Vercel BlobhandleUploadAPI doesn't require this callback, consider removing it entirely to reduce noise.♻️ Proposed cleanup
const jsonResponse = await handleUpload({ body, request, onBeforeGenerateToken: async () => ({ maximumSizeInBytes: MAX_UPLOAD_SIZE_BYTES, addRandomSuffix: true, }), - onUploadCompleted: async () => {}, });If the callback is required by the API signature but intentionally does nothing (because signature verification is sufficient), add a comment:
- onUploadCompleted: async () => {}, + onUploadCompleted: async () => { + // No-op: signature verification by handleUpload is sufficient + },🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/sandbox/postSandboxesUploadTokensHandler.ts` at line 30, The onUploadCompleted: async () => {} callback in postSandboxesUploadTokensHandler.ts is a no-op and should be removed to reduce noise; either drop the onUploadCompleted property from the options passed to handleUpload() if the Vercel Blob handleUpload API does not require it, or if the API signature mandates the property, replace the empty function with a short explanatory comment (e.g., "// intentionally no-op: signature required, verification handled elsewhere") and keep the minimal async stub to satisfy TypeScript types in the same place where handleUpload is invoked.
10-41: 💤 Low valueConsider extracting auth validation into a helper function.
The function is 32 lines, exceeding the 20-line guideline. While the logic is straightforward, you could improve readability by extracting the auth validation block (lines 16-21) into a separate helper like
validateTokenGenerationAuth(body, request).♻️ Optional refactor to reduce function length
Create a new file
lib/sandbox/validateTokenGenerationAuth.ts:import type { NextRequest } from "next/server"; import { NextResponse } from "next/server"; import type { HandleUploadBody } from "@vercel/blob/client"; import { validateAuthContext } from "@/lib/auth/validateAuthContext"; export async function validateTokenGenerationAuth( body: HandleUploadBody, request: NextRequest, ): Promise<NextResponse | null> { if (body.type === "blob.generate-client-token") { const auth = await validateAuthContext(request); if (auth instanceof NextResponse) { return auth; } } return null; }Then update the handler:
+import { validateTokenGenerationAuth } from "@/lib/sandbox/validateTokenGenerationAuth"; + export async function postSandboxesUploadTokensHandler( request: NextRequest, ): Promise<NextResponse> { try { const body = (await request.json()) as HandleUploadBody; - if (body.type === "blob.generate-client-token") { - const auth = await validateAuthContext(request); - if (auth instanceof NextResponse) { - return auth; - } - } + const authError = await validateTokenGenerationAuth(body, request); + if (authError) { + return authError; + } const jsonResponse = await handleUpload({As per coding guidelines: "Flag functions longer than 20 lines".
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@lib/sandbox/postSandboxesUploadTokensHandler.ts` around lines 10 - 41, Extract the auth validation block inside postSandboxesUploadTokensHandler into a helper named validateTokenGenerationAuth that accepts (body: HandleUploadBody, request: NextRequest) and returns Promise<NextResponse | null>; move the conditional that checks body.type === "blob.generate-client-token" and calls validateAuthContext(request) into that helper, returning the NextResponse when auth fails and null otherwise, then replace the inline block in postSandboxesUploadTokensHandler with a call to validateTokenGenerationAuth and early-return if it yields a NextResponse; keep the rest of handleUpload invocation unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/api/sandboxes/staged-file/route.ts`:
- Around line 11-14: Add a complete JSDoc block above the exported POST function
that documents the request parameter and the returned Response: include a brief
description, an `@param` tag for the request parameter (type NextRequest) and an
`@returns` tag indicating a Promise<Response> (or Response) returned by
postSandboxesUploadTokensHandler; place it directly above the POST function
declaration so the linter picks it up and reference the handler name
postSandboxesUploadTokensHandler in the description for clarity.
- Around line 6-9: Add a complete JSDoc block above the exported OPTIONS
function describing its purpose and include a `@returns` tag specifying it returns
a NextResponse (CORS preflight response) so the linter is satisfied; locate the
OPTIONS function and reference NextResponse and getCorsHeaders in the
description to make clear it returns a NextResponse with CORS headers for
preflight requests.
---
Nitpick comments:
In `@lib/sandbox/postSandboxesUploadTokensHandler.ts`:
- Line 30: The onUploadCompleted: async () => {} callback in
postSandboxesUploadTokensHandler.ts is a no-op and should be removed to reduce
noise; either drop the onUploadCompleted property from the options passed to
handleUpload() if the Vercel Blob handleUpload API does not require it, or if
the API signature mandates the property, replace the empty function with a short
explanatory comment (e.g., "// intentionally no-op: signature required,
verification handled elsewhere") and keep the minimal async stub to satisfy
TypeScript types in the same place where handleUpload is invoked.
- Around line 10-41: Extract the auth validation block inside
postSandboxesUploadTokensHandler into a helper named validateTokenGenerationAuth
that accepts (body: HandleUploadBody, request: NextRequest) and returns
Promise<NextResponse | null>; move the conditional that checks body.type ===
"blob.generate-client-token" and calls validateAuthContext(request) into that
helper, returning the NextResponse when auth fails and null otherwise, then
replace the inline block in postSandboxesUploadTokensHandler with a call to
validateTokenGenerationAuth and early-return if it yields a NextResponse; keep
the rest of handleUpload invocation unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: da23ad0c-8f1a-433e-8c49-6d58a537ee87
⛔ Files ignored due to path filters (1)
lib/sandbox/__tests__/postSandboxesUploadTokensHandler.test.tsis excluded by!**/*.test.*,!**/__tests__/**and included bylib/**
📒 Files selected for processing (2)
app/api/sandboxes/staged-file/route.tslib/sandbox/postSandboxesUploadTokensHandler.ts
Adds POST /api/sandboxes/staged-files — the Vercel Blob client-upload handshake half of the sandbox upload flow. Mirrors chat's existing /api/sandbox/upload handler so the chat side can flip its handleUploadUrl to api and delete the local route. Auth follows the minimum-port shape: validates clientPayload.token because @vercel/blob/client.upload() does not allow setting an Authorization header on the handshake POST. The downstream commit (POST /api/sandboxes/files) re-authenticates with a real Bearer token. OPTIONS preflight is wired with getCorsHeaders() so cross-origin calls from chat.recoupable.com work the same way the existing /api/sandboxes/files route does. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tage-files Verb form matches /api/sandboxes/files (commit) and /api/sandboxes/file (get). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…aged-file Noun-shaped resource matching /api/sandboxes/file (singular getter) and /api/sandboxes/files (collection commit). Each call generates a token to stage one file. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drops clientPayload.token in favor of validateAuthContext() so the endpoint matches the auth surface of every other api route. The chat-side upload() now forwards an Authorization Bearer header via the library's headers option (which I missed earlier — that field is documented for exactly this use case). Branches on body.type to skip auth on the upload-completed callback; that POST comes from Vercel Blob's backend without the user's auth header, and handleUpload() verifies its signature internally. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Log unexpected handleUpload errors server-side via console.error and return a generic 500 — matches the createSandboxHandler pattern and avoids leaking internal context in error responses. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drop the multi-paragraph commentary; keep a one-liner on the handler explaining the asymmetric auth, and minimal JSDoc on the route per project convention. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…e route Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4c55c39 to
27163a9
Compare
Manual verification on the preview deploymentPreview URL: https://api-git-feature-sandbox-staged-files-recoup.vercel.app
SetupUsed an existing fresh agent's API key (account Results
Highlights
Note on the URL itself: documented as 🤖 Generated with Claude Code |
Summary
Adds
POST /api/sandboxes/staged-files— the Vercel Blob client-upload token-handshake endpoint, ported from chat's/api/sandbox/upload. Closes Group 6 of the chat→api migration once paired with the chat-side cutover PR.Auth is minimum-port: validates
clientPayload.token(the Privy access token) insideonBeforeGenerateToken.@vercel/blob/client.upload()does not allow setting anAuthorizationheader on the handshake POST, so token transport rides onclientPayload. The downstream commit (POST /api/sandboxes/files) re-authenticates with a real Bearer token.CORS preflight is wired through
getCorsHeaders()so cross-origin calls fromchat.recoupable.comwork the same way the existing/api/sandboxes/filesroute does.Test plan
pnpm test lib/sandbox/__tests__/postSandboxesUploadTokensHandler.test.ts— 7/7 passpnpm lint,pnpm formatcleanOPTIONSandPOST /api/sandboxes/staged-filesfrom a browser atchat.recoupable.com; confirm token-generation phase + completion callback both succeedlib/sandboxes/uploadSandboxFiles.tsafter the chat PR points at this routeSummary by cubic
Adds
POST /api/sandboxes/staged-fileto issue Vercel Blob client-upload tokens, replacing chat’s/api/sandbox/upload. Adds CORS, Bearer auth, and safer error handling.New Features
@vercel/blob/clientuploads.AuthorizationBearer header withvalidateAuthContext; upload-completed callbacks skip auth and are verified byhandleUpload().getCorsHeaders().Migration
handleUploadUrlto/api/sandboxes/staged-file.@vercel/blob/client.upload()forwards theAuthorizationheader via itsheadersoption.Written for commit 27163a9. Summary will update on new commits.
Summary by CodeRabbit