3m-portal/lib/password.js
Vadym Samoilenko 53a85c788d Add full auth system: SQLite sessions, email invites, admin console
- 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>
2026-05-05 11:26:40 +01:00

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 };