const jwt = require('jsonwebtoken'); const bcrypt = require('bcryptjs'); const logger = require('../utils/logger'); const database = require('../config/database'); const redis = require('../config/redis'); // Protect routes - require authentication const protect = async (req, res, next) => { let token; if (req.headers.authorization && req.headers.authorization.startsWith('Bearer')) { token = req.headers.authorization.split(' ')[1]; } if (!token) { return res.status(401).json({ success: false, message: 'Not authorized to access this route' }); } try { // Verify token const decoded = jwt.verify(token, process.env.JWT_SECRET); // Check if user session exists in Redis const session = await redis.getUserSession(decoded.id); if (!session) { return res.status(401).json({ success: false, message: 'Session expired, please login again' }); } // Get user from database const [users] = await database.query( 'SELECT id, username, email, role, status, created_at FROM users WHERE id = ? AND status = "active"', [decoded.id] ); if (users.length === 0) { return res.status(401).json({ success: false, message: 'User not found or inactive' }); } req.user = users[0]; next(); } catch (error) { logger.error('Token verification failed:', error); return res.status(401).json({ success: false, message: 'Not authorized to access this route' }); } }; // Grant access to specific roles const authorize = (...roles) => { return (req, res, next) => { if (!req.user) { return res.status(401).json({ success: false, message: 'User not authenticated' }); } if (!roles.includes(req.user.role)) { return res.status(403).json({ success: false, message: `User role ${req.user.role} is not authorized to access this route` }); } next(); }; }; // Optional authentication - doesn't fail if no token const optionalAuth = async (req, res, next) => { let token; if (req.headers.authorization && req.headers.authorization.startsWith('Bearer')) { token = req.headers.authorization.split(' ')[1]; } if (!token) { req.user = null; return next(); } try { const decoded = jwt.verify(token, process.env.JWT_SECRET); const session = await redis.getUserSession(decoded.id); if (session) { const [users] = await database.query( 'SELECT id, username, email, role, status FROM users WHERE id = ? AND status = "active"', [decoded.id] ); if (users.length > 0) { req.user = users[0]; } } } catch (error) { logger.debug('Optional auth failed:', error.message); } next(); }; // Rate limiting for authentication attempts const authRateLimit = (req, res, next) => { const key = `auth_attempts:${req.ip}`; redis.get(key).then(attempts => { const attemptCount = attempts ? parseInt(attempts) : 0; if (attemptCount >= 5) { return res.status(429).json({ success: false, message: 'Too many authentication attempts. Please try again later.' }); } next(); }).catch(error => { logger.error('Rate limit check failed:', error); next(); // Continue if Redis is unavailable }); }; // Log authentication attempts const logAuthAttempt = (req, res, next) => { const originalSend = res.send; res.send = function(data) { const response = JSON.parse(data); if (response.success === false && req.path.includes('/login')) { const key = `auth_attempts:${req.ip}`; redis.get(key).then(attempts => { const attemptCount = attempts ? parseInt(attempts) : 0; redis.set(key, attemptCount + 1, 900); // 15 minutes }).catch(error => { logger.error('Failed to log auth attempt:', error); }); logger.logSecurity('failed_login', { ip: req.ip, userAgent: req.get('User-Agent'), username: req.body.username }, 'warn'); } else if (response.success === true && req.path.includes('/login')) { logger.logSecurity('successful_login', { ip: req.ip, userAgent: req.get('User-Agent'), username: req.body.username }, 'info'); } originalSend.call(this, data); }; next(); }; // Generate JWT token const generateToken = (userId) => { return jwt.sign( { id: userId }, process.env.JWT_SECRET, { expiresIn: process.env.JWT_EXPIRES_IN || '24h' } ); }; // Hash password const hashPassword = async (password) => { const salt = await bcrypt.genSalt(10); return bcrypt.hash(password, salt); }; // Compare password const comparePassword = async (password, hashedPassword) => { return bcrypt.compare(password, hashedPassword); }; module.exports = { protect, authorize, optionalAuth, authRateLimit, logAuthAttempt, generateToken, hashPassword, comparePassword };