import crypto from "node:crypto"; import { generateSecret, verify, generateURI } from "otplib"; import QRCode from "qrcode"; import { encryptCredential, decryptCredential } from "@/lib/crypto/credentials"; const ISSUER = "top-tickets"; const RECOVERY_CODE_COUNT = 8; export function generateTotpSecret(): string { return generateSecret(); } export function encryptTotpSecret(secret: string): string { return encryptCredential(secret); } export function decryptTotpSecret(encrypted: string): string { return decryptCredential(encrypted); } export async function buildTotpQrCode(secret: string, email: string): Promise { const uri = generateURI({ issuer: ISSUER, label: email, secret }); return QRCode.toDataURL(uri); } export async function verifyTotpCode(secret: string, code: string): Promise { const result = await verify({ secret, token: code.trim() }); return result.valid; } /** Human-typeable: groups of 5 lowercase hex chars, e.g. "a1b2c-d3e4f". */ function formatRecoveryCode(): string { const raw = crypto.randomBytes(5).toString("hex"); return `${raw.slice(0, 5)}-${raw.slice(5, 10)}`; } export function generateRecoveryCodes(count = RECOVERY_CODE_COUNT): string[] { return Array.from({ length: count }, formatRecoveryCode); } export function hashRecoveryCode(code: string): string { return crypto.createHash("sha256").update(code.trim().toLowerCase()).digest("hex"); }