const jwtConfig = require('../config/jwt'); const authService = require('../services/authService'); const rateLimit = require('express-rate-limit'); // JWT Authentication Middleware const authenticateToken = async (req, res, next) => { try { const authHeader = req.headers.authorization; if (!authHeader) { return res.status(401).json({ success: false, error: 'Access token required', message: 'Authorization header missing' }); } const token = jwtConfig.extractTokenFromHeader(authHeader); const user = await authService.verifyAccessToken(token); // Attach user to request req.user = user; req.token = token; next(); } catch (error) { console.error('🔐 Authentication failed:', error.message); return res.status(401).json({ success: false, error: 'Invalid access token', message: error.message }); } }; // Optional Authentication (doesn't fail if no token) const optionalAuth = async (req, res, next) => { try { const authHeader = req.headers.authorization; if (authHeader) { const token = jwtConfig.extractTokenFromHeader(authHeader); const user = await authService.verifyAccessToken(token); req.user = user; req.token = token; } next(); } catch (error) { // Continue without authentication for optional auth console.warn('⚠️ Optional auth failed:', error.message); next(); } }; // Role-based Authorization Middleware const requireRole = (roles) => { return (req, res, next) => { if (!req.user) { return res.status(401).json({ success: false, error: 'Authentication required', message: 'User not authenticated' }); } const userRole = req.user.role; const allowedRoles = Array.isArray(roles) ? roles : [roles]; if (!allowedRoles.includes(userRole)) { return res.status(403).json({ success: false, error: 'Insufficient permissions', message: `Role '${userRole}' is not authorized. Required: ${allowedRoles.join(', ')}` }); } next(); }; }; // Admin-only middleware const requireAdmin = requireRole(['admin']); // User or Admin middleware const requireUserOrAdmin = requireRole(['user', 'admin']); // Rate Limiting Middleware const createRateLimit = (windowMs, max, message) => { return rateLimit({ windowMs, max, message: { success: false, error: 'Rate limit exceeded', message, retryAfter: Math.ceil(windowMs / 1000) }, standardHeaders: true, legacyHeaders: false, // Custom key generator based on IP and user ID keyGenerator: (req) => { return req.user ? `${req.ip}-${req.user.id}` : req.ip; } }); }; // Specific rate limiters const loginRateLimit = createRateLimit( 15 * 60 * 1000, // 15 minutes 5, // 5 attempts 'Too many login attempts. Please try again in 15 minutes.' ); const registerRateLimit = createRateLimit( 60 * 60 * 1000, // 1 hour 3, // 3 registrations 'Too many registration attempts. Please try again in 1 hour.' ); const passwordChangeRateLimit = createRateLimit( 60 * 60 * 1000, // 1 hour 3, // 3 password changes 'Too many password change attempts. Please try again in 1 hour.' ); const apiRateLimit = createRateLimit( 15 * 60 * 10000, // 15 minutes 10000, // 100 requests 'Too many API requests. Please slow down.' ); // Session Validation Middleware const validateSession = async (req, res, next) => { try { const sessionToken = req.headers['x-session-token'] || req.cookies.sessionToken; if (sessionToken && req.user) { // Update session activity await authService.updateSessionActivity(sessionToken); } next(); } catch (error) { console.warn('⚠️ Session validation failed:', error.message); next(); // Continue even if session update fails } }; // Request Logging Middleware const logAuthRequests = (req, res, next) => { const start = Date.now(); // Log request console.log(`🔐 ${req.method} ${req.originalUrl} - ${req.ip} - ${req.user ? req.user.email : 'anonymous'}`); // Log response const originalSend = res.send; res.send = function(data) { const duration = Date.now() - start; const statusColor = res.statusCode >= 400 ? '❌' : '✅'; console.log(`${statusColor} ${res.statusCode} - ${duration}ms`); originalSend.call(this, data); }; next(); }; // Validate User Ownership (for user-specific resources) const validateOwnership = (req, res, next) => { const userId = req.params.userId || req.params.id; const requestingUserId = req.user.id; const userRole = req.user.role; // Admin can access any user's resources if (userRole === 'admin') { return next(); } // Users can only access their own resources if (userId && userId !== requestingUserId) { return res.status(403).json({ success: false, error: 'Access denied', message: 'You can only access your own resources' }); } next(); }; // Input Validation Middleware const validateRegistration = (req, res, next) => { const { username, email, password, first_name, last_name } = req.body; const errors = []; if (!username || username.length < 3) { errors.push('Username must be at least 3 characters long'); } if (!/^[a-zA-Z0-9_]+$/.test(username)) { errors.push('Username can only contain letters, numbers, and underscores'); } if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { errors.push('Valid email is required'); } if (!password || password.length < 8) { errors.push('Password must be at least 8 characters long'); } if (!first_name || first_name.trim().length === 0) { errors.push('First name is required'); } if (!last_name || last_name.trim().length === 0) { errors.push('Last name is required'); } if (errors.length > 0) { return res.status(400).json({ success: false, error: 'Validation failed', message: 'Please fix the following errors', details: errors }); } next(); }; const validateLogin = (req, res, next) => { const { email, password } = req.body; if (!email || !password) { return res.status(400).json({ success: false, error: 'Validation failed', message: 'Email and password are required' }); } next(); }; // Security Headers Middleware const securityHeaders = (req, res, next) => { res.setHeader('X-Content-Type-Options', 'nosniff'); res.setHeader('X-Frame-Options', 'DENY'); res.setHeader('X-XSS-Protection', '1; mode=block'); res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin'); next(); }; // Error Handler Middleware const authErrorHandler = (error, req, res, next) => { console.error('🔐 Auth Error:', error); // JWT specific errors if (error.name === 'JsonWebTokenError') { return res.status(401).json({ success: false, error: 'Invalid token', message: 'The provided token is malformed' }); } if (error.name === 'TokenExpiredError') { return res.status(401).json({ success: false, error: 'Token expired', message: 'Please refresh your token' }); } // Database errors if (error.code === '23505') { return res.status(409).json({ success: false, error: 'Conflict', message: 'Resource already exists' }); } // Default error res.status(500).json({ success: false, error: 'Internal server error', message: process.env.NODE_ENV === 'development' ? error.message : 'Something went wrong' }); }; module.exports = { authenticateToken, optionalAuth, requireRole, requireAdmin, requireUserOrAdmin, loginRateLimit, registerRateLimit, passwordChangeRateLimit, apiRateLimit, validateSession, logAuthRequests, validateOwnership, validateRegistration, validateLogin, securityHeaders, authErrorHandler };