- Real email/password login backed by SQLite (better-sqlite3) - HttpOnly cookie sessions with 8h sliding TTL - Admin role: invite users via Mailgun magic-link, manage roles/status - Per-user One2Edit username mapping for job filtering - Self-service forgot-password / reset-password via email - Admin console (admin.html) with user table, invite modal, row actions - New pages: change-password, forgot-password, reset-password, accept-invite - Gated /api proxy: requires valid session, anti-hijack sessionId check - Bootstrap initial admins from INITIAL_ADMINS env var on first boot - Remove Oliver login button, SSO buttons, and legacy api.js/login.js - deploy.sh: add build-essential (for native module), npm install, data dir Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
23 lines
825 B
JavaScript
23 lines
825 B
JavaScript
const crypto = require('crypto');
|
|
|
|
const PARAMS = { N: 16384, r: 8, p: 1, keylen: 64 };
|
|
|
|
function hash(plain) {
|
|
const salt = crypto.randomBytes(16);
|
|
const key = crypto.scryptSync(plain, salt, PARAMS.keylen, { N: PARAMS.N, r: PARAMS.r, p: PARAMS.p });
|
|
return `scrypt$${PARAMS.N}$${salt.toString('base64')}$${key.toString('base64')}`;
|
|
}
|
|
|
|
function verify(plain, stored) {
|
|
try {
|
|
const [, N, saltB64, hashB64] = stored.split('$');
|
|
const salt = Buffer.from(saltB64, 'base64');
|
|
const expected = Buffer.from(hashB64, 'base64');
|
|
const actual = crypto.scryptSync(plain, salt, expected.length, { N: parseInt(N, 10), r: PARAMS.r, p: PARAMS.p });
|
|
return crypto.timingSafeEqual(actual, expected);
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
module.exports = { hash, verify };
|