'use strict'; const express = require('express'); const bcrypt = require('bcryptjs'); const { getUserByUsername } = require('./db'); const router = express.Router(); // Dummy hash — used when user is not found to prevent timing attacks const DUMMY_HASH = '$2b$12$invaliddummyhashfortimingattttttttttttttttttttttttttt'; // POST /auth/login router.post('/login', async (req, res) => { const username = String(req.body?.username ?? '').trim(); const password = String(req.body?.password ?? ''); if (!username || !password) { return res.status(400).json({ error: 'Username and password are required.' }); } const user = getUserByUsername(username); const hash = user?.password_hash ?? DUMMY_HASH; // Always run bcrypt comparison — even for unknown users — to prevent timing-based enumeration const valid = await bcrypt.compare(password, hash); if (!valid || !user) { return res.status(401).json({ error: 'Invalid username or password.' }); } // Regenerate session ID to prevent session fixation req.session.regenerate((err) => { if (err) return res.status(500).json({ error: 'Session error.' }); req.session.user = { id: user.id, username: user.username, name: user.name, role: user.role, syncro_customer_id: user.syncro_customer_id ?? null, }; res.json({ ok: true, user: req.session.user }); }); }); // POST /auth/logout router.post('/logout', (req, res) => { req.session.destroy(() => { res.clearCookie('sid'); res.json({ ok: true }); }); }); // GET /auth/me — returns current session user (used by the SPA on startup) router.get('/me', (req, res) => { if (!req.session?.user) { return res.status(401).json({ error: 'Not authenticated.' }); } res.json({ user: req.session.user }); }); module.exports = router;