2025-12-19 20:44:46 -06:00
|
|
|
const rateLimit = require("express-rate-limit");
|
|
|
|
|
const logger = require("./logger");
|
|
|
|
|
const { RATE_LIMITS, HTTP_STATUS } = require("./constants");
|
|
|
|
|
|
|
|
|
|
const createRateLimiter = (config, limitType = "API") => {
|
|
|
|
|
return rateLimit({
|
|
|
|
|
windowMs: config.windowMs,
|
|
|
|
|
max: config.max,
|
|
|
|
|
skipSuccessfulRequests: config.skipSuccessfulRequests || false,
|
2026-01-18 02:22:05 -06:00
|
|
|
skipFailedRequests: config.skipFailedRequests || false,
|
2025-12-19 20:44:46 -06:00
|
|
|
message: {
|
|
|
|
|
success: false,
|
|
|
|
|
message: config.message,
|
|
|
|
|
},
|
|
|
|
|
standardHeaders: true,
|
|
|
|
|
legacyHeaders: false,
|
2026-01-18 02:22:05 -06:00
|
|
|
// Use X-Forwarded-For header from nginx/proxy - properly handle IPv6
|
|
|
|
|
keyGenerator: (req, res) => {
|
|
|
|
|
const ip =
|
|
|
|
|
req.headers["x-forwarded-for"]?.split(",")[0]?.trim() ||
|
|
|
|
|
req.headers["x-real-ip"] ||
|
|
|
|
|
req.ip ||
|
|
|
|
|
req.connection.remoteAddress;
|
|
|
|
|
// Normalize IPv6 addresses to prevent bypass
|
|
|
|
|
return ip.includes(":") ? ip.replace(/:/g, "-") : ip;
|
|
|
|
|
},
|
2025-12-19 20:44:46 -06:00
|
|
|
handler: (req, res) => {
|
2026-01-18 02:22:05 -06:00
|
|
|
const clientIp =
|
|
|
|
|
req.headers["x-forwarded-for"]?.split(",")[0]?.trim() || req.ip;
|
2025-12-19 20:44:46 -06:00
|
|
|
logger.warn(`${limitType} rate limit exceeded`, {
|
2026-01-18 02:22:05 -06:00
|
|
|
ip: clientIp,
|
2025-12-19 20:44:46 -06:00
|
|
|
path: req.path,
|
|
|
|
|
email: req.body?.email,
|
|
|
|
|
});
|
|
|
|
|
res.status(HTTP_STATUS.TOO_MANY_REQUESTS).json({
|
|
|
|
|
success: false,
|
|
|
|
|
message: config.message,
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// General API rate limiter
|
|
|
|
|
const apiLimiter = createRateLimiter(
|
|
|
|
|
{
|
|
|
|
|
windowMs:
|
|
|
|
|
parseInt(process.env.RATE_LIMIT_WINDOW_MS) || RATE_LIMITS.API.windowMs,
|
|
|
|
|
max: parseInt(process.env.RATE_LIMIT_MAX_REQUESTS) || RATE_LIMITS.API.max,
|
|
|
|
|
message: "Too many requests from this IP, please try again later.",
|
|
|
|
|
},
|
2026-01-18 02:22:05 -06:00
|
|
|
"API",
|
2025-12-19 20:44:46 -06:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// Strict limiter for authentication endpoints
|
|
|
|
|
const authLimiter = createRateLimiter(
|
|
|
|
|
{
|
|
|
|
|
windowMs: RATE_LIMITS.AUTH.windowMs,
|
|
|
|
|
max: RATE_LIMITS.AUTH.max,
|
|
|
|
|
skipSuccessfulRequests: true,
|
|
|
|
|
message: "Too many login attempts, please try again after 15 minutes.",
|
|
|
|
|
},
|
2026-01-18 02:22:05 -06:00
|
|
|
"Auth",
|
2025-12-19 20:44:46 -06:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// File upload limiter
|
|
|
|
|
const uploadLimiter = createRateLimiter(
|
|
|
|
|
{
|
|
|
|
|
windowMs: RATE_LIMITS.UPLOAD.windowMs,
|
|
|
|
|
max: RATE_LIMITS.UPLOAD.max,
|
|
|
|
|
message: "Upload limit reached, please try again later.",
|
|
|
|
|
},
|
2026-01-18 02:22:05 -06:00
|
|
|
"Upload",
|
2025-12-19 20:44:46 -06:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
module.exports = {
|
|
|
|
|
apiLimiter,
|
|
|
|
|
authLimiter,
|
|
|
|
|
uploadLimiter,
|
|
|
|
|
};
|