hikma-engine
Version:
Code Knowledge Graph Indexer - A sophisticated TypeScript-based indexer that transforms Git repositories into multi-dimensional knowledge stores for AI agents
165 lines (164 loc) • 6.5 kB
JavaScript
/**
* @file Rate limiting middleware for API requests.
* Implements configurable rate limiting per IP address with different limits for different endpoints.
*/
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.RateLimiters = exports.developmentRateLimit = exports.healthCheckRateLimit = exports.heavySearchRateLimit = exports.searchRateLimit = exports.globalRateLimit = void 0;
exports.getEnvironmentRateLimit = getEnvironmentRateLimit;
exports.addRateLimitHeaders = addRateLimitHeaders;
const express_rate_limit_1 = __importDefault(require("express-rate-limit"));
const logger_1 = require("../../utils/logger");
const logger = (0, logger_1.getLogger)('RateLimitMiddleware');
/**
* Custom rate limit exceeded response handler.
*/
function rateLimitExceededHandler(req, res) {
const requestId = req.headers['x-request-id'] || 'unknown';
logger.warn('Rate limit exceeded', {
requestId,
ip: req.ip,
method: req.method,
url: req.url,
userAgent: req.get('User-Agent'),
});
res.status(429).json({
success: false,
error: {
code: 'RATE_LIMIT_EXCEEDED',
message: 'Too many requests from this IP address. Please try again later.',
retryAfter: Math.ceil(req.rateLimit?.resetTime ? (req.rateLimit.resetTime.getTime() - Date.now()) / 1000 : 60),
},
meta: {
timestamp: new Date().toISOString(),
requestId,
rateLimit: {
limit: req.rateLimit?.limit,
current: req.rateLimit?.limit ? (req.rateLimit.limit - (req.rateLimit.remaining || 0)) : 0,
remaining: req.rateLimit?.remaining,
resetTime: req.rateLimit?.resetTime?.toISOString(),
},
},
});
}
/**
* Skip function for rate limiting - allows bypassing rate limits for certain conditions.
*/
function skipLimitFunction(req) {
// Skip rate limiting for health check endpoints
if (req.path.startsWith('/health')) {
return true;
}
// Skip rate limiting for root endpoint
if (req.path === '/') {
return true;
}
return false;
}
/**
* Creates a rate limiting middleware with specified configuration.
*/
function createRateLimiter(config) {
return (0, express_rate_limit_1.default)({
windowMs: config.windowMs,
max: config.max,
message: config.message,
standardHeaders: config.standardHeaders ?? true,
legacyHeaders: config.legacyHeaders ?? false,
skipSuccessfulRequests: config.skipSuccessfulRequests ?? false,
skipFailedRequests: config.skipFailedRequests ?? false,
// Use default keyGenerator for IPv6 compatibility
skip: skipLimitFunction,
handler: rateLimitExceededHandler,
// Note: onLimitReached is not available in newer versions of express-rate-limit
// The rate limit exceeded logging is handled in the handler function
});
}
/**
* Global rate limiter - applies to all API endpoints.
* Default: 100 requests per 15 minutes per IP.
*/
exports.globalRateLimit = createRateLimiter({
windowMs: 15 * 60 * 1000, // 15 minutes
max: parseInt(process.env.GLOBAL_RATE_LIMIT || '100', 10),
message: 'Too many requests from this IP address. Please try again later.',
});
/**
* Search endpoint rate limiter - more restrictive for search operations.
* Default: 50 requests per 10 minutes per IP.
*/
exports.searchRateLimit = createRateLimiter({
windowMs: 10 * 60 * 1000, // 10 minutes
max: parseInt(process.env.SEARCH_RATE_LIMIT || '50', 10),
message: 'Too many search requests from this IP address. Please try again later.',
skipSuccessfulRequests: false,
skipFailedRequests: true, // Don't count failed requests (validation errors, etc.)
});
/**
* Heavy search operations rate limiter - very restrictive for resource-intensive operations.
* Default: 20 requests per 10 minutes per IP.
*/
exports.heavySearchRateLimit = createRateLimiter({
windowMs: 10 * 60 * 1000, // 10 minutes
max: parseInt(process.env.HEAVY_SEARCH_RATE_LIMIT || '20', 10),
message: 'Too many heavy search requests from this IP address. Please try again later.',
skipSuccessfulRequests: false,
skipFailedRequests: true,
});
/**
* Health check rate limiter - very permissive for monitoring.
* Default: 200 requests per 5 minutes per IP.
*/
exports.healthCheckRateLimit = createRateLimiter({
windowMs: 5 * 60 * 1000, // 5 minutes
max: parseInt(process.env.HEALTH_RATE_LIMIT || '200', 10),
message: 'Too many health check requests from this IP address.',
});
/**
* Development rate limiter - more permissive for development environments.
* Default: 500 requests per 15 minutes per IP.
*/
exports.developmentRateLimit = createRateLimiter({
windowMs: 15 * 60 * 1000, // 15 minutes
max: parseInt(process.env.DEV_RATE_LIMIT || '500', 10),
message: 'Too many requests from this IP address (development mode).',
});
/**
* Returns appropriate rate limiter based on environment.
*/
function getEnvironmentRateLimit() {
const env = process.env.NODE_ENV || 'development';
if (env === 'development' || env === 'test') {
logger.info('Using development rate limits');
return exports.developmentRateLimit;
}
logger.info('Using production rate limits');
return exports.globalRateLimit;
}
/**
* Rate limiting configuration for different endpoint types.
*/
exports.RateLimiters = {
global: exports.globalRateLimit,
search: exports.searchRateLimit,
heavySearch: exports.heavySearchRateLimit,
healthCheck: exports.healthCheckRateLimit,
development: exports.developmentRateLimit,
environment: getEnvironmentRateLimit(),
};
/**
* Middleware to add rate limit information to response headers.
*/
function addRateLimitHeaders(req, res, next) {
// Add custom rate limit headers if available
if (req.rateLimit) {
res.setHeader('X-RateLimit-Limit', req.rateLimit.limit.toString());
res.setHeader('X-RateLimit-Remaining', req.rateLimit.remaining.toString());
res.setHeader('X-RateLimit-Reset', req.rateLimit.resetTime?.toISOString() || '');
res.setHeader('X-RateLimit-Used', (req.rateLimit.limit - req.rateLimit.remaining).toString());
}
next();
}
;