import crypto from 'node:crypto' export function normalizeEmail(email: string) { return email.trim().toLowerCase() } export function isValidEmail(email: string) { const e = normalizeEmail(email) return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e) } export function isStrongPassword(password: string) { if (password.length < 8) return false const hasLetter = /[a-zA-Z]/.test(password) const hasNumber = /\d/.test(password) return hasLetter && hasNumber } export async function hashPassword(password: string) { const salt = crypto.randomBytes(16) const key = await new Promise((resolve, reject) => { crypto.scrypt(password, salt, 64, { N: 16384, r: 8, p: 1 }, (err, derivedKey) => { if (err) reject(err) else resolve(derivedKey as Buffer) }) }) return `scrypt$${salt.toString('hex')}$${key.toString('hex')}` } export async function verifyPassword(password: string, stored: string) { const parts = stored.split('$') if (parts.length !== 3 || parts[0] !== 'scrypt') return false const salt = Buffer.from(parts[1], 'hex') const expected = Buffer.from(parts[2], 'hex') const key = await new Promise((resolve, reject) => { crypto.scrypt(password, salt, 64, { N: 16384, r: 8, p: 1 }, (err, derivedKey) => { if (err) reject(err) else resolve(derivedKey as Buffer) }) }) if (key.length !== expected.length) return false return crypto.timingSafeEqual(key, expected) } export function hashBackupCode(code: string, saltHex?: string) { const salt = saltHex ? Buffer.from(saltHex, 'hex') : crypto.randomBytes(16) const h = crypto.createHash('sha256').update(salt).update(code).digest('hex') return `sha256$${salt.toString('hex')}$${h}` } export function verifyBackupCode(code: string, stored: string) { const parts = stored.split('$') if (parts.length !== 3 || parts[0] !== 'sha256') return false const saltHex = parts[1] const expected = parts[2] const actual = hashBackupCode(code, saltHex).split('$')[2] return crypto.timingSafeEqual(Buffer.from(actual), Buffer.from(expected)) } export function generateBackupCodes(count: number) { const codes: string[] = [] for (let i = 0; i < count; i++) { const raw = crypto.randomBytes(5).toString('hex').slice(0, 10).toUpperCase() codes.push(`${raw.slice(0, 5)}-${raw.slice(5)}`) } return codes }