This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
'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
|
||||
};
|
||||
Reference in New Issue
Block a user