codenuk_backend_mine/services/user-auth/src/middleware/auth.js

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
};