BillTracker/middleware/requireAuth.js

79 lines
2.9 KiB
JavaScript

const crypto = require('crypto');
const { getSessionUser, COOKIE_NAME, SINGLE_COOKIE_NAME, cookieOpts, publicUser, recordLogin } = require('../services/authService');
const { getDb, getSetting } = require('../db/database');
const { standardizeError } = require('./errorFormatter');
function getSingleModeUser() {
if (getSetting('auth_mode') !== 'single') return null;
const userId = getSetting('default_user_id');
if (!userId) return null;
// Single-user mode: validate only that the configured user exists,
// is active, and has role 'user'. Sessions are not relevant here —
// single-user mode bypasses session auth entirely.
const row = getDb().prepare(`
SELECT id, username, display_name, role, must_change_password, first_login,
active, is_default_admin, last_seen_version
FROM users
WHERE id = ? AND role = 'user' AND active = 1
`).get(userId);
return row ? publicUser(row) : null;
}
function requireAuth(req, res, next) {
// Single-user mode: bypass session entirely, auto-attach the default user
const singleUser = getSingleModeUser();
if (singleUser) {
req.user = singleUser;
req.singleUserMode = true;
// Track logins via a presence cookie so login history works without a real session.
// A new cookie = new browser/device visit → record a login entry.
const existing = req.cookies?.[SINGLE_COOKIE_NAME];
if (existing) {
req.singleSessionId = existing;
} else {
const sessionId = crypto.randomUUID();
res.cookie(SINGLE_COOKIE_NAME, sessionId, {
httpOnly: true,
sameSite: 'strict',
secure: cookieOpts(req).secure,
maxAge: 30 * 86400 * 1000, // 30 days
path: '/',
});
req.singleSessionId = sessionId;
// Non-blocking — don't delay the first request
setImmediate(() => {
try { recordLogin(singleUser.id, req.ip, req.get('user-agent'), sessionId); } catch {}
});
}
return next();
}
const user = getSessionUser(req.cookies?.[COOKIE_NAME]);
if (!user) return res.status(401).json(standardizeError('Not authenticated', 'AUTH_ERROR'));
req.user = user;
next();
}
function requireUser(req, res, next) {
if (req.user?.is_default_admin) {
return res.status(403).json(standardizeError('Default admin account does not have tracker access', 'FORBIDDEN'));
}
if (!['user', 'admin'].includes(req.user?.role)) {
return res.status(403).json(standardizeError('Access denied: user account required', 'FORBIDDEN'));
}
next();
}
function requireAdmin(req, res, next) {
// In single-user mode the auto-attached user is never admin,
// so admin routes naturally stay protected by session.
if (req.user?.role !== 'admin') {
return res.status(403).json(standardizeError('Access denied: admin account required', 'FORBIDDEN'));
}
next();
}
module.exports = { requireAuth, requireUser, requireAdmin };