feat: enhance rate limiter options to support dynamic maxRequests and conditional skipping

This commit is contained in:
Rotimi Best
2026-03-12 09:39:17 +01:00
parent 3a2487a322
commit 6bc0c9543c
+16 -7
View File
@@ -13,8 +13,9 @@ import { redis } from '@api/utils/redis/redis';
export interface RateLimiterOptions { export interface RateLimiterOptions {
windowMs?: number; windowMs?: number;
maxRequests?: number; maxRequests?: number | ((c: any) => number);
keyGenerator?: (c: any) => string; keyGenerator?: (c: any) => string;
skip?: (c: any) => boolean;
skipSuccessfulRequests?: boolean; skipSuccessfulRequests?: boolean;
skipFailedRequests?: boolean; skipFailedRequests?: boolean;
message?: string; message?: string;
@@ -24,20 +25,23 @@ export interface RateLimiterOptions {
const defaultOptions: Required<RateLimiterOptions> = { const defaultOptions: Required<RateLimiterOptions> = {
...DEFAULT_RATE_LIMITER_OPTIONS, ...DEFAULT_RATE_LIMITER_OPTIONS,
keyGenerator: userKeyGenerator keyGenerator: userKeyGenerator,
skip: () => false
}; };
export const createRateLimiter = (options: RateLimiterOptions = {}): MiddlewareHandler => { export const createRateLimiter = (options: RateLimiterOptions = {}): MiddlewareHandler => {
const opts = { ...defaultOptions, ...options }; const opts = { ...defaultOptions, ...options };
const limiter = new RedisRateLimiter(redis, opts.windowMs, opts.maxRequests);
return async (c, next) => { return async (c, next) => {
// Skip rate limiting if not in production // Skip rate limiting if not in production
if (env.NODE_ENV !== 'production') { if (env.NODE_ENV !== 'production' || opts.skip(c)) {
return await next(); return await next();
} }
try { try {
const maxRequests = typeof opts.maxRequests === 'function' ? opts.maxRequests(c) : opts.maxRequests;
const limiter = new RedisRateLimiter(redis, opts.windowMs, maxRequests);
// Generate rate limit key // Generate rate limit key
const key = opts.keyGenerator(c); const key = opts.keyGenerator(c);
@@ -46,13 +50,13 @@ export const createRateLimiter = (options: RateLimiterOptions = {}): MiddlewareH
// Set rate limit headers // Set rate limit headers
if (opts.standardHeaders) { if (opts.standardHeaders) {
c.header(RATE_LIMIT_HEADERS.LIMIT, opts.maxRequests.toString()); c.header(RATE_LIMIT_HEADERS.LIMIT, maxRequests.toString());
c.header(RATE_LIMIT_HEADERS.REMAINING, result.remaining.toString()); c.header(RATE_LIMIT_HEADERS.REMAINING, result.remaining.toString());
c.header(RATE_LIMIT_HEADERS.RESET, new Date(result.resetTime).toISOString()); c.header(RATE_LIMIT_HEADERS.RESET, new Date(result.resetTime).toISOString());
} }
if (opts.legacyHeaders) { if (opts.legacyHeaders) {
c.header(RATE_LIMIT_HEADERS.LIMIT, opts.maxRequests.toString()); c.header(RATE_LIMIT_HEADERS.LIMIT, maxRequests.toString());
c.header(RATE_LIMIT_HEADERS.REMAINING, result.remaining.toString()); c.header(RATE_LIMIT_HEADERS.REMAINING, result.remaining.toString());
c.header(RATE_LIMIT_HEADERS.RESET, Math.ceil(result.resetTime / 1000).toString()); c.header(RATE_LIMIT_HEADERS.RESET, Math.ceil(result.resetTime / 1000).toString());
} }
@@ -84,4 +88,9 @@ export const createRateLimiter = (options: RateLimiterOptions = {}): MiddlewareH
}; };
// Default rate limiter middleware with standard configuration // Default rate limiter middleware with standard configuration
export default createRateLimiter(); export default createRateLimiter({
maxRequests: (c) =>
c.req.path === '/api/auth/get-session'
? DEFAULT_RATE_LIMITER_OPTIONS.maxRequests * 3
: DEFAULT_RATE_LIMITER_OPTIONS.maxRequests
});