Files
kelolabumi-web/lib/contact-captcha.ts
2026-04-23 01:43:48 +07:00

55 lines
1.3 KiB
TypeScript

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();
}