import { createHmac, timingSafeEqual } from "crypto"; /** * Shared-secret check for the public automation endpoint. Returns null when the * caller is allowed, otherwise the reason to reject with. * * ponytail: an API key is the whole security model here — one render burns ~10 * minutes of CPU, so an open endpoint is free DoS for anyone who finds it. */ export function checkApiKey(req: Request): string | null { const expected = process.env.REEL_API_KEY; // fail closed: no key configured means the endpoint stays shut, not wide open if (!expected) return "REEL_API_KEY is not configured on the server"; const provided = req.headers.get("x-api-key") || req.headers.get("authorization")?.replace(/^Bearer\s+/i, "") || ""; if (!provided) return "Missing x-api-key header"; if (!safeEqual(provided, expected)) return "Invalid API key"; return null; } // ponytail: HMAC both sides first so timingSafeEqual never sees mismatched // lengths (it throws) and length itself doesn't leak through timing function safeEqual(a: string, b: string): boolean { const key = "reel-api-key-compare"; const ha = createHmac("sha256", key).update(a).digest(); const hb = createHmac("sha256", key).update(b).digest(); return timingSafeEqual(ha, hb); }