@@ -22,25 +22,30 @@ const WRITE_BURST_MAX = Number(process.env.RATE_LIMIT_WRITE_BURST_MAX) || 3
2222let redisStore : RedisStore | undefined
2323let redisClient : IORedis | undefined
2424
25- try {
26- redisClient = new IORedis ( REDIS_URL , {
27- lazyConnect : true ,
28- connectTimeout : 3000 ,
29- maxRetriesPerRequest : 1 ,
30- enableReadyCheck : false ,
31- } )
32-
33- redisStore = new RedisStore ( {
34- sendCommand : ( ...args : Parameters < IORedis [ 'call' ] > ) => redisClient ! . call ( ...args ) as Promise < any > ,
35- } )
36-
37- logger . info ( '[RATE-LIMIT] Redis store initialized for distributed rate limiting' , {
38- redisUrl : REDIS_URL . replace ( / : \/ \/ [ ^ @ ] * @ / , '://***@' )
39- } )
40- } catch ( error ) {
41- logger . warn ( '[RATE-LIMIT] Redis unavailable - falling back to memory store (single instance only)' , {
42- error : error instanceof Error ? error . message : String ( error )
43- } )
25+ // Only try to connect to Redis if not in test environment
26+ if ( process . env . NODE_ENV !== 'test' ) {
27+ try {
28+ redisClient = new IORedis ( REDIS_URL , {
29+ lazyConnect : true ,
30+ connectTimeout : 3000 ,
31+ maxRetriesPerRequest : 1 ,
32+ enableReadyCheck : false ,
33+ } )
34+
35+ redisStore = new RedisStore ( {
36+ sendCommand : ( ...args : Parameters < IORedis [ 'call' ] > ) => redisClient ! . call ( ...args ) as Promise < any > ,
37+ } )
38+
39+ logger . info ( '[RATE-LIMIT] Redis store initialized for distributed rate limiting' , {
40+ redisUrl : REDIS_URL . replace ( / : \/ \/ [ ^ @ ] * @ / , '://***@' )
41+ } )
42+ } catch ( error ) {
43+ logger . warn ( '[RATE-LIMIT] Redis unavailable - falling back to memory store (single instance only)' , {
44+ error : error instanceof Error ? error . message : String ( error )
45+ } )
46+ }
47+ } else {
48+ logger . info ( '[RATE-LIMIT] Test environment detected - using memory store' )
4449}
4550
4651// Enhanced rate limit handler with detailed metrics
@@ -111,6 +116,11 @@ function skipSuccessfulRequests(req: import('express').Request, res: import('exp
111116 return true
112117 }
113118
119+ // In test environment, don't skip any requests to ensure rate limiting works
120+ if ( process . env . NODE_ENV === 'test' ) {
121+ return false
122+ }
123+
114124 // Skip successful responses (only count failed/suspicious requests)
115125 return res . statusCode < 400
116126}
@@ -125,69 +135,86 @@ const baseOptions: Partial<Options> = {
125135
126136// Global rate limiter - applies to all requests
127137export const globalRateLimiter = rateLimit ( {
128- ...baseOptions ,
129138 windowMs : GLOBAL_WINDOW_MS ,
130139 limit : GLOBAL_MAX ,
131140 keyGenerator : createKeyGenerator ( 'global' ) ,
132141 handler : createHandler ( GLOBAL_WINDOW_MS , 'global' ) ,
142+ standardHeaders : 'draft-7' ,
143+ legacyHeaders : false ,
144+ store : redisStore ,
145+ skip : skipSuccessfulRequests ,
133146 message : 'Too many requests from this IP, please try again later.'
134147} )
135148
136149// Burst protection - very short window to prevent rapid-fire attacks
137150export const burstProtectionLimiter = rateLimit ( {
138- ...baseOptions ,
139151 windowMs : BURST_WINDOW_MS ,
140152 limit : BURST_MAX ,
141153 keyGenerator : createKeyGenerator ( 'burst' ) ,
142154 handler : createHandler ( BURST_WINDOW_MS , 'burst-protection' ) ,
155+ standardHeaders : 'draft-7' ,
156+ legacyHeaders : false ,
157+ store : redisStore ,
143158 skip : ( req ) => req . path === '/health' || req . path === '/metrics' , // Only skip health checks
144159} )
145160
146161// Write operations rate limiter - stricter limits for mutating operations
147162export const writeRateLimiter = rateLimit ( {
148- ...baseOptions ,
149163 windowMs : GLOBAL_WINDOW_MS ,
150164 limit : WRITE_MAX ,
151165 keyGenerator : createKeyGenerator ( 'write' ) ,
152166 handler : createHandler ( GLOBAL_WINDOW_MS , 'write-operations' ) ,
167+ standardHeaders : 'draft-7' ,
168+ legacyHeaders : false ,
169+ store : redisStore ,
170+ skip : ( req ) => req . path === '/health' || req . path === '/metrics' , // Only skip health checks
153171} )
154172
155173// Write burst protection - prevent rapid write attempts
156174export const writeBurstLimiter = rateLimit ( {
157- ...baseOptions ,
158175 windowMs : BURST_WINDOW_MS ,
159176 limit : WRITE_BURST_MAX ,
160177 keyGenerator : createKeyGenerator ( 'write-burst' ) ,
161178 handler : createHandler ( BURST_WINDOW_MS , 'write-burst-protection' ) ,
179+ standardHeaders : 'draft-7' ,
180+ legacyHeaders : false ,
181+ store : redisStore ,
182+ skip : ( req ) => req . path === '/health' || req . path === '/metrics' ,
162183} )
163184
164185// Authentication rate limiter - protect login/refresh endpoints
165186export const authRateLimiter = rateLimit ( {
166- ...baseOptions ,
167187 windowMs : GLOBAL_WINDOW_MS ,
168188 limit : AUTH_MAX ,
169189 keyGenerator : createKeyGenerator ( 'auth' ) ,
170190 handler : createHandler ( GLOBAL_WINDOW_MS , 'authentication' ) ,
191+ standardHeaders : 'draft-7' ,
192+ legacyHeaders : false ,
193+ store : redisStore ,
171194 skip : ( ) => false , // Never skip auth rate limiting
172195} )
173196
174197// Critical operations rate limiter - for rebalancing and high-value operations
175198export const criticalRateLimiter = rateLimit ( {
176- ...baseOptions ,
177199 windowMs : GLOBAL_WINDOW_MS ,
178200 limit : CRITICAL_MAX ,
179201 keyGenerator : createKeyGenerator ( 'critical' ) ,
180202 handler : createHandler ( GLOBAL_WINDOW_MS , 'critical-operations' ) ,
203+ standardHeaders : 'draft-7' ,
204+ legacyHeaders : false ,
205+ store : redisStore ,
181206 skip : ( ) => false , // Never skip critical operation rate limiting
182207} )
183208
184209// Admin operations rate limiter - protect admin endpoints
185210export const adminRateLimiter = rateLimit ( {
186- ...baseOptions ,
187211 windowMs : GLOBAL_WINDOW_MS ,
188212 limit : AUTH_MAX , // Same as auth for admin operations
189213 keyGenerator : createKeyGenerator ( 'admin' ) ,
190214 handler : createHandler ( GLOBAL_WINDOW_MS , 'admin-operations' ) ,
215+ standardHeaders : 'draft-7' ,
216+ legacyHeaders : false ,
217+ store : redisStore ,
191218 skip : ( ) => false ,
192219} )
193220
0 commit comments