import { timingSafeEqual } from "node:crypto"; import type { MiddlewareHandler } from "hono"; /** * Middleware to authenticate cron endpoints using CRON_SECRET bearer token. * * If CRON_SECRET is not set (dev mode), logs a warning and allows through. * Otherwise, requires Authorization: Bearer header with timing-safe comparison. */ export const cronAuth: MiddlewareHandler = async (c, next) => { const secret = process.env.CRON_SECRET; // Dev mode: no secret set if (!secret) { console.warn( "[cronAuth] CRON_SECRET not set - allowing request (dev mode)", ); return next(); } // Extract bearer token const authHeader = c.req.header("Authorization"); if (!authHeader) { return c.json({ error: "Missing Authorization header" }, 401); } const [scheme, token] = authHeader.split(" "); if (scheme !== "Bearer" || !token) { return c.json({ error: "Invalid Authorization header format" }, 401); } // Timing-safe comparison // timingSafeEqual requires equal-length buffers, so check lengths first if (token.length !== secret.length) { return c.json({ error: "Unauthorized" }, 401); } const encoder = new TextEncoder(); const tokenBuf = encoder.encode(token); const secretBuf = encoder.encode(secret); try { if (!timingSafeEqual(tokenBuf, secretBuf)) { return c.json({ error: "Unauthorized" }, 401); } } catch { // timingSafeEqual can throw if buffers are different lengths // (though we pre-check, this is defensive) return c.json({ error: "Unauthorized" }, 401); } return next(); };