169 lines
5.7 KiB
JavaScript
169 lines
5.7 KiB
JavaScript
const rateLimit = require('express-rate-limit');
|
|
const { ipKeyGenerator } = require('express-rate-limit');
|
|
const { getConfig } = require('../config/config');
|
|
|
|
/**
|
|
* Rate limiting middleware for different endpoint types
|
|
*
|
|
* Rate limits are configured based on authentication state and endpoint sensitivity:
|
|
* - Strict limits for authentication endpoints (login, register)
|
|
* - Moderate limits for general API endpoints
|
|
* - Higher limits for authenticated users with API tokens
|
|
*
|
|
* Configuration is centralized in backend/config/config.js and can be customized via environment variables.
|
|
* Rate limiting is automatically disabled in test environment.
|
|
*/
|
|
|
|
const config = getConfig();
|
|
const rateLimitConfig = config.rateLimiting;
|
|
|
|
// Skip rate limiting if disabled in config
|
|
const skipInTest = (req) => !rateLimitConfig.enabled;
|
|
|
|
/**
|
|
* Strict rate limiting for authentication endpoints
|
|
* Prevents brute force attacks on login/register
|
|
*/
|
|
const authLimiter = rateLimit({
|
|
windowMs: rateLimitConfig.auth.windowMs,
|
|
max: rateLimitConfig.auth.max,
|
|
message: {
|
|
error: 'Too many authentication attempts from this IP, please try again after 15 minutes',
|
|
},
|
|
standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
|
|
legacyHeaders: false, // Disable the `X-RateLimit-*` headers
|
|
skip: skipInTest,
|
|
handler: (req, res) => {
|
|
res.status(429).json({
|
|
error: 'Too many authentication attempts',
|
|
message:
|
|
'You have exceeded the maximum number of login attempts. Please try again after 15 minutes.',
|
|
retryAfter: Math.ceil(req.rateLimit.resetTime / 1000),
|
|
});
|
|
},
|
|
});
|
|
|
|
/**
|
|
* General API rate limiting for unauthenticated requests
|
|
*/
|
|
const apiLimiter = rateLimit({
|
|
windowMs: rateLimitConfig.api.windowMs,
|
|
max: rateLimitConfig.api.max,
|
|
message: {
|
|
error: 'Too many requests from this IP, please try again later',
|
|
},
|
|
standardHeaders: true,
|
|
legacyHeaders: false,
|
|
// Skip rate limiting for authenticated users or if disabled
|
|
skip: (req) => {
|
|
// Skip if rate limiting is disabled
|
|
if (!rateLimitConfig.enabled) return true;
|
|
// If user is authenticated via session or API token, skip this limiter
|
|
return !!(req.session?.userId || req.user);
|
|
},
|
|
handler: (req, res) => {
|
|
res.status(429).json({
|
|
error: 'Rate limit exceeded',
|
|
message:
|
|
'You have exceeded the maximum number of requests. Please try again later.',
|
|
retryAfter: Math.ceil(req.rateLimit.resetTime / 1000),
|
|
});
|
|
},
|
|
});
|
|
|
|
/**
|
|
* Rate limiting for authenticated API requests
|
|
* More lenient limits for authenticated users
|
|
*/
|
|
const authenticatedApiLimiter = rateLimit({
|
|
windowMs: rateLimitConfig.authenticatedApi.windowMs,
|
|
max: rateLimitConfig.authenticatedApi.max,
|
|
standardHeaders: true,
|
|
legacyHeaders: false,
|
|
// Use user ID as the key instead of IP for authenticated requests
|
|
keyGenerator: (req) => {
|
|
// Prefer user ID from session or API token authentication
|
|
const userId =
|
|
req.session?.userId?.toString() || req.user?.id?.toString();
|
|
if (userId) return userId;
|
|
// Use proper IPv6-compatible IP key generator as fallback
|
|
return ipKeyGenerator(req);
|
|
},
|
|
// Only apply to authenticated requests or if disabled
|
|
skip: (req) => {
|
|
// Skip if rate limiting is disabled
|
|
if (!rateLimitConfig.enabled) return true;
|
|
// Skip if not authenticated
|
|
return !(req.session?.userId || req.user);
|
|
},
|
|
handler: (req, res) => {
|
|
res.status(429).json({
|
|
error: 'Rate limit exceeded',
|
|
message:
|
|
'You have exceeded the maximum number of requests. Please try again later.',
|
|
retryAfter: Math.ceil(req.rateLimit.resetTime / 1000),
|
|
});
|
|
},
|
|
});
|
|
|
|
/**
|
|
* Stricter rate limiting for resource creation endpoints
|
|
* Prevents spam and abuse
|
|
*/
|
|
const createResourceLimiter = rateLimit({
|
|
windowMs: rateLimitConfig.createResource.windowMs,
|
|
max: rateLimitConfig.createResource.max,
|
|
standardHeaders: true,
|
|
legacyHeaders: false,
|
|
skip: skipInTest,
|
|
keyGenerator: (req) => {
|
|
const userId =
|
|
req.session?.userId?.toString() || req.user?.id?.toString();
|
|
if (userId) return userId;
|
|
// Use proper IPv6-compatible IP key generator as fallback
|
|
return ipKeyGenerator(req);
|
|
},
|
|
handler: (req, res) => {
|
|
res.status(429).json({
|
|
error: 'Rate limit exceeded',
|
|
message:
|
|
'You have exceeded the maximum number of resource creation requests. Please try again later.',
|
|
retryAfter: Math.ceil(req.rateLimit.resetTime / 1000),
|
|
});
|
|
},
|
|
});
|
|
|
|
/**
|
|
* Rate limiting for API key management endpoints
|
|
* Very strict to prevent abuse
|
|
*/
|
|
const apiKeyManagementLimiter = rateLimit({
|
|
windowMs: rateLimitConfig.apiKeyManagement.windowMs,
|
|
max: rateLimitConfig.apiKeyManagement.max,
|
|
standardHeaders: true,
|
|
legacyHeaders: false,
|
|
skip: skipInTest,
|
|
keyGenerator: (req) => {
|
|
const userId =
|
|
req.session?.userId?.toString() || req.user?.id?.toString();
|
|
if (userId) return userId;
|
|
// Use proper IPv6-compatible IP key generator as fallback
|
|
return ipKeyGenerator(req);
|
|
},
|
|
handler: (req, res) => {
|
|
res.status(429).json({
|
|
error: 'Rate limit exceeded',
|
|
message:
|
|
'You have exceeded the maximum number of API key management requests. Please try again later.',
|
|
retryAfter: Math.ceil(req.rateLimit.resetTime / 1000),
|
|
});
|
|
},
|
|
});
|
|
|
|
module.exports = {
|
|
authLimiter,
|
|
apiLimiter,
|
|
authenticatedApiLimiter,
|
|
createResourceLimiter,
|
|
apiKeyManagementLimiter,
|
|
};
|