import { timingSafeEqual } from "node:crypto"; import { NextResponse } from "next/server"; /** * Verify the `Authorization: Bearer ` header against CRON_SECRET. * * Security properties: * - Fail-closed: returns 401 when CRON_SECRET is not configured (A05-3) * - Timing-safe: uses crypto.timingSafeEqual to prevent timing attacks (A02-1) * * Usage: * const deny = verifyCronSecret(request); * if (deny) return deny; */ export function verifyCronSecret(request: Request): NextResponse | null { const cronSecret = process.env["CRON_SECRET"]; // Fail-closed: if the secret is not configured, reject all requests. if (!cronSecret) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } const auth = request.headers.get("authorization") ?? ""; const expected = `Bearer ${cronSecret}`; const expectedBuf = Buffer.from(expected, "utf8"); const actualBuf = Buffer.from(auth, "utf8"); // Different lengths can be rejected without timing exposure (length itself is not secret). if (actualBuf.length !== expectedBuf.length || !timingSafeEqual(expectedBuf, actualBuf)) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } return null; }