/** * Admin Authentication Middleware * Handles JWT token validation and permission checking for admin endpoints */ const { verifyAdminToken, extractTokenFromHeader } = require('../utils/jwt'); const logger = require('../utils/logger'); /** * Middleware to authenticate admin requests * @param {Object} req - Express request object * @param {Object} res - Express response object * @param {Function} next - Express next function */ async function authenticateAdmin(req, res, next) { try { const correlationId = req.correlationId; // Extract token from Authorization header const authHeader = req.get('Authorization'); const token = extractTokenFromHeader(authHeader); if (!token) { logger.warn('Admin authentication failed - no token provided', { correlationId, ip: req.ip, userAgent: req.get('User-Agent'), path: req.path, }); return res.status(401).json({ error: 'Authentication required', message: 'No authentication token provided', correlationId, }); } // Verify the token const decoded = verifyAdminToken(token); // Add admin information to request object req.user = { adminId: decoded.adminId, email: decoded.email, username: decoded.username, permissions: decoded.permissions || [], type: 'admin', iat: decoded.iat, exp: decoded.exp, }; // Log admin access logger.audit('Admin authenticated', { correlationId, adminId: decoded.adminId, username: decoded.username, permissions: decoded.permissions, path: req.path, method: req.method, ip: req.ip, userAgent: req.get('User-Agent'), }); next(); } catch (error) { const correlationId = req.correlationId; logger.warn('Admin authentication failed', { correlationId, error: error.message, ip: req.ip, userAgent: req.get('User-Agent'), path: req.path, }); let statusCode = 401; let message = 'Invalid authentication token'; if (error.message === 'Token expired') { statusCode = 401; message = 'Authentication token has expired'; } else if (error.message === 'Invalid token') { statusCode = 401; message = 'Invalid authentication token'; } return res.status(statusCode).json({ error: 'Authentication failed', message, correlationId, }); } } /** * Middleware factory to check if admin has required permissions * @param {string|Array} requiredPermissions - Permission(s) required * @returns {Function} Express middleware function */ function requirePermissions(requiredPermissions) { // Normalize to array const permissions = Array.isArray(requiredPermissions) ? requiredPermissions : [requiredPermissions]; return (req, res, next) => { try { const correlationId = req.correlationId; const adminPermissions = req.user?.permissions || []; const adminId = req.user?.adminId; const username = req.user?.username; if (!adminId) { logger.warn('Permission check failed - no authenticated admin', { correlationId, requiredPermissions: permissions, path: req.path, }); return res.status(401).json({ error: 'Authentication required', message: 'Admin authentication required', correlationId, }); } // Check if admin has super admin permission (bypasses all checks) if (adminPermissions.includes('super_admin')) { logger.info('Permission check passed - super admin', { correlationId, adminId, username, requiredPermissions: permissions, path: req.path, }); return next(); } // Check if admin has all required permissions const hasPermissions = permissions.every(permission => adminPermissions.includes(permission), ); if (!hasPermissions) { const missingPermissions = permissions.filter(permission => !adminPermissions.includes(permission), ); logger.warn('Permission check failed - insufficient permissions', { correlationId, adminId, username, adminPermissions, requiredPermissions: permissions, missingPermissions, path: req.path, method: req.method, }); return res.status(403).json({ error: 'Insufficient permissions', message: 'You do not have the required permissions to access this resource', requiredPermissions: permissions, correlationId, }); } logger.info('Permission check passed', { correlationId, adminId, username, requiredPermissions: permissions, path: req.path, }); next(); } catch (error) { logger.error('Permission check error', { correlationId: req.correlationId, error: error.message, stack: error.stack, requiredPermissions: permissions, }); return res.status(500).json({ error: 'Internal server error', message: 'Failed to verify permissions', correlationId: req.correlationId, }); } }; } /** * Middleware to check if admin can access player-specific resources * Requires 'player_management' permission or ownership * @param {string} paramName - Parameter name containing the player ID * @returns {Function} Express middleware function */ function requirePlayerAccess(paramName = 'playerId') { return (req, res, next) => { try { const correlationId = req.correlationId; const adminPermissions = req.user?.permissions || []; const adminId = req.user?.adminId; const username = req.user?.username; const targetPlayerId = req.params[paramName]; if (!adminId) { return res.status(401).json({ error: 'Authentication required', correlationId, }); } // Super admin can access everything if (adminPermissions.includes('super_admin')) { return next(); } // Check for player management permission if (adminPermissions.includes('player_management')) { logger.info('Player access granted - player management permission', { correlationId, adminId, username, targetPlayerId, path: req.path, }); return next(); } // Check for read-only player data permission for GET requests if (req.method === 'GET' && adminPermissions.includes('player_data_read')) { logger.info('Player access granted - read-only permission', { correlationId, adminId, username, targetPlayerId, path: req.path, }); return next(); } logger.warn('Player access denied - insufficient permissions', { correlationId, adminId, username, adminPermissions, targetPlayerId, path: req.path, method: req.method, }); return res.status(403).json({ error: 'Insufficient permissions', message: 'You do not have permission to access player data', correlationId, }); } catch (error) { logger.error('Player access check error', { correlationId: req.correlationId, error: error.message, stack: error.stack, }); return res.status(500).json({ error: 'Internal server error', message: 'Failed to verify player access permissions', correlationId: req.correlationId, }); } }; } /** * Middleware to log admin actions for audit purposes * @param {string} action - Action being performed * @returns {Function} Express middleware function */ function auditAdminAction(action) { return (req, res, next) => { try { const correlationId = req.correlationId; const adminId = req.user?.adminId; const username = req.user?.username; // Log the action logger.audit('Admin action initiated', { correlationId, adminId, username, action, path: req.path, method: req.method, params: req.params, query: req.query, ip: req.ip, userAgent: req.get('User-Agent'), }); // Override res.json to log the response const originalJson = res.json; res.json = function (data) { logger.audit('Admin action completed', { correlationId, adminId, username, action, path: req.path, method: req.method, statusCode: res.statusCode, success: res.statusCode < 400, }); return originalJson.call(this, data); }; next(); } catch (error) { logger.error('Admin audit logging error', { correlationId: req.correlationId, error: error.message, stack: error.stack, action, }); // Continue even if audit logging fails next(); } }; } /** * Common admin permission constants */ const ADMIN_PERMISSIONS = { SUPER_ADMIN: 'super_admin', PLAYER_MANAGEMENT: 'player_management', PLAYER_DATA_READ: 'player_data_read', SYSTEM_MANAGEMENT: 'system_management', GAME_MANAGEMENT: 'game_management', EVENT_MANAGEMENT: 'event_management', ANALYTICS_READ: 'analytics_read', CONTENT_MANAGEMENT: 'content_management', }; module.exports = { authenticateAdmin, requirePermissions, requirePlayerAccess, auditAdminAction, ADMIN_PERMISSIONS, };