77 lines
2.2 KiB
JavaScript
77 lines
2.2 KiB
JavaScript
/**
|
|
* Rate Limiting Middleware
|
|
*
|
|
* Simple in-memory rate limiter.
|
|
* For production, consider using Redis-backed solution.
|
|
*/
|
|
|
|
import { config } from '../config.js';
|
|
|
|
// In-memory store for request counts
|
|
const requestCounts = new Map();
|
|
|
|
// Cleanup old entries periodically
|
|
setInterval(() => {
|
|
const now = Date.now();
|
|
for (const [key, data] of requestCounts) {
|
|
if (now - data.windowStart > config.rateLimitWindowMs * 2) {
|
|
requestCounts.delete(key);
|
|
}
|
|
}
|
|
}, 60000); // Every minute
|
|
|
|
/**
|
|
* Get client identifier from request
|
|
*/
|
|
function getClientId(req) {
|
|
// Use X-Forwarded-For header if behind proxy
|
|
const forwarded = req.headers['x-forwarded-for'];
|
|
if (forwarded) {
|
|
return forwarded.split(',')[0].trim();
|
|
}
|
|
return req.ip || (req.connection ? req.connection.remoteAddress : null) || 'unknown';
|
|
}
|
|
|
|
/**
|
|
* Rate limiting middleware
|
|
*/
|
|
export function rateLimit(req, res, next) {
|
|
const clientId = getClientId(req);
|
|
const now = Date.now();
|
|
|
|
let data = requestCounts.get(clientId);
|
|
|
|
if (!data || now - data.windowStart > config.rateLimitWindowMs) {
|
|
// New window
|
|
data = {
|
|
windowStart: now,
|
|
count: 1
|
|
};
|
|
requestCounts.set(clientId, data);
|
|
next();
|
|
return;
|
|
}
|
|
|
|
data.count++;
|
|
|
|
if (data.count > config.rateLimitMaxRequests) {
|
|
const retryAfter = Math.ceil((data.windowStart + config.rateLimitWindowMs - now) / 1000);
|
|
|
|
res.set('Retry-After', retryAfter.toString());
|
|
res.set('X-RateLimit-Limit', config.rateLimitMaxRequests.toString());
|
|
res.set('X-RateLimit-Remaining', '0');
|
|
res.set('X-RateLimit-Reset', new Date(data.windowStart + config.rateLimitWindowMs).toISOString());
|
|
|
|
return res.status(429).json({
|
|
error: 'Too many requests',
|
|
retry_after: retryAfter
|
|
});
|
|
}
|
|
|
|
// Add rate limit headers
|
|
res.set('X-RateLimit-Limit', config.rateLimitMaxRequests.toString());
|
|
res.set('X-RateLimit-Remaining', (config.rateLimitMaxRequests - data.count).toString());
|
|
res.set('X-RateLimit-Reset', new Date(data.windowStart + config.rateLimitWindowMs).toISOString());
|
|
|
|
next();
|
|
} |