User story
As a developer, I want the internal editor and camera API routes to require a secret token, so that they are not accidentally callable from a public deployment or by an external user with knowledge of the URL.
Background
Two API routes write to the local filesystem under public/:
POST /api/editor/save-transcript — writes transcript.json and transcript.doc.txt
POST /api/camera/save-profiles — writes camera-profiles.json
These routes exist only for the internal editing workflow running on localhost. When the app is deployed publicly, these routes must not be callable by anyone without the internal secret. A static Bearer token in the Authorization header is sufficient — no session cookies or user auth needed.
Acceptance criteria
Happy path
Given the internal editor or camera UI sends a request with Authorization: Bearer {INTERNAL_API_SECRET}
When the request arrives at /api/editor/save-transcript or /api/camera/save-profiles
Then the request proceeds normally and the file is saved
Error path
Given any request arrives at these routes WITHOUT the Authorization header or with a wrong token
When it hits the handler
Then the server returns 401 { error: "Unauthorized" } and no filesystem write occurs
Given INTERNAL_API_SECRET is not set in the environment
When the server starts (or first request is received)
Then an error is thrown: "INTERNAL_API_SECRET env var is required"
Out of scope
- Public-facing routes (no token check on /api/generate-carousel, /api/transcribe, etc.)
- Session-based auth for internal routes
- Any changes to internal editor or camera UI beyond adding the Authorization header to fetch calls
Technical context
The token check should live in a shared helper:
// app/lib/requireInternalToken.ts
export function requireInternalToken(req: NextRequest): NextResponse | null
Returns a 401 NextResponse if token is missing or wrong, null if OK.
Reads the expected token from process.env.INTERNAL_API_SECRET.
Add INTERNAL_API_SECRET to .env.example.
Files affected:
Implementation details
- Create
app/lib/requireInternalToken.ts:
const secret = process.env.INTERNAL_API_SECRET;
if (!secret) throw new Error('INTERNAL_API_SECRET env var is required');
export function requireInternalToken(req: NextRequest): NextResponse | null {
const auth = req.headers.get('authorization');
if (!auth || !timingSafeEqual(auth, `Bearer ${secret}`)) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
return null;
}
Use crypto.timingSafeEqual or a constant-time comparison to prevent timing attacks.
- In each internal API route, add at the top of the handler:
const guard = requireInternalToken(request);
if (guard) return guard;
- In the internal UI pages, add
Authorization header to fetch calls. Prefer a Next.js Server Action so INTERNAL_API_SECRET stays server-side and never appears in the client bundle. If a server action is not feasible, use NEXT_PUBLIC_INTERNAL_API_SECRET and document the tradeoff.
Additional test scenarios
- A request with a valid token to a public route (e.g. /api/generate-carousel) is NOT affected
- INTERNAL_API_SECRET containing special characters (spaces, symbols) is handled correctly
- Empty string Authorization header returns 401
Hard constraints
- Token comparison must be constant-time (use crypto.timingSafeEqual) to prevent timing attacks
- INTERNAL_API_SECRET must not appear in client-side JavaScript if avoidable — prefer server actions
- 401 response must be JSON, not HTML
Dependency issues
User story
As a developer, I want the internal editor and camera API routes to require a secret token, so that they are not accidentally callable from a public deployment or by an external user with knowledge of the URL.
Background
Two API routes write to the local filesystem under
public/:POST /api/editor/save-transcript— writestranscript.jsonandtranscript.doc.txtPOST /api/camera/save-profiles— writescamera-profiles.jsonThese routes exist only for the internal editing workflow running on localhost. When the app is deployed publicly, these routes must not be callable by anyone without the internal secret. A static Bearer token in the Authorization header is sufficient — no session cookies or user auth needed.
Acceptance criteria
Happy path
Given the internal editor or camera UI sends a request with
Authorization: Bearer {INTERNAL_API_SECRET}When the request arrives at
/api/editor/save-transcriptor/api/camera/save-profilesThen the request proceeds normally and the file is saved
Error path
Given any request arrives at these routes WITHOUT the Authorization header or with a wrong token
When it hits the handler
Then the server returns
401 { error: "Unauthorized" }and no filesystem write occursGiven INTERNAL_API_SECRET is not set in the environment
When the server starts (or first request is received)
Then an error is thrown: "INTERNAL_API_SECRET env var is required"
Out of scope
Technical context
The token check should live in a shared helper:
Returns a 401 NextResponse if token is missing or wrong, null if OK.
Reads the expected token from
process.env.INTERNAL_API_SECRET.Add
INTERNAL_API_SECRETto.env.example.Files affected:
app/lib/requireInternalToken.ts— newapp/api/editor/save-transcript/route.ts— add token checkapp/api/camera/save-profiles/route.ts— add token checkapp/(internal)/editor/page.tsx(post-refactor(app): reorganise routes into (public) and (internal) Next.js route groups #76) — add Authorization header to save-transcript fetchapp/(internal)/camera/page.tsx(post-refactor(app): reorganise routes into (public) and (internal) Next.js route groups #76) — add Authorization header to save-profiles fetchImplementation details
app/lib/requireInternalToken.ts:Use
crypto.timingSafeEqualor a constant-time comparison to prevent timing attacks.Authorizationheader to fetch calls. Prefer a Next.js Server Action soINTERNAL_API_SECRETstays server-side and never appears in the client bundle. If a server action is not feasible, useNEXT_PUBLIC_INTERNAL_API_SECRETand document the tradeoff.Additional test scenarios
Hard constraints
Dependency issues