'use strict'; const crypto = require('node:crypto'); // HMAC key for signing session cookies. Persisted in the DB (settings.session_secret) and injected // via setSessionSecret() at startup, so sessions survive restarts with zero config. The random // fallback only covers the brief window before init (and is replaced once the DB value loads). let sessionSecret = crypto.randomBytes(32).toString('hex'); function setSessionSecret(secret) { if (secret) sessionSecret = String(secret); } const SESSION_TTL_MS = 1000 * 60 * 60 * 24 * 30; function hashPassword(password, salt = crypto.randomBytes(16).toString('hex')) { const hash = crypto.pbkdf2Sync(String(password), salt, 210000, 32, 'sha256').toString('hex'); return `pbkdf2_sha256$210000$${salt}$${hash}`; } function verifyPassword(password, stored) { const parts = String(stored || '').split('$'); if (parts.length !== 4 || parts[0] !== 'pbkdf2_sha256') return false; const [, roundsText, salt, expected] = parts; const actual = crypto.pbkdf2Sync(String(password), salt, Number(roundsText), 32, 'sha256').toString('hex'); return crypto.timingSafeEqual(Buffer.from(actual, 'hex'), Buffer.from(expected, 'hex')); } function sign(value) { return crypto.createHmac('sha256', sessionSecret).update(value).digest('base64url'); } function createSessionCookie() { const payload = Buffer.from(JSON.stringify({ iat: Date.now(), exp: Date.now() + SESSION_TTL_MS })).toString('base64url'); return `${payload}.${sign(payload)}`; } function parseCookies(req) { const out = {}; for (const part of String(req.headers.cookie || '').split(';')) { const index = part.indexOf('='); if (index > -1) out[part.slice(0, index).trim()] = decodeURIComponent(part.slice(index + 1).trim()); } return out; } function isAuthed(req) { const token = parseCookies(req).bv_session; if (!token || !token.includes('.')) return false; const [payload, sig] = token.split('.'); if (!sig || sign(payload) !== sig) return false; try { const data = JSON.parse(Buffer.from(payload, 'base64url').toString('utf8')); return Number(data.exp || 0) > Date.now(); } catch { return false; } } module.exports = { SESSION_TTL_MS, setSessionSecret, hashPassword, verifyPassword, sign, createSessionCookie, parseCookies, isAuthed };