- 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>
58 lines
1.8 KiB
JavaScript
58 lines
1.8 KiB
JavaScript
const session = require('./session');
|
|
const { getUserById } = require('./db');
|
|
|
|
function jsonError(res, status, message) {
|
|
res.writeHead(status, { 'Content-Type': 'application/json' });
|
|
res.end(JSON.stringify({ error: message }));
|
|
}
|
|
|
|
// Attaches req.session and req.user; returns false and sends 401 if unauthenticated.
|
|
function requireAuth(req, res) {
|
|
const token = session.getTokenFromRequest(req);
|
|
if (!token) {
|
|
jsonError(res, 401, 'Not authenticated');
|
|
return false;
|
|
}
|
|
const row = session.get(token);
|
|
if (!row) {
|
|
session.clearCookie(res);
|
|
jsonError(res, 401, 'Session expired');
|
|
return false;
|
|
}
|
|
const user = getUserById(row.user_id);
|
|
if (!user || !user.is_active) {
|
|
session.destroy(token);
|
|
session.clearCookie(res);
|
|
jsonError(res, 401, 'Account inactive');
|
|
return false;
|
|
}
|
|
req.session = { ...row, token };
|
|
req.user = user;
|
|
return true;
|
|
}
|
|
|
|
// Like requireAuth, but also rejects limited sessions (must_change_password users
|
|
// who haven't yet set their password — they may only call /api/auth/me and /api/auth/change-password).
|
|
function requireFullSession(req, res) {
|
|
if (!requireAuth(req, res)) return false;
|
|
if (req.user.must_change_password) {
|
|
jsonError(res, 403, 'Password change required');
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function requireAdmin(req, res) {
|
|
if (!requireAuth(req, res)) return false;
|
|
if (req.user.role !== 'admin') {
|
|
jsonError(res, 403, 'Admin access required');
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function getClientIp(req) {
|
|
return (req.headers['x-forwarded-for'] || req.socket.remoteAddress || '').split(',')[0].trim();
|
|
}
|
|
|
|
module.exports = { requireAuth, requireFullSession, requireAdmin, jsonError, getClientIp };
|