// Password hashing and session token helpers. const crypto = require('crypto'); const PASSWORD_ITERATIONS = Number(process.env.PASSWORD_HASH_ITERATIONS || 310000); const PASSWORD_KEY_LENGTH = 32; const PASSWORD_DIGEST = 'sha256'; const SESSION_BYTES = 32; function validatePasswordStrength(password) { const value = String(password || ''); const hasLowercase = /[a-z]/.test(value); const hasUppercase = /[A-Z]/.test(value); const hasNumber = /[0-9]/.test(value); const hasSymbol = /[^A-Za-z0-9]/.test(value); const categoryCount = [hasLowercase, hasUppercase, hasNumber, hasSymbol].filter(Boolean).length; if (value.length < 10 || categoryCount < 3) { return 'Password must be at least 10 characters and include 3 of: uppercase, lowercase, number, and symbol.'; } return ''; } function hashPassword(password, salt) { const safePassword = String(password || ''); const safeSalt = salt || crypto.randomBytes(16).toString('hex'); const hash = crypto.pbkdf2Sync(safePassword, safeSalt, PASSWORD_ITERATIONS, PASSWORD_KEY_LENGTH, PASSWORD_DIGEST).toString('hex'); return { salt: safeSalt, hash: hash, iterations: PASSWORD_ITERATIONS }; } function verifyPassword(password, record) { if (!record || !record.password_salt || !record.password_hash) { return false; } const iterations = Number(record.password_iterations || PASSWORD_ITERATIONS); const safePassword = String(password || ''); const expected = crypto.pbkdf2Sync(safePassword, String(record.password_salt), iterations, PASSWORD_KEY_LENGTH, PASSWORD_DIGEST); const actual = Buffer.from(String(record.password_hash), 'hex'); return actual.length === expected.length && crypto.timingSafeEqual(actual, expected); } function createSessionToken() { return crypto.randomBytes(SESSION_BYTES).toString('hex'); } function hashSessionToken(token) { return crypto.createHash('sha256').update(String(token || '')).digest('hex'); } module.exports = { hashPassword, verifyPassword, validatePasswordStrength, createSessionToken, hashSessionToken };