import jwt from 'jsonwebtoken'; import env from '../../config/env.js'; import * as db from '../../db/googleAuth.js'; import { meJson } from '../auth/meJson.js'; const userCache = new Map(); const USER_CACHE_TTL = 10 * 60 * 1000; const TOKEN_COOKIE = { httpOnly: true, secure: true, sameSite: 'strict', }; export function clearTokenCookie(res) { res.clearCookie('token', TOKEN_COOKIE); } export function getCachedUser(authId) { const cached = userCache.get(authId); if (cached && cached.expiresAt > Date.now()) { return cached.user; } userCache.delete(authId); return null; } export function setCachedUser(authId, user) { userCache.set(authId, { user, expiresAt: Date.now() + USER_CACHE_TTL }); if (userCache.size > 1000) { const now = Date.now(); for (const [k, v] of userCache) { if (v.expiresAt <= now) userCache.delete(k); } } } export function clearCachedUser(authId) { userCache.delete(authId); } function authMeJson(res, req, status, body) { if (req.path === '/api/auth/me') { return meJson(res, status, body); } return res.status(status).json(body); } export const authMiddleware = async (req, res, next) => { if (req.path === '/' || req.path === '/api/auth/login' || req.path === '/api/auth/logout' || req.path === '/api/health') { return next(); } if (req.path.startsWith('/v1') || req.path.startsWith('/api/v1')) { return next(); } if (req.path.startsWith('/@') || req.path.startsWith('/node_modules') || req.path.startsWith('/src') || req.path === '/favicon.ico') { return next(); } const token = req.cookies.token; if (!token) return authMeJson(res, req, 401, { error: 'Unauthorized' }); try { const decoded = jwt.verify(token, env.jwtSecret); const authId = Number(decoded.id); if (!Number.isInteger(authId) || authId <= 0) { clearTokenCookie(res); return authMeJson(res, req, 401, { error: 'Invalid token' }); } let user = getCachedUser(authId); if (!user) { const row = await db.getById(authId); if (row) { // Do not cache pictureBinary — large and only needed for /auth/me. const { pictureBinary: _pictureBinary, ...rest } = row; user = rest; setCachedUser(authId, user); } } if (!user) { clearCachedUser(authId); clearTokenCookie(res); return authMeJson(res, req, 403, { error: 'User is no longer authorized' }); } req.user = { id: user.id, email: user.email, selectedCompanyId: user.selectedCompanyId, }; const now = Math.floor(Date.now() / 1000); if (decoded.exp - now < 2 * 60 * 60) { const newToken = jwt.sign({ id: user.id }, env.jwtSecret, { expiresIn: '1d' }); res.cookie('token', newToken, { ...TOKEN_COOKIE, maxAge: 24 * 60 * 60 * 1000, }); } next(); } catch { clearTokenCookie(res); return authMeJson(res, req, 401, { error: 'Invalid token' }); } };