import { randomInt, timingSafeEqual } from "crypto"; import { queryWithRetry } from "./db.js"; const CODE_EXPIRY_MS = 15 * 60 * 1000; const MAX_ATTEMPTS = 3; export async function createPendingVerification(email) { await queryWithRetry("DELETE FROM pending_verifications WHERE email = $1", [email]); const now = new Date(); const pending = { email, code: String(randomInt(100000, 999999)), createdAt: now.toISOString(), expiresAt: new Date(now.getTime() + CODE_EXPIRY_MS).toISOString(), attempts: 0, }; await queryWithRetry("INSERT INTO pending_verifications (email, code, created_at, expires_at, attempts) VALUES ($1, $2, $3, $4, $5)", [pending.email, pending.code, pending.createdAt, pending.expiresAt, pending.attempts]); return pending; } export async function verifyCode(email, code) { const cleanEmail = email.trim().toLowerCase(); const result = await queryWithRetry("SELECT * FROM pending_verifications WHERE email = $1", [cleanEmail]); const pending = result.rows[0]; if (!pending) return { status: "invalid" }; if (new Date() > new Date(pending.expires_at)) { await queryWithRetry("DELETE FROM pending_verifications WHERE email = $1", [cleanEmail]); return { status: "expired" }; } if (pending.attempts >= MAX_ATTEMPTS) { await queryWithRetry("DELETE FROM pending_verifications WHERE email = $1", [cleanEmail]); return { status: "max_attempts" }; } await queryWithRetry("UPDATE pending_verifications SET attempts = attempts + 1 WHERE email = $1", [cleanEmail]); const a = Buffer.from(pending.code, "utf8"); const b = Buffer.from(code, "utf8"); const codeMatch = a.length === b.length && timingSafeEqual(a, b); if (!codeMatch) { return { status: "invalid" }; } await queryWithRetry("DELETE FROM pending_verifications WHERE email = $1", [cleanEmail]); return { status: "ok" }; }