import crypto from "node:crypto"; const CAPTCHA_TTL_MS = 10 * 60 * 1000; function getSecret() { return process.env.CONTACT_CAPTCHA_SECRET ?? "fallback-contact-captcha-secret"; } export function generateCaptchaChallenge() { const left = crypto.randomInt(1, 10); const right = crypto.randomInt(1, 10); const answer = left + right; const expiresAt = Date.now() + CAPTCHA_TTL_MS; const payload = `${answer}:${expiresAt}`; const signature = crypto .createHmac("sha256", getSecret()) .update(payload) .digest("hex"); return { prompt: `${left} + ${right} = ?`, token: `${payload}:${signature}` }; } export function verifyCaptchaToken(token: string, answer: string) { const parts = token.split(":"); if (parts.length !== 3) { return false; } const [expectedAnswer, expiresAt, signature] = parts; const payload = `${expectedAnswer}:${expiresAt}`; const expectedSignature = crypto .createHmac("sha256", getSecret()) .update(payload) .digest("hex"); const isSignatureValid = crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expectedSignature) ); if (!isSignatureValid) { return false; } if (Number(expiresAt) < Date.now()) { return false; } return expectedAnswer === answer.trim(); }