import crypto from "crypto"; export function generateCode(): string { return crypto.randomInt(0, 1_000_000).toString().padStart(6, "0"); } export function hashCode(code: string, secret: string): string { return crypto.createHash("sha256").update(code + secret).digest("hex"); } export type VerificationRowState = { codeHash: string | null; attempts: number; expires: Date; }; export type CodeAttemptOutcome = | { outcome: "ok" } | { outcome: "wrong"; newAttempts: number } | { outcome: "locked-out" } | { outcome: "expired" } | { outcome: "no-such-row" }; const MAX_ATTEMPTS = 5; export function evaluateCodeAttempt({ submittedCodeHash, row, now, }: { submittedCodeHash: string; row: VerificationRowState | null; now: Date; }): CodeAttemptOutcome { if (!row) return { outcome: "no-such-row" }; if (row.expires.getTime() <= now.getTime()) return { outcome: "expired" }; if (row.attempts >= MAX_ATTEMPTS) return { outcome: "locked-out" }; if (row.codeHash === submittedCodeHash) return { outcome: "ok" }; const newAttempts = row.attempts + 1; if (newAttempts >= MAX_ATTEMPTS) return { outcome: "locked-out" }; return { outcome: "wrong", newAttempts }; }