// shared/auth/requireAuth.js import jwt from 'jsonwebtoken'; import pool from '../config/mysqlPool.js'; const { JWT_SECRET, TOKEN_MAX_AGE_MS, ACCESS_COOKIE_NAME = 'aptiva_access' } = process.env; const MAX_AGE = Number(TOKEN_MAX_AGE_MS || 0); function extractBearer(authz) { if (!authz || typeof authz !== 'string') return ''; if (!authz.toLowerCase().startsWith('bearer ')) return ''; const v = authz.slice(7).trim(); if (!v || v === 'null' || v === 'undefined') return ''; return v; } export async function requireAuth(req, res, next) { try { const cookieToken = req.cookies?.[ACCESS_COOKIE_NAME]; const bearerToken = extractBearer(req.headers.authorization); const token = cookieToken || bearerToken; // cookie always wins if (!token) return res.status(401).json({ error: 'Auth required' }); let payload; try { payload = jwt.verify(token, JWT_SECRET); } catch { return res.status(401).json({ error: 'Invalid or expired token' }); } const userId = payload.sub || payload.id || payload.userId; const iatMs = (payload.iat || 0) * 1000; if (MAX_AGE && Date.now() - iatMs > MAX_AGE) { return res.status(401).json({ error: 'Session expired. Please sign in again.' }); } const [rows] = await (pool.raw || pool).query( 'SELECT password_changed_at FROM user_auth WHERE user_id = ? ORDER BY id DESC LIMIT 1', [userId] ); const changedAtMs = rows?.[0]?.password_changed_at ? new Date(rows[0].password_changed_at).getTime() : 0; if (changedAtMs && iatMs < changedAtMs) { return res.status(401).json({ error: 'Session invalidated. Please sign in again.' }); } req.user = (payload && typeof payload === 'object') ? { ...payload, id: userId } : { id: userId }; req.userId = userId; next(); next(); } catch (e) { console.error('[requireAuth]', e?.message || e); res.status(500).json({ error: 'Server error' }); } }