// Minimal shared-password auth. Enabled ONLY when LYRA_PASSWORD is set; otherwise the // app stays open (unchanged behavior) — see README "Security". The auth cookie holds a // token derived from the password via HMAC-SHA256 keyed by LYRA_SECRET_KEY, so it can't // be forged without the secret and the plaintext password is never stored client-side. // Edge-safe: uses global Web Crypto + btoa (no node:crypto / Buffer), so the same code // runs in middleware (Edge runtime) and in the API route (Node runtime). export const AUTH_COOKIE = "lyra_auth"; function toB64(bytes: Uint8Array): string { let s = ""; for (const b of bytes) s += String.fromCharCode(b); return btoa(s); } /** The token a valid session cookie must carry, or "" when auth is disabled (no password). */ export async function expectedToken(): Promise { const password = process.env.LYRA_PASSWORD; if (!password) return ""; const secret = process.env.LYRA_SECRET_KEY ?? "lyra"; const enc = new TextEncoder(); const key = await crypto.subtle.importKey( "raw", enc.encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"], ); const sig = await crypto.subtle.sign("HMAC", key, enc.encode(password)); return toB64(new Uint8Array(sig)); } /** Constant-time string compare (both operands are our own derived tokens). */ export function safeEqual(a: string, b: string): boolean { if (a.length !== b.length) return false; let diff = 0; for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i); return diff === 0; }