314 lines
7.9 KiB
JavaScript
314 lines
7.9 KiB
JavaScript
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
|
|
}; |