@noony-serverless/core 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/core/core.d.ts +16 -48
- package/build/core/core.js +2 -61
- package/build/core/handler.d.ts +37 -16
- package/build/core/handler.js +131 -42
- package/build/core/index.d.ts +0 -1
- package/build/core/index.js +0 -1
- package/build/middlewares/ConsolidatedValidationMiddleware.d.ts +126 -0
- package/build/middlewares/ConsolidatedValidationMiddleware.js +330 -0
- package/build/middlewares/ProcessingMiddleware.d.ts +138 -0
- package/build/middlewares/ProcessingMiddleware.js +425 -0
- package/build/middlewares/SecurityMiddleware.d.ts +157 -0
- package/build/middlewares/SecurityMiddleware.js +307 -0
- package/build/middlewares/authenticationMiddleware.d.ts +379 -0
- package/build/middlewares/authenticationMiddleware.js +216 -0
- package/build/middlewares/bodyParserMiddleware.d.ts +99 -0
- package/build/middlewares/bodyParserMiddleware.js +99 -0
- package/build/middlewares/bodyValidationMiddleware.d.ts +69 -3
- package/build/middlewares/bodyValidationMiddleware.js +68 -2
- package/build/middlewares/dependencyInjectionMiddleware.d.ts +238 -0
- package/build/middlewares/dependencyInjectionMiddleware.js +238 -0
- package/build/middlewares/errorHandlerMiddleware.d.ts +94 -0
- package/build/middlewares/errorHandlerMiddleware.js +105 -0
- package/build/middlewares/guards/RouteGuards.d.ts +476 -21
- package/build/middlewares/guards/RouteGuards.js +418 -21
- package/build/middlewares/guards/adapters/CustomTokenVerificationPortAdapter.d.ts +271 -0
- package/build/middlewares/guards/adapters/CustomTokenVerificationPortAdapter.js +301 -0
- package/build/middlewares/guards/cache/CacheAdapter.d.ts +369 -28
- package/build/middlewares/guards/cache/CacheAdapter.js +124 -5
- package/build/middlewares/guards/cache/MemoryCacheAdapter.d.ts +113 -4
- package/build/middlewares/guards/cache/MemoryCacheAdapter.js +113 -4
- package/build/middlewares/guards/config/GuardConfiguration.d.ts +568 -18
- package/build/middlewares/guards/config/GuardConfiguration.js +266 -10
- package/build/middlewares/guards/guards/FastAuthGuard.d.ts +5 -5
- package/build/middlewares/guards/guards/PermissionGuardFactory.d.ts +5 -13
- package/build/middlewares/guards/guards/PermissionGuardFactory.js +4 -4
- package/build/middlewares/guards/index.d.ts +43 -1
- package/build/middlewares/guards/index.js +46 -1
- package/build/middlewares/guards/resolvers/ExpressionPermissionResolver.d.ts +1 -1
- package/build/middlewares/guards/resolvers/ExpressionPermissionResolver.js +1 -1
- package/build/middlewares/guards/resolvers/PermissionResolver.d.ts +1 -1
- package/build/middlewares/guards/resolvers/PlainPermissionResolver.d.ts +1 -1
- package/build/middlewares/guards/resolvers/WildcardPermissionResolver.d.ts +1 -1
- package/build/middlewares/guards/services/FastUserContextService.d.ts +20 -33
- package/build/middlewares/guards/services/FastUserContextService.js +19 -5
- package/build/middlewares/headerVariablesMiddleware.d.ts +118 -0
- package/build/middlewares/headerVariablesMiddleware.js +118 -0
- package/build/middlewares/httpAttributesMiddleware.d.ts +235 -0
- package/build/middlewares/httpAttributesMiddleware.js +236 -1
- package/build/middlewares/index.d.ts +3 -1
- package/build/middlewares/index.js +6 -1
- package/build/middlewares/queryParametersMiddleware.d.ts +105 -0
- package/build/middlewares/queryParametersMiddleware.js +105 -0
- package/build/middlewares/rateLimitingMiddleware.d.ts +601 -9
- package/build/middlewares/rateLimitingMiddleware.js +623 -11
- package/build/middlewares/responseWrapperMiddleware.d.ts +170 -1
- package/build/middlewares/responseWrapperMiddleware.js +170 -1
- package/build/middlewares/securityAuditMiddleware.js +5 -5
- package/package.json +11 -9
- package/build/core/containerPool.d.ts +0 -44
- package/build/core/containerPool.js +0 -103
- package/build/middlewares/validationMiddleware.d.ts +0 -9
- package/build/middlewares/validationMiddleware.js +0 -40
|
@@ -104,8 +104,193 @@ const getRateLimit = (context, options) => {
|
|
|
104
104
|
};
|
|
105
105
|
};
|
|
106
106
|
/**
|
|
107
|
-
* Rate Limiting Middleware
|
|
108
|
-
* Implements
|
|
107
|
+
* Rate Limiting Middleware with sliding window implementation.
|
|
108
|
+
* Implements comprehensive rate limiting with dynamic limits, custom storage, and security features.
|
|
109
|
+
*
|
|
110
|
+
* ## When to Use RateLimitingMiddleware
|
|
111
|
+
*
|
|
112
|
+
* ### ✅ Recommended Use Cases:
|
|
113
|
+
* - **Business Logic Rate Limiting**: User-specific quotas, subscription-based limits
|
|
114
|
+
* - **Authentication & Security**: Login attempts, password resets, token refresh
|
|
115
|
+
* - **Content Creation**: Post creation, comment submission, profile updates
|
|
116
|
+
* - **Resource-Intensive Operations**: File uploads, data processing, complex queries
|
|
117
|
+
* - **Advanced Scenarios**: A/B testing limits, geographic restrictions, time-based rules
|
|
118
|
+
*
|
|
119
|
+
* ### ❌ Not Recommended Use Cases:
|
|
120
|
+
* - **Basic DDoS Protection**: Use WAF/CloudFlare instead
|
|
121
|
+
* - **Static Asset Protection**: Use CDN rate limiting
|
|
122
|
+
* - **Simple Volumetric Attacks**: Network-level solutions more effective
|
|
123
|
+
*
|
|
124
|
+
* ## Architecture Integration
|
|
125
|
+
*
|
|
126
|
+
* ### With WAF (Web Application Firewall):
|
|
127
|
+
* - WAF handles: IP blocking, DDoS protection, bot detection
|
|
128
|
+
* - Application handles: Business logic, user-aware limits, complex rules
|
|
129
|
+
* - Focus on complementary functionality, not duplication
|
|
130
|
+
*
|
|
131
|
+
* ### With API Gateway:
|
|
132
|
+
* - Gateway handles: Service-level limits, routing quotas, load balancing
|
|
133
|
+
* - Application handles: User context, subscription limits, feature-specific rules
|
|
134
|
+
* - Different layers for different concerns
|
|
135
|
+
*
|
|
136
|
+
* ### Direct Exposure (No WAF/Gateway):
|
|
137
|
+
* - Application must handle comprehensive protection
|
|
138
|
+
* - Implement multiple protection layers within middleware
|
|
139
|
+
* - Critical for security in simple deployments
|
|
140
|
+
*
|
|
141
|
+
* @implements {BaseMiddleware}
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* Basic API rate limiting:
|
|
145
|
+
* ```typescript
|
|
146
|
+
* import { Handler, RateLimitingMiddleware } from '@noony-serverless/core';
|
|
147
|
+
*
|
|
148
|
+
* const apiHandler = new Handler()
|
|
149
|
+
* .use(new RateLimitingMiddleware({
|
|
150
|
+
* maxRequests: 100,
|
|
151
|
+
* windowMs: 60000, // 1 minute
|
|
152
|
+
* message: 'Too many API requests'
|
|
153
|
+
* }))
|
|
154
|
+
* .handle(async (context) => {
|
|
155
|
+
* const data = await getApiData();
|
|
156
|
+
* return { success: true, data };
|
|
157
|
+
* });
|
|
158
|
+
* ```
|
|
159
|
+
*
|
|
160
|
+
* @example
|
|
161
|
+
* Authentication endpoint with strict limits:
|
|
162
|
+
* ```typescript
|
|
163
|
+
* const loginHandler = new Handler()
|
|
164
|
+
* .use(new RateLimitingMiddleware({
|
|
165
|
+
* maxRequests: 5,
|
|
166
|
+
* windowMs: 60000, // 1 minute
|
|
167
|
+
* message: 'Too many login attempts',
|
|
168
|
+
* statusCode: 429
|
|
169
|
+
* }))
|
|
170
|
+
* .handle(async (context) => {
|
|
171
|
+
* const { email, password } = context.req.parsedBody;
|
|
172
|
+
* const token = await authenticate(email, password);
|
|
173
|
+
* return { success: true, token };
|
|
174
|
+
* });
|
|
175
|
+
* ```
|
|
176
|
+
*
|
|
177
|
+
* @example
|
|
178
|
+
* Dynamic limits based on user authentication:
|
|
179
|
+
* ```typescript
|
|
180
|
+
* const smartApiHandler = new Handler()
|
|
181
|
+
* .use(new RateLimitingMiddleware({
|
|
182
|
+
* maxRequests: 50, // Default for unauthenticated
|
|
183
|
+
* windowMs: 60000,
|
|
184
|
+
* dynamicLimits: {
|
|
185
|
+
* authenticated: {
|
|
186
|
+
* maxRequests: 1000,
|
|
187
|
+
* windowMs: 60000,
|
|
188
|
+
* matcher: (context) => !!context.user
|
|
189
|
+
* },
|
|
190
|
+
* premium: {
|
|
191
|
+
* maxRequests: 5000,
|
|
192
|
+
* windowMs: 60000,
|
|
193
|
+
* matcher: (context) => context.user?.plan === 'premium'
|
|
194
|
+
* }
|
|
195
|
+
* }
|
|
196
|
+
* }))
|
|
197
|
+
* .handle(async (context) => {
|
|
198
|
+
* return { success: true, limit: 'applied dynamically' };
|
|
199
|
+
* });
|
|
200
|
+
* ```
|
|
201
|
+
*
|
|
202
|
+
* @example
|
|
203
|
+
* Multi-layer defense (with WAF):
|
|
204
|
+
* ```typescript
|
|
205
|
+
* // WAF handles basic IP limits (10,000/min), DDoS protection
|
|
206
|
+
* // Application refines with business logic
|
|
207
|
+
* const wafAwareHandler = new Handler()
|
|
208
|
+
* .use(new RateLimitingMiddleware({
|
|
209
|
+
* maxRequests: 100, // Refined limit after WAF filtering
|
|
210
|
+
* windowMs: 60000,
|
|
211
|
+
* dynamicLimits: {
|
|
212
|
+
* premium: {
|
|
213
|
+
* maxRequests: 500,
|
|
214
|
+
* windowMs: 60000,
|
|
215
|
+
* matcher: (context) => context.user?.plan === 'premium'
|
|
216
|
+
* }
|
|
217
|
+
* },
|
|
218
|
+
* keyGenerator: (context) => `user:${context.user?.id || context.req.ip}`
|
|
219
|
+
* }))
|
|
220
|
+
* .handle(async (context) => {
|
|
221
|
+
* // Business logic with user-aware limits
|
|
222
|
+
* return await processUserRequest(context);
|
|
223
|
+
* });
|
|
224
|
+
* ```
|
|
225
|
+
*
|
|
226
|
+
* @example
|
|
227
|
+
* API Gateway integration:
|
|
228
|
+
* ```typescript
|
|
229
|
+
* // Gateway: 10,000 req/hour per service, 1,000 req/min per endpoint
|
|
230
|
+
* // Application: User-specific limits within gateway envelope
|
|
231
|
+
* const gatewayAwareHandler = new Handler()
|
|
232
|
+
* .use(new RateLimitingMiddleware({
|
|
233
|
+
* maxRequests: 100, // Per user within gateway limits
|
|
234
|
+
* windowMs: 60000,
|
|
235
|
+
* dynamicLimits: {
|
|
236
|
+
* freeTrial: {
|
|
237
|
+
* maxRequests: 10,
|
|
238
|
+
* windowMs: 60000,
|
|
239
|
+
* matcher: (context) => context.user?.trialExpired === false
|
|
240
|
+
* },
|
|
241
|
+
* enterprise: {
|
|
242
|
+
* maxRequests: 5000,
|
|
243
|
+
* windowMs: 60000,
|
|
244
|
+
* matcher: (context) => context.user?.plan === 'enterprise'
|
|
245
|
+
* }
|
|
246
|
+
* },
|
|
247
|
+
* keyGenerator: (context) => `user:${context.user?.id}`
|
|
248
|
+
* }))
|
|
249
|
+
* .handle(async (context) => {
|
|
250
|
+
* // Refined business logic limits
|
|
251
|
+
* return await handleApiRequest(context);
|
|
252
|
+
* });
|
|
253
|
+
* ```
|
|
254
|
+
*
|
|
255
|
+
* @example
|
|
256
|
+
* Comprehensive protection (no WAF/Gateway):
|
|
257
|
+
* ```typescript
|
|
258
|
+
* // Application must handle all protection layers
|
|
259
|
+
* const comprehensiveHandler = new Handler()
|
|
260
|
+
* .use(new RateLimitingMiddleware({
|
|
261
|
+
* maxRequests: 100,
|
|
262
|
+
* windowMs: 60000,
|
|
263
|
+
* dynamicLimits: {
|
|
264
|
+
* // IP-based protection (WAF-like)
|
|
265
|
+
* suspicious_ip: {
|
|
266
|
+
* maxRequests: 10,
|
|
267
|
+
* windowMs: 60000,
|
|
268
|
+
* matcher: (context) => detectSuspiciousIP(context.req.ip)
|
|
269
|
+
* },
|
|
270
|
+
* // Endpoint-specific (Gateway-like)
|
|
271
|
+
* auth_endpoint: {
|
|
272
|
+
* maxRequests: 5,
|
|
273
|
+
* windowMs: 60000,
|
|
274
|
+
* matcher: (context) => context.req.path?.includes('/auth/')
|
|
275
|
+
* },
|
|
276
|
+
* // Business logic (Application-specific)
|
|
277
|
+
* user_specific: {
|
|
278
|
+
* maxRequests: 1000,
|
|
279
|
+
* windowMs: 60000,
|
|
280
|
+
* matcher: (context) => !!context.user
|
|
281
|
+
* }
|
|
282
|
+
* },
|
|
283
|
+
* keyGenerator: (context) => {
|
|
284
|
+
* const user = context.user?.id;
|
|
285
|
+
* const ip = context.req.ip;
|
|
286
|
+
* const endpoint = context.req.path;
|
|
287
|
+
* return user ? `user:${user}` : `ip:${ip}:${endpoint}`;
|
|
288
|
+
* }
|
|
289
|
+
* }))
|
|
290
|
+
* .handle(async (context) => {
|
|
291
|
+
* return await processRequest(context);
|
|
292
|
+
* });
|
|
293
|
+
* ```
|
|
109
294
|
*/
|
|
110
295
|
class RateLimitingMiddleware {
|
|
111
296
|
store;
|
|
@@ -179,18 +364,224 @@ class RateLimitingMiddleware {
|
|
|
179
364
|
}
|
|
180
365
|
exports.RateLimitingMiddleware = RateLimitingMiddleware;
|
|
181
366
|
/**
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
367
|
+
* Factory function that creates a rate limiting middleware.
|
|
368
|
+
* Provides flexible rate limiting with configurable options and presets.
|
|
369
|
+
*
|
|
370
|
+
* ## Architecture Decision Matrix
|
|
371
|
+
*
|
|
372
|
+
* | Infrastructure | WAF Rate Limiting | Gateway Rate Limiting | Application Rate Limiting |
|
|
373
|
+
* |----------------|------------------|---------------------|-------------------------|
|
|
374
|
+
* | **WAF + Gateway + App** | ✅ Basic DDoS protection | ✅ Service-level limits | ✅ Business logic |
|
|
375
|
+
* | **Gateway + App** | ❌ Not available | ✅ Service + IP limits | ✅ User context + business |
|
|
376
|
+
* | **WAF + App** | ✅ Network protection | ❌ Not available | ✅ All business logic |
|
|
377
|
+
* | **App Only** | ❌ Must implement | ❌ Must implement | ✅ Everything |
|
|
378
|
+
*
|
|
379
|
+
* ## Implementation Strategy by Architecture
|
|
380
|
+
*
|
|
381
|
+
* ### Multi-Layer Defense (Recommended for Enterprise)
|
|
382
|
+
* ```typescript
|
|
383
|
+
* // WAF Layer: 10,000 req/min per IP (CloudFlare/AWS WAF)
|
|
384
|
+
* // Gateway Layer: 1,000 req/min per API key (Kong/AWS API Gateway)
|
|
385
|
+
* // Application Layer: User-specific business rules (This middleware)
|
|
386
|
+
*
|
|
387
|
+
* const enterprise = rateLimiting({
|
|
388
|
+
* maxRequests: 100, // Refined after other layers
|
|
389
|
+
* dynamicLimits: {
|
|
390
|
+
* premium: { maxRequests: 500, matcher: (ctx) => ctx.user?.plan === 'premium' }
|
|
391
|
+
* }
|
|
392
|
+
* });
|
|
393
|
+
* ```
|
|
394
|
+
*
|
|
395
|
+
* ### Gateway + Application (Good for Most Applications)
|
|
396
|
+
* ```typescript
|
|
397
|
+
* // Gateway: Service capacity protection
|
|
398
|
+
* // Application: Business logic enforcement
|
|
399
|
+
*
|
|
400
|
+
* const standard = rateLimiting({
|
|
401
|
+
* maxRequests: 200, // Higher since Gateway pre-filters
|
|
402
|
+
* dynamicLimits: {
|
|
403
|
+
* authenticated: { maxRequests: 1000, matcher: (ctx) => !!ctx.user }
|
|
404
|
+
* }
|
|
405
|
+
* });
|
|
406
|
+
* ```
|
|
407
|
+
*
|
|
408
|
+
* ### Application Only (Comprehensive Protection Required)
|
|
409
|
+
* ```typescript
|
|
410
|
+
* // Must handle all layers of protection
|
|
411
|
+
*
|
|
412
|
+
* const comprehensive = rateLimiting({
|
|
413
|
+
* maxRequests: 50, // Conservative default
|
|
414
|
+
* dynamicLimits: {
|
|
415
|
+
* // WAF-like: IP protection
|
|
416
|
+
* suspicious: { maxRequests: 5, matcher: (ctx) => detectSuspicious(ctx.req.ip) },
|
|
417
|
+
* // Gateway-like: Endpoint protection
|
|
418
|
+
* auth: { maxRequests: 10, matcher: (ctx) => ctx.req.path?.includes('/auth/') },
|
|
419
|
+
* // Business: User-specific
|
|
420
|
+
* premium: { maxRequests: 1000, matcher: (ctx) => ctx.user?.plan === 'premium' }
|
|
421
|
+
* }
|
|
422
|
+
* });
|
|
423
|
+
* ```
|
|
424
|
+
*
|
|
425
|
+
* ## Cost-Benefit Analysis
|
|
426
|
+
*
|
|
427
|
+
* | Architecture | Setup Complexity | Runtime Cost | Protection Level | Maintenance |
|
|
428
|
+
* |-------------|-----------------|-------------|-----------------|-------------|
|
|
429
|
+
* | **WAF + Gateway + App** | High | High | Maximum | Medium |
|
|
430
|
+
* | **Gateway + App** | Medium | Medium | Good | Low |
|
|
431
|
+
* | **WAF + App** | Medium | Medium | Good | Medium |
|
|
432
|
+
* | **App Only** | Low | Low | Variable | High |
|
|
433
|
+
*
|
|
434
|
+
* @param options - Rate limiting configuration options
|
|
435
|
+
* @returns BaseMiddleware instance
|
|
436
|
+
*
|
|
437
|
+
* @example
|
|
438
|
+
* Using preset configurations:
|
|
439
|
+
* ```typescript
|
|
440
|
+
* import { Handler, rateLimiting, RateLimitPresets } from '@noony-serverless/core';
|
|
441
|
+
*
|
|
442
|
+
* // Strict limits for sensitive endpoints
|
|
443
|
+
* const authHandler = new Handler()
|
|
444
|
+
* .use(rateLimiting(RateLimitPresets.AUTH))
|
|
445
|
+
* .handle(async (context) => {
|
|
446
|
+
* return await handleAuthentication(context.req.parsedBody);
|
|
447
|
+
* });
|
|
448
|
+
*
|
|
449
|
+
* // Standard API limits
|
|
450
|
+
* const apiHandler = new Handler()
|
|
451
|
+
* .use(rateLimiting(RateLimitPresets.API))
|
|
452
|
+
* .handle(async (context) => {
|
|
453
|
+
* return await handleApiRequest(context);
|
|
454
|
+
* });
|
|
455
|
+
* ```
|
|
456
|
+
*
|
|
457
|
+
* @example
|
|
458
|
+
* Custom rate limiting with skip conditions:
|
|
459
|
+
* ```typescript
|
|
460
|
+
* const conditionalHandler = new Handler()
|
|
461
|
+
* .use(rateLimiting({
|
|
462
|
+
* maxRequests: 100,
|
|
463
|
+
* windowMs: 60000,
|
|
464
|
+
* skip: (context) => {
|
|
465
|
+
* // Skip rate limiting for admin users
|
|
466
|
+
* return context.user?.role === 'admin';
|
|
467
|
+
* },
|
|
468
|
+
* keyGenerator: (context) => {
|
|
469
|
+
* // Rate limit per user instead of IP
|
|
470
|
+
* return context.user?.id || context.req.ip || 'anonymous';
|
|
471
|
+
* }
|
|
472
|
+
* }))
|
|
473
|
+
* .handle(async (context) => {
|
|
474
|
+
* return { success: true, message: 'Request processed' };
|
|
475
|
+
* });
|
|
476
|
+
* ```
|
|
477
|
+
*
|
|
478
|
+
* @example
|
|
479
|
+
* Production Redis store integration:
|
|
480
|
+
* ```typescript
|
|
481
|
+
* import Redis from 'ioredis';
|
|
482
|
+
*
|
|
483
|
+
* class RedisRateLimitStore implements RateLimitStore {
|
|
484
|
+
* constructor(private redis: Redis) {}
|
|
485
|
+
*
|
|
486
|
+
* async increment(key: string, windowMs: number) {
|
|
487
|
+
* const multi = this.redis.multi();
|
|
488
|
+
* multi.incr(key);
|
|
489
|
+
* multi.expire(key, Math.ceil(windowMs / 1000));
|
|
490
|
+
* const results = await multi.exec();
|
|
491
|
+
* return { count: results![0][1] as number, resetTime: Date.now() + windowMs };
|
|
492
|
+
* }
|
|
493
|
+
* }
|
|
494
|
+
*
|
|
495
|
+
* const productionHandler = new Handler()
|
|
496
|
+
* .use(rateLimiting({
|
|
497
|
+
* store: new RedisRateLimitStore(redisClient),
|
|
498
|
+
* maxRequests: 1000,
|
|
499
|
+
* windowMs: 60000
|
|
500
|
+
* }))
|
|
501
|
+
* .handle(async (context) => {
|
|
502
|
+
* return await handleHighVolumeAPI(context);
|
|
503
|
+
* });
|
|
504
|
+
* ```
|
|
505
|
+
*
|
|
506
|
+
* @example
|
|
507
|
+
* Multi-dimensional rate limiting:
|
|
508
|
+
* ```typescript
|
|
509
|
+
* const advancedHandler = new Handler()
|
|
510
|
+
* .use(rateLimiting({
|
|
511
|
+
* maxRequests: 100,
|
|
512
|
+
* windowMs: 60000,
|
|
513
|
+
* dynamicLimits: {
|
|
514
|
+
* // Different limits by operation type
|
|
515
|
+
* read_operations: {
|
|
516
|
+
* maxRequests: 1000,
|
|
517
|
+
* windowMs: 60000,
|
|
518
|
+
* matcher: (context) => context.req.method === 'GET'
|
|
519
|
+
* },
|
|
520
|
+
* write_operations: {
|
|
521
|
+
* maxRequests: 50,
|
|
522
|
+
* windowMs: 60000,
|
|
523
|
+
* matcher: (context) => ['POST', 'PUT', 'DELETE'].includes(context.req.method || '')
|
|
524
|
+
* },
|
|
525
|
+
* // Different limits by user tier
|
|
526
|
+
* enterprise_users: {
|
|
527
|
+
* maxRequests: 5000,
|
|
528
|
+
* windowMs: 60000,
|
|
529
|
+
* matcher: (context) => context.user?.tier === 'enterprise'
|
|
530
|
+
* }
|
|
531
|
+
* },
|
|
532
|
+
* keyGenerator: (context) => {
|
|
533
|
+
* // Multi-dimensional key: user + operation type
|
|
534
|
+
* const userId = context.user?.id || context.req.ip;
|
|
535
|
+
* const operation = context.req.method === 'GET' ? 'read' : 'write';
|
|
536
|
+
* return `${operation}:${userId}`;
|
|
537
|
+
* }
|
|
538
|
+
* }))
|
|
539
|
+
* .handle(async (context) => {
|
|
540
|
+
* return await processAdvancedRequest(context);
|
|
541
|
+
* });
|
|
542
|
+
* ```
|
|
185
543
|
*/
|
|
186
544
|
const rateLimiting = (options = {}) => new RateLimitingMiddleware(options);
|
|
187
545
|
exports.rateLimiting = rateLimiting;
|
|
188
546
|
/**
|
|
189
|
-
* Predefined rate limit configurations
|
|
547
|
+
* Predefined rate limit configurations for common use cases.
|
|
548
|
+
*
|
|
549
|
+
* These presets are designed to work well in different infrastructure scenarios:
|
|
550
|
+
* - WAF + Application: Higher limits since WAF pre-filters traffic
|
|
551
|
+
* - Gateway + Application: Moderate limits complementing gateway quotas
|
|
552
|
+
* - Application Only: Conservative limits for comprehensive protection
|
|
553
|
+
*
|
|
554
|
+
* ## Preset Selection Guide
|
|
555
|
+
*
|
|
556
|
+
* | Preset | Use Case | Infrastructure | Requests/Min |
|
|
557
|
+
* |--------|----------|---------------|-------------|
|
|
558
|
+
* | `STRICT` | Sensitive operations | Any | 5 |
|
|
559
|
+
* | `AUTH` | Authentication endpoints | Any | 10 |
|
|
560
|
+
* | `PUBLIC` | Public/unauthenticated | App Only | 50 |
|
|
561
|
+
* | `API` | Standard API endpoints | WAF/Gateway + App | 100-1000 |
|
|
562
|
+
* | `DEVELOPMENT` | Development/testing | Development | 10,000 |
|
|
563
|
+
*
|
|
564
|
+
* @example
|
|
565
|
+
* Choosing the right preset:
|
|
566
|
+
* ```typescript
|
|
567
|
+
* // High-security endpoint (password reset)
|
|
568
|
+
* .use(rateLimiting(RateLimitPresets.STRICT))
|
|
569
|
+
*
|
|
570
|
+
* // Login/registration
|
|
571
|
+
* .use(rateLimiting(RateLimitPresets.AUTH))
|
|
572
|
+
*
|
|
573
|
+
* // Public API with WAF protection
|
|
574
|
+
* .use(rateLimiting(RateLimitPresets.API))
|
|
575
|
+
*
|
|
576
|
+
* // Public API without WAF (direct exposure)
|
|
577
|
+
* .use(rateLimiting(RateLimitPresets.PUBLIC))
|
|
578
|
+
* ```
|
|
190
579
|
*/
|
|
191
580
|
exports.RateLimitPresets = {
|
|
192
581
|
/**
|
|
193
582
|
* Very strict limits for sensitive endpoints
|
|
583
|
+
* Use for: Password resets, account changes, payment operations
|
|
584
|
+
* Infrastructure: Any (universal protection)
|
|
194
585
|
*/
|
|
195
586
|
STRICT: {
|
|
196
587
|
maxRequests: 5,
|
|
@@ -198,14 +589,16 @@ exports.RateLimitPresets = {
|
|
|
198
589
|
message: 'Too many requests to sensitive endpoint',
|
|
199
590
|
},
|
|
200
591
|
/**
|
|
201
|
-
* Standard API limits
|
|
592
|
+
* Standard API limits with dynamic scaling for authenticated users
|
|
593
|
+
* Use for: Main API endpoints, data retrieval, business operations
|
|
594
|
+
* Infrastructure: Best with WAF or Gateway (higher baseline limits)
|
|
202
595
|
*/
|
|
203
596
|
API: {
|
|
204
|
-
maxRequests: 100,
|
|
597
|
+
maxRequests: 100, // Baseline for unauthenticated/free users
|
|
205
598
|
windowMs: 60000, // 1 minute
|
|
206
599
|
dynamicLimits: {
|
|
207
600
|
authenticated: {
|
|
208
|
-
maxRequests: 1000,
|
|
601
|
+
maxRequests: 1000, // 10x increase for authenticated users
|
|
209
602
|
windowMs: 60000,
|
|
210
603
|
matcher: (context) => !!context.user,
|
|
211
604
|
},
|
|
@@ -213,25 +606,244 @@ exports.RateLimitPresets = {
|
|
|
213
606
|
},
|
|
214
607
|
/**
|
|
215
608
|
* Authentication endpoint limits
|
|
609
|
+
* Use for: Login, registration, token refresh, password operations
|
|
610
|
+
* Infrastructure: Any (essential security protection)
|
|
216
611
|
*/
|
|
217
612
|
AUTH: {
|
|
218
613
|
maxRequests: 10,
|
|
219
614
|
windowMs: 60000, // 1 minute
|
|
220
615
|
message: 'Too many authentication attempts',
|
|
616
|
+
keyGenerator: (context) => {
|
|
617
|
+
// Rate limit per IP + email combination for better security
|
|
618
|
+
const ip = context.req.ip || 'unknown';
|
|
619
|
+
const email = context.req.parsedBody?.email;
|
|
620
|
+
return email ? `auth:${email}:${ip}` : `auth:${ip}`;
|
|
621
|
+
},
|
|
221
622
|
},
|
|
222
623
|
/**
|
|
223
|
-
* Public endpoint limits
|
|
624
|
+
* Public endpoint limits for direct application exposure
|
|
625
|
+
* Use for: Public APIs, webhooks, health checks
|
|
626
|
+
* Infrastructure: Application only (no WAF/Gateway protection)
|
|
224
627
|
*/
|
|
225
628
|
PUBLIC: {
|
|
226
629
|
maxRequests: 50,
|
|
227
630
|
windowMs: 60000, // 1 minute
|
|
631
|
+
dynamicLimits: {
|
|
632
|
+
// Be more restrictive with suspicious traffic patterns
|
|
633
|
+
suspicious: {
|
|
634
|
+
maxRequests: 10,
|
|
635
|
+
windowMs: 60000,
|
|
636
|
+
matcher: (context) => {
|
|
637
|
+
const userAgent = context.req.headers?.['user-agent'] || '';
|
|
638
|
+
return (!userAgent || userAgent.includes('bot') || userAgent.length < 10);
|
|
639
|
+
},
|
|
640
|
+
},
|
|
641
|
+
},
|
|
228
642
|
},
|
|
229
643
|
/**
|
|
230
|
-
* Development mode - very permissive
|
|
644
|
+
* Development mode - very permissive limits
|
|
645
|
+
* Use for: Development, testing, debugging
|
|
646
|
+
* Infrastructure: Development environment only
|
|
231
647
|
*/
|
|
232
648
|
DEVELOPMENT: {
|
|
233
649
|
maxRequests: 10000,
|
|
234
650
|
windowMs: 60000, // 1 minute
|
|
651
|
+
skip: (context) => {
|
|
652
|
+
// Skip rate limiting for localhost and development IPs
|
|
653
|
+
const ip = context.req.ip || '';
|
|
654
|
+
return (ip.startsWith('127.') ||
|
|
655
|
+
ip.startsWith('::1') ||
|
|
656
|
+
ip.startsWith('192.168.'));
|
|
657
|
+
},
|
|
658
|
+
},
|
|
659
|
+
/**
|
|
660
|
+
* Enterprise-grade configuration with multi-tier support
|
|
661
|
+
* Use for: Production SaaS applications, enterprise APIs
|
|
662
|
+
* Infrastructure: WAF + Gateway + Application (full stack protection)
|
|
663
|
+
*/
|
|
664
|
+
ENTERPRISE: {
|
|
665
|
+
maxRequests: 200, // Higher baseline with multiple protection layers
|
|
666
|
+
windowMs: 60000,
|
|
667
|
+
dynamicLimits: {
|
|
668
|
+
free: {
|
|
669
|
+
maxRequests: 100,
|
|
670
|
+
windowMs: 60000,
|
|
671
|
+
matcher: (context) => !context.user || context.user?.plan === 'free',
|
|
672
|
+
},
|
|
673
|
+
premium: {
|
|
674
|
+
maxRequests: 1000,
|
|
675
|
+
windowMs: 60000,
|
|
676
|
+
matcher: (context) => context.user?.plan === 'premium',
|
|
677
|
+
},
|
|
678
|
+
enterprise: {
|
|
679
|
+
maxRequests: 5000,
|
|
680
|
+
windowMs: 60000,
|
|
681
|
+
matcher: (context) => context.user?.plan === 'enterprise',
|
|
682
|
+
},
|
|
683
|
+
admin: {
|
|
684
|
+
maxRequests: 10000,
|
|
685
|
+
windowMs: 60000,
|
|
686
|
+
matcher: (context) => context.user?.role === 'admin',
|
|
687
|
+
},
|
|
688
|
+
},
|
|
689
|
+
keyGenerator: (context) => {
|
|
690
|
+
// Use user ID for authenticated, IP for anonymous
|
|
691
|
+
return context.user?.id
|
|
692
|
+
? `user:${context.user.id}`
|
|
693
|
+
: `ip:${context.req.ip}`;
|
|
694
|
+
},
|
|
235
695
|
},
|
|
236
696
|
};
|
|
697
|
+
/**
|
|
698
|
+
* Configuration helpers and utilities for rate limiting setup
|
|
699
|
+
*
|
|
700
|
+
* ## Best Practices for Production
|
|
701
|
+
*
|
|
702
|
+
* ### 1. Store Selection
|
|
703
|
+
* - **Development**: Use default `MemoryStore` (built-in)
|
|
704
|
+
* - **Production Single Instance**: Use `MemoryStore` with cleanup
|
|
705
|
+
* - **Production Multi-Instance**: Use Redis-based store
|
|
706
|
+
* - **Serverless**: Use external store (Redis/DynamoDB) for state persistence
|
|
707
|
+
*
|
|
708
|
+
* ### 2. Key Generation Strategy
|
|
709
|
+
* ```typescript
|
|
710
|
+
* // Bad: Too generic, easy to abuse
|
|
711
|
+
* keyGenerator: () => 'global'
|
|
712
|
+
*
|
|
713
|
+
* // Good: Multi-dimensional keys
|
|
714
|
+
* keyGenerator: (context) => {
|
|
715
|
+
* const user = context.user?.id;
|
|
716
|
+
* const endpoint = context.req.path?.split('/')[2]; // /api/users -> users
|
|
717
|
+
* const method = context.req.method;
|
|
718
|
+
* return user ? `${user}:${endpoint}:${method}` : `${context.req.ip}:${endpoint}`;
|
|
719
|
+
* }
|
|
720
|
+
* ```
|
|
721
|
+
*
|
|
722
|
+
* ### 3. Dynamic Limits Best Practices
|
|
723
|
+
* ```typescript
|
|
724
|
+
* // Order matchers from most specific to least specific
|
|
725
|
+
* dynamicLimits: {
|
|
726
|
+
* admin: { maxRequests: 10000, matcher: (ctx) => ctx.user?.role === 'admin' },
|
|
727
|
+
* enterprise: { maxRequests: 5000, matcher: (ctx) => ctx.user?.plan === 'enterprise' },
|
|
728
|
+
* premium: { maxRequests: 1000, matcher: (ctx) => ctx.user?.plan === 'premium' },
|
|
729
|
+
* authenticated: { maxRequests: 500, matcher: (ctx) => !!ctx.user },
|
|
730
|
+
* // Default fallback handled by maxRequests
|
|
731
|
+
* }
|
|
732
|
+
* ```
|
|
733
|
+
*
|
|
734
|
+
* ### 4. Error Handling and Fallback
|
|
735
|
+
* ```typescript
|
|
736
|
+
* const resilientRateLimit = rateLimiting({
|
|
737
|
+
* maxRequests: 100,
|
|
738
|
+
* windowMs: 60000,
|
|
739
|
+
*
|
|
740
|
+
* // Custom store with fallback
|
|
741
|
+
* store: new ResilientStore({
|
|
742
|
+
* primary: redisStore,
|
|
743
|
+
* fallback: new MemoryStore(),
|
|
744
|
+
* timeout: 500 // ms
|
|
745
|
+
* }),
|
|
746
|
+
*
|
|
747
|
+
* // Graceful degradation on errors
|
|
748
|
+
* onError: (error, context) => {
|
|
749
|
+
* logger.warn('Rate limiting error, allowing request', { error, ip: context.req.ip });
|
|
750
|
+
* return false; // Don't block request on store errors
|
|
751
|
+
* }
|
|
752
|
+
* });
|
|
753
|
+
* ```
|
|
754
|
+
*
|
|
755
|
+
* ### 5. Monitoring and Alerting
|
|
756
|
+
* ```typescript
|
|
757
|
+
* // Monitor rate limit effectiveness
|
|
758
|
+
* const monitoredRateLimit = rateLimiting({
|
|
759
|
+
* maxRequests: 100,
|
|
760
|
+
* windowMs: 60000,
|
|
761
|
+
*
|
|
762
|
+
* onRateLimit: (context, info) => {
|
|
763
|
+
* // Alert on high rate limit hits
|
|
764
|
+
* metrics.increment('rate_limit.exceeded', {
|
|
765
|
+
* endpoint: context.req.path,
|
|
766
|
+
* user: context.user?.id || 'anonymous'
|
|
767
|
+
* });
|
|
768
|
+
*
|
|
769
|
+
* // Log suspicious patterns
|
|
770
|
+
* if (info.current > info.limit * 2) {
|
|
771
|
+
* logger.warn('Potential abuse detected', {
|
|
772
|
+
* ip: context.req.ip,
|
|
773
|
+
* userAgent: context.req.headers?.['user-agent'],
|
|
774
|
+
* attempts: info.current
|
|
775
|
+
* });
|
|
776
|
+
* }
|
|
777
|
+
* }
|
|
778
|
+
* });
|
|
779
|
+
* ```
|
|
780
|
+
*
|
|
781
|
+
* ### 6. Testing Rate Limits
|
|
782
|
+
* ```typescript
|
|
783
|
+
* // Test helper for rate limit validation
|
|
784
|
+
* export const testRateLimit = async (
|
|
785
|
+
* handler: Handler,
|
|
786
|
+
* requests: number,
|
|
787
|
+
* shouldSucceed: number
|
|
788
|
+
* ) => {
|
|
789
|
+
* const results = await Promise.all(
|
|
790
|
+
* Array(requests).fill(0).map(() => handler.execute(mockRequest, mockResponse))
|
|
791
|
+
* );
|
|
792
|
+
*
|
|
793
|
+
* const successful = results.filter(r => r.statusCode !== 429).length;
|
|
794
|
+
* expect(successful).toBe(shouldSucceed);
|
|
795
|
+
* };
|
|
796
|
+
* ```
|
|
797
|
+
*
|
|
798
|
+
* ## Troubleshooting Common Issues
|
|
799
|
+
*
|
|
800
|
+
* ### Issue: Rate limits not working
|
|
801
|
+
* **Solution**: Check key generation and store connection
|
|
802
|
+
* ```typescript
|
|
803
|
+
* // Debug key generation
|
|
804
|
+
* keyGenerator: (context) => {
|
|
805
|
+
* const key = generateKey(context);
|
|
806
|
+
* console.log('Rate limit key:', key); // Remove in production
|
|
807
|
+
* return key;
|
|
808
|
+
* }
|
|
809
|
+
* ```
|
|
810
|
+
*
|
|
811
|
+
* ### Issue: Too many false positives
|
|
812
|
+
* **Solution**: Refine dynamic limits and key generation
|
|
813
|
+
* ```typescript
|
|
814
|
+
* // More granular limits
|
|
815
|
+
* dynamicLimits: {
|
|
816
|
+
* read: { maxRequests: 1000, matcher: (ctx) => ctx.req.method === 'GET' },
|
|
817
|
+
* write: { maxRequests: 100, matcher: (ctx) => ctx.req.method !== 'GET' }
|
|
818
|
+
* }
|
|
819
|
+
* ```
|
|
820
|
+
*
|
|
821
|
+
* ### Issue: Memory leaks in MemoryStore
|
|
822
|
+
* **Solution**: Ensure proper cleanup interval and limits
|
|
823
|
+
* ```typescript
|
|
824
|
+
* // Monitor store size
|
|
825
|
+
* setInterval(() => {
|
|
826
|
+
* const storeSize = memoryStore.size();
|
|
827
|
+
* if (storeSize > 10000) {
|
|
828
|
+
* logger.warn('Rate limit store size growing', { size: storeSize });
|
|
829
|
+
* }
|
|
830
|
+
* }, 60000);
|
|
831
|
+
* ```
|
|
832
|
+
*
|
|
833
|
+
* ### Issue: Rate limits too restrictive
|
|
834
|
+
* **Solution**: Implement gradual enforcement
|
|
835
|
+
* ```typescript
|
|
836
|
+
* const gradualLimit = rateLimiting({
|
|
837
|
+
* maxRequests: 100,
|
|
838
|
+
* windowMs: 60000,
|
|
839
|
+
*
|
|
840
|
+
* // Warn before blocking
|
|
841
|
+
* onApproachingLimit: (context, info) => {
|
|
842
|
+
* if (info.remaining < 10) {
|
|
843
|
+
* context.res.header('X-Rate-Limit-Warning', 'Approaching limit');
|
|
844
|
+
* }
|
|
845
|
+
* }
|
|
846
|
+
* });
|
|
847
|
+
* ```
|
|
848
|
+
*/
|
|
237
849
|
//# sourceMappingURL=rateLimitingMiddleware.js.map
|