qapinterface
Version:
Comprehensive API utilities for Node.js applications including authentication, security, request processing, and response handling with zero external dependencies
70 lines (61 loc) • 2.65 kB
JavaScript
/**
* Rate Limiter Middleware Creator
* Single Responsibility: Create rate limiting middleware ONLY
*/
const localVars = require('../../config/localVars');
const { logger } = require('../asyncLogger');
const { createRateLimiter } = require('./limiter-creator');
/**
* Creates an Express middleware for rate limiting requests.
* @param {object} [options] - Options passed to createRateLimiter.
* @returns {function} An Express middleware function.
*/
function createRateLimiterMiddleware(options = {}) {
return (req, res, next) => {
try {
// Determine rate limit settings based on endpoint and user tier
let points = localVars.DEFAULT_RATE_LIMIT_POINTS;
let duration = localVars.DEFAULT_RATE_LIMIT_DURATION;
// Check for endpoint-specific override
const endpoint = req.path;
if (localVars.ENDPOINT_RATE_LIMITS[endpoint]) {
points = localVars.ENDPOINT_RATE_LIMITS[endpoint].points;
duration = localVars.ENDPOINT_RATE_LIMITS[endpoint].duration;
}
// Check for user-tier override
const userTier = req.user?.tier || 'free';
if (localVars.RATE_LIMIT_TIERS[userTier]) {
points = localVars.RATE_LIMIT_TIERS[userTier].points;
duration = localVars.RATE_LIMIT_TIERS[userTier].duration;
}
// Create limiter instance with calculated settings
const limiter = createRateLimiter({ ...options, points, duration });
// Use user ID if available, else IP address
const key = req.user?.id || req.ip;
limiter.consume(key)
.then((rateLimiterRes) => {
logger.debug(`Rate limit consumed for ${key}: ${rateLimiterRes.remainingPoints}/${points} remaining`);
next();
})
.catch((rateLimiterRes) => {
const retryAfter = Math.ceil(rateLimiterRes.msBeforeNext / 1000);
logger.warn(`Rate limit exceeded for ${key}: ${points} req/${duration}s - Retry after ${retryAfter}s`);
res.setHeader('Retry-After', String(retryAfter));
res.writeHead(429, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
error: 'Too Many Requests',
message: `Rate limit exceeded. Try again in ${retryAfter} seconds.`,
limit: points,
remaining: rateLimiterRes.remainingPoints,
reset: new Date(Date.now() + rateLimiterRes.msBeforeNext)
}));
});
} catch (err) {
logger.error(`Rate limiter error: ${err.message}`, { stack: err.stack });
next(); // Fail open to avoid blocking traffic
}
};
}
module.exports = {
createRateLimiterMiddleware
};