import { Router } from 'express'; import bcrypt from 'bcryptjs'; import { z } from 'zod'; import { pool } from '../db.js'; import { config } from '../config.js'; import { requireAuth, signUser } from '../middleware/auth.js'; import { audit } from '../services/audit.js'; export const authRouter = Router(); const loginSchema = z.object({ email: z.string().email(), password: z.string().min(1) }); authRouter.post('/login', async (req, res) => { const body = loginSchema.parse(req.body); const result = await pool.query( `SELECT id, email, password_hash, role, allowed_domains FROM admin_users WHERE email=$1 AND active=true`, [body.email.toLowerCase()], ); const user = result.rows[0]; if (!user || !(await bcrypt.compare(body.password, user.password_hash))) { res.status(401).json({ error: 'Invalid email or password' }); return; } const token = signUser({ id: user.id, email: user.email, role: user.role, allowed_domains: user.allowed_domains ?? [] }); res.cookie('mailadmin_token', token, { httpOnly: true, sameSite: 'lax', secure: config.cookieSecure, maxAge: 12 * 60 * 60 * 1000, }); res.json({ email: user.email, role: user.role, allowed_domains: user.allowed_domains ?? [] }); }); authRouter.post('/logout', (_req, res) => { res.clearCookie('mailadmin_token'); res.json({ ok: true }); }); authRouter.get('/me', requireAuth, (req, res) => { res.json(req.user); }); // Self-service password change. Requires the current password to prevent // session hijacking from changing the password silently. const changePwSchema = z.object({ current_password: z.string().min(1), new_password: z.string().min(8), }); authRouter.post('/change-password', requireAuth, async (req, res) => { const body = changePwSchema.parse(req.body); const result = await pool.query( `SELECT id, password_hash FROM admin_users WHERE email=$1 AND active=true`, [req.user!.email.toLowerCase()], ); const row = result.rows[0]; if (!row || !(await bcrypt.compare(body.current_password, row.password_hash))) { res.status(401).json({ error: 'Current password is incorrect' }); return; } const newHash = await bcrypt.hash(body.new_password, 12); await pool.query( `UPDATE admin_users SET password_hash=$1, updated_at=now() WHERE id=$2`, [newHash, row.id], ); await audit(req.user!.email, 'admin.self_password_change', 'admin', req.user!.email, {}, req.ip); res.json({ ok: true }); });