From c48bfa1e1ae711a8a6955e7341e29fb999f094e2 Mon Sep 17 00:00:00 2001 From: Ben Stokes Date: Fri, 21 Aug 2026 09:36:36 -0700 Subject: [PATCH 1/2] fix: keep auth and global rate limits on separate counters --- apps/api/src/middleware/RateLimiter.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/api/src/middleware/RateLimiter.ts b/apps/api/src/middleware/RateLimiter.ts index 6f5f10b..d1bf777 100644 --- a/apps/api/src/middleware/RateLimiter.ts +++ b/apps/api/src/middleware/RateLimiter.ts @@ -22,6 +22,7 @@ interface RateLimitOptions { } const store: RateLimitStore = {}; +let limiterId = 0; // Clean up expired entries periodically setInterval(() => { @@ -42,9 +43,10 @@ export function RateLimiter(options: RateLimitOptions) { maxRequests, keyGenerator = (req: Request) => req.ip || 'unknown', } = options; + const id = ++limiterId; return (req: Request, res: Response, next: NextFunction) => { - const key = keyGenerator(req); + const key = `${id}:${keyGenerator(req)}`; const now = Date.now(); // Initialize or reset if window expired From c33ae66e404404de049101bd5e4f302cef6d302e Mon Sep 17 00:00:00 2001 From: Ben Stokes Date: Fri, 21 Aug 2026 09:53:19 -0700 Subject: [PATCH 2/2] fix: change to named counters --- apps/api/src/middleware/RateLimiter.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/apps/api/src/middleware/RateLimiter.ts b/apps/api/src/middleware/RateLimiter.ts index d1bf777..083d2fe 100644 --- a/apps/api/src/middleware/RateLimiter.ts +++ b/apps/api/src/middleware/RateLimiter.ts @@ -14,6 +14,7 @@ interface RateLimitStore { } interface RateLimitOptions { + name: string; windowMs: number; // Time window in milliseconds maxRequests: number; // Max requests per window keyGenerator?: (req: Request) => string; // Custom key generator @@ -22,7 +23,6 @@ interface RateLimitOptions { } const store: RateLimitStore = {}; -let limiterId = 0; // Clean up expired entries periodically setInterval(() => { @@ -39,14 +39,14 @@ setInterval(() => { */ export function RateLimiter(options: RateLimitOptions) { const { + name, windowMs, maxRequests, keyGenerator = (req: Request) => req.ip || 'unknown', } = options; - const id = ++limiterId; return (req: Request, res: Response, next: NextFunction) => { - const key = `${id}:${keyGenerator(req)}`; + const key = `${name}:${keyGenerator(req)}`; const now = Date.now(); // Initialize or reset if window expired @@ -90,24 +90,28 @@ export function RateLimiter(options: RateLimitOptions) { export const RateLimiters = { // Standard API rate limit: 5000 requests per 15 minutes standard: RateLimiter({ + name: 'standard', windowMs: 15 * 60 * 1000, maxRequests: 5000, }), // Strict rate limit for sensitive operations: 100 requests per minute strict: RateLimiter({ + name: 'strict', windowMs: 60 * 1000, maxRequests: 100, }), // Auth rate limit: 100 attempts per 15 minutes auth: RateLimiter({ + name: 'auth', windowMs: 15 * 60 * 1000, maxRequests: 100, }), // Rate limit by API key instead of IP byApiKey: RateLimiter({ + name: 'byApiKey', windowMs: 15 * 60 * 1000, maxRequests: 10000, keyGenerator: (req: Request) => {