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>
48 lines
1.3 KiB
TypeScript
48 lines
1.3 KiB
TypeScript
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 })
|
|
}
|