const express = require('express'); const router = express.Router(); const pool = require('../../db'); const crypto = require('crypto'); const { promisify } = require('util'); const { sanitizeLoginInput } = require('../../middleware/sanitize_person_input'); const { strictInput } = require('../../middleware/strict_input'); const { signToken } = require('../../middleware/authenticate_token'); const scrypt = promisify(crypto.scrypt); // Fixed salt for unknown emails: the scrypt work is still performed so the // response time does not reveal whether the email address exists. const DUMMY_SALT = '00000000000000000000000000000000'; async function login(req, res) { try { const { email, password } = req.loginInput; const { rows } = await pool.query( 'SELECT id, password, token_version FROM people WHERE email = $1', [email] ); if (rows.length === 0) { await scrypt(password, DUMMY_SALT, 64); res.status(401).send('Invalid email or password'); return; } const [salt, storedKeyHex] = rows[0].password.split(':'); const storedKey = Buffer.from(storedKeyHex || '', 'hex'); const derivedKey = await scrypt(password, salt, 64); if ( storedKey.length !== derivedKey.length || !crypto.timingSafeEqual(storedKey, derivedKey) ) { res.status(401).send('Invalid email or password'); return; } const sessionId = crypto.randomUUID(); const deviceName = (req.headers['user-agent'] || 'unknown').slice(0, 255); await pool.query( 'INSERT INTO sessions (id, user_id, device_name) VALUES ($1, $2, $3)', [sessionId, rows[0].id, deviceName], ); const { token, expiresAt } = signToken(rows[0].id, { jti: sessionId, tokenVersion: rows[0].token_version, }); res.cookie('fc_session_token', token, { httpOnly: true, secure: true, sameSite: 'Strict', path: '/', }); res.status(200).json({ id: rows[0].id, token, expires_at: expiresAt }); } catch (err) { console.error('login failed', { message: err.message, code: err.code }); res.status(500).send('Login failed, request could not be completed'); } }; router.post('/', strictInput({ body: ['email', 'password'] }), sanitizeLoginInput, login); module.exports = router;