const express = require('express'); const router = express.Router(); const { getDb, getSetting, setSetting } = require('../db/database'); const { login, logout, hashPassword, cookieOpts, COOKIE_NAME } = require('../services/authService'); const { requireAuth, requireAdmin } = require('../middleware/requireAuth'); const { getPublicOidcInfo } = require('../services/oidcService'); const { loginLimiter, passwordLimiter } = require('../middleware/rateLimiter'); // ───────────────────────────────────────── // PUBLIC AUTH ROUTES // ───────────────────────────────────────── // POST /api/auth/login router.post('/login', loginLimiter, async (req, res) => { // Respect admin-configured login method toggle if (getSetting('local_login_enabled') === 'false') { return res.status(403).json({ error: 'Local username/password login is not enabled on this server.' }); } const { username, password } = req.body; if (!username || !password) { return res.status(400).json({ error: 'Username and password are required' }); } const result = await login(username, password); if (!result) { return res.status(401).json({ error: 'Invalid username or password' }); } res.cookie(COOKIE_NAME, result.sessionId, cookieOpts(req)); res.json({ user: result.user }); }); // POST /api/auth/logout router.post('/logout', requireAuth, (req, res) => { logout(req.cookies?.[COOKIE_NAME]); res.clearCookie(COOKIE_NAME, { path: '/', ...cookieOpts(req), maxAge: undefined }); res.json({ success: true }); }); // GET /api/auth/me router.get('/me', requireAuth, (req, res) => { res.json({ user: req.user, single_user_mode: !!req.singleUserMode, }); }); // GET /api/auth/mode // Public — tells the login page which options are available. // Never returns secrets. local_enabled/oidc_enabled reflect admin settings. router.get('/mode', (req, res) => { const oidcInfo = getPublicOidcInfo(); const localEnabled = getSetting('local_login_enabled') !== 'false'; res.json({ auth_mode: getSetting('auth_mode') || 'multi', local_enabled: localEnabled, ...oidcInfo, }); }); // POST /api/auth/restore-multi-user-mode // Recovery path for single-user mode. In single-user mode requireAuth attaches // the configured default user, so this lets that Settings page restore normal // login without needing access to Admin routes. router.post('/restore-multi-user-mode', requireAuth, (req, res) => { if (!req.singleUserMode && getSetting('auth_mode') !== 'single') { return res.status(400).json({ error: 'Single-user mode is not enabled.' }); } setSetting('auth_mode', 'multi'); setSetting('default_user_id', ''); res.json({ success: true, auth_mode: 'multi' }); }); // POST /api/auth/acknowledge-privacy router.post('/acknowledge-privacy', requireAuth, (req, res) => { getDb().prepare( "UPDATE users SET first_login = 0, updated_at = datetime('now') WHERE id = ?" ).run(req.user.id); res.json({ success: true }); }); // POST /api/auth/change-password router.post('/change-password', requireAuth, passwordLimiter, async (req, res) => { const { current_password, new_password } = req.body; if (!new_password || new_password.length < 8) { return res.status(400).json({ error: 'New password must be at least 8 characters' }); } const db = getDb(); const user = db.prepare('SELECT * FROM users WHERE id = ?').get(req.user.id); if (!user.must_change_password) { const bcrypt = require('bcryptjs'); const valid = await bcrypt.compare(current_password || '', user.password_hash); if (!valid) return res.status(401).json({ error: 'Current password is incorrect' }); } const hash = await hashPassword(new_password); db.prepare( "UPDATE users SET password_hash = ?, must_change_password = 0, updated_at = datetime('now') WHERE id = ?" ).run(hash, req.user.id); res.json({ success: true }); }); // ───────────────────────────────────────── // ADMIN ROUTES (MOUNTED AT /api/admin) // ───────────────────────────────────────── // GET /api/admin/has-users router.get('/has-users', (req, res) => { const count = getDb() .prepare("SELECT COUNT(*) AS n FROM users WHERE role = 'user'") .get().n; res.json({ has_users: count > 0 }); }); // GET /api/admin/users router.get('/users', requireAuth, requireAdmin, (req, res) => { const users = getDb().prepare( "SELECT id, username, role, must_change_password, first_login, created_at FROM users ORDER BY role DESC, username ASC" ).all(); res.json(users); }); // POST /api/admin/users router.post('/users', requireAuth, requireAdmin, async (req, res) => { const { username, password } = req.body; if (!username || username.length < 3) { return res.status(400).json({ error: 'Username must be at least 3 characters' }); } if (!password || password.length < 8) { return res.status(400).json({ error: 'Password must be at least 8 characters' }); } const db = getDb(); const existing = db.prepare('SELECT id FROM users WHERE username = ?').get(username); if (existing) return res.status(409).json({ error: 'Username already taken' }); const hash = await hashPassword(password); const result = db.prepare( "INSERT INTO users (username, password_hash, role, first_login) VALUES (?, ?, 'user', 1)" ).run(username, hash); const created = db.prepare( 'SELECT id, username, role, must_change_password, first_login, created_at FROM users WHERE id = ?' ).get(result.lastInsertRowid); res.status(201).json(created); }); module.exports = router;