import { createHash, randomBytes, scrypt, timingSafeEqual } from 'node:crypto'; import { promisify } from 'node:util'; const scryptAsync = promisify(scrypt); const N = 16_384; const R = 8; const P = 1; export const SESSION_COOKIE = 'court_session'; export const SESSION_DAYS = 7; export function hashToken(token) { return createHash('sha256').update(token).digest('hex'); } export async function hashPassword(password) { const salt = randomBytes(16); const derived = await scryptAsync(password, salt, 64, { N, r: R, p: P, maxmem: 32 * 1024 * 1024 }); return `scrypt$${N},${R},${P}$${salt.toString('base64url')}$${Buffer.from(derived).toString('base64url')}`; } export async function verifyPassword(password, encoded) { try { const [algorithm, params, saltText, hashText] = String(encoded).split('$'); if (algorithm !== 'scrypt' || !params || !saltText || !hashText) return false; const [nText, rText, pText] = params.split(','); const n = Number(nText); const r = Number(rText); const p = Number(pText); if (n !== N || r !== R || p !== P) return false; const expected = Buffer.from(hashText, 'base64url'); const derived = Buffer.from(await scryptAsync(password, Buffer.from(saltText, 'base64url'), expected.length, { N: n, r, p, maxmem: 32 * 1024 * 1024 })); if (derived.length !== expected.length) return false; return timingSafeEqual(derived, expected); } catch { return false; } } export function newSessionToken() { return randomBytes(32).toString('base64url'); } export function sessionExpiry(now = Date.now()) { return new Date(now + SESSION_DAYS * 24 * 60 * 60 * 1000).toISOString(); } export function validEmail(email) { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); }