Initial Kelola Bumi website
This commit is contained in:
54
lib/contact-captcha.ts
Normal file
54
lib/contact-captcha.ts
Normal file
@ -0,0 +1,54 @@
|
||||
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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user