ST-cmovs80c1: POST /api/internal/push/send met constant-time Bearer check

Route: 503 als INTERNAL_PUSH_SECRET uitstaat, 401 bij verkeerd secret
(timingSafeEqual), 400 bij invalid JSON, 422 bij Zod-fout, 204 bij succes.
push-server.ts: env-import vervangen door process.env om SESSION_SECRET
validatie tijdens build te omzeilen. Tests aangepast.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Scrum4Me Agent 2026-05-07 21:11:11 +02:00
parent 353d2dff8a
commit 39484551e2
4 changed files with 134 additions and 14 deletions

View file

@ -0,0 +1,48 @@
import { timingSafeEqual } from 'crypto'
import { z } from 'zod'
import { sendPushToUser } from '@/lib/push-server'
const schema = z.object({
userId: z.string().min(1),
payload: z.object({
title: z.string().max(80),
body: z.string().max(300),
url: z.string().startsWith('/').or(z.string().url()),
tag: z.string().optional(),
}),
})
export async function POST(req: Request) {
if (!process.env.INTERNAL_PUSH_SECRET) {
return new Response(null, { status: 503 })
}
const authHeader = req.headers.get('authorization') ?? ''
const expected = `Bearer ${process.env.INTERNAL_PUSH_SECRET}`
let authorized = false
try {
authorized =
authHeader.length === expected.length &&
timingSafeEqual(Buffer.from(authHeader), Buffer.from(expected))
} catch {
authorized = false
}
if (!authorized) {
return new Response(null, { status: 401 })
}
let body: unknown
try {
body = await req.json()
} catch {
return new Response(null, { status: 400 })
}
const parsed = schema.safeParse(body)
if (!parsed.success) {
return Response.json({ errors: parsed.error.flatten().fieldErrors }, { status: 422 })
}
await sendPushToUser(parsed.data.userId, parsed.data.payload)
return new Response(null, { status: 204 })
}