const jwt = require('jsonwebtoken'); const logger = require('../utils/logger'); const db = require('../database/db').db; const { SESSION_POLICY } = require('../utils/passwordPolicy'); const JWT_SECRET = process.env.JWT_SECRET || 'change_this_in_production'; const authenticate = (req, res, next) => { // Check Authorization header first, then query parameter let token = req.headers.authorization?.split(' ')[1]; if (!token && req.query.token) { token = req.query.token; } if (!token) { logger.info('[AUTH] No token provided'); return res.status(401).json({ error: 'Authentication required' }); } // CWE-532: Do not log tokens or token details - they are credentials logger.info('[AUTH] Verifying authentication token'); try { const decoded = jwt.verify(token, JWT_SECRET); logger.info(`[AUTH] Token verified for user ${decoded.userId}`); // Check session activity and idle timeout db.get( 'SELECT * FROM active_sessions WHERE session_token = ? AND user_id = ?', [token, decoded.userId], (err, session) => { if (err) { logger.error('Session check error:', err); return res.status(500).json({ error: 'Session validation failed' }); } if (!session) { logger.info('[AUTH] Session not found for token in database'); return res.status(401).json({ error: 'Session not found or expired', sessionExpired: true }); } logger.info('[AUTH] Session found, checking expiry'); // Check if session has expired (absolute timeout) const now = new Date(); const expiresAt = new Date(session.expires_at); if (now >= expiresAt) { // Delete expired session db.run('DELETE FROM active_sessions WHERE id = ?', [session.id]); return res.status(401).json({ error: 'Session expired', sessionExpired: true }); } // Check idle timeout (2 hours by default) const lastActivity = new Date(session.last_activity); const idleTimeMs = now - lastActivity; const idleTimeoutMs = SESSION_POLICY.idleTimeout * 60 * 60 * 1000; // Convert hours to ms if (idleTimeMs > idleTimeoutMs) { // Session idle for too long - terminate it db.run('DELETE FROM active_sessions WHERE id = ?', [session.id]); logger.info(`Session ${session.id} terminated due to idle timeout (${idleTimeMs}ms idle)`); return res.status(401).json({ error: 'Session expired due to inactivity', sessionExpired: true }); } // Update last activity db.run( 'UPDATE active_sessions SET last_activity = ? WHERE id = ?', [now.toISOString(), session.id], (updateErr) => { if (updateErr) { logger.error('Failed to update session activity:', updateErr); } } ); req.user = decoded; req.sessionId = session.id; next(); } ); } catch (error) { logger.error('Authentication error:', error); logger.error(`[AUTH] JWT Verification Failed: ${error.name} - ${error.message}`); // Provide more specific error messages let errorMessage = 'Invalid or expired token'; if (error.name === 'TokenExpiredError') { errorMessage = 'Token has expired'; } else if (error.name === 'JsonWebTokenError') { errorMessage = 'Invalid token'; } res.status(401).json({ error: errorMessage, sessionExpired: true // This triggers automatic logout on frontend }); } }; const authorize = (...roles) => { return (req, res, next) => { if (!req.user) { return res.status(401).json({ error: 'Authentication required' }); } if (!roles.includes(req.user.role)) { return res.status(403).json({ error: 'Insufficient permissions' }); } next(); }; }; // Convenience middleware for admin-only routes const requireAdmin = (req, res, next) => { if (!req.user) { return res.status(401).json({ error: 'Authentication required' }); } if (req.user.role !== 'admin') { return res.status(403).json({ error: 'Admin access required' }); } next(); }; module.exports = { authenticate, authorize, requireAdmin };