File size: 1,279 Bytes
ef4c36f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | 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);
}
|