@noony-serverless/core 0.1.5 → 0.2.1
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/README.md +7 -7
- package/build/core/core.d.ts +18 -50
- package/build/core/core.js +5 -67
- 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/bodyValidationMiddleware.d.ts +12 -10
- package/build/middlewares/bodyValidationMiddleware.js +10 -8
- package/build/middlewares/dependencyInjectionMiddleware.js +1 -1
- package/build/middlewares/guards/RouteGuards.d.ts +239 -4
- package/build/middlewares/guards/RouteGuards.js +301 -8
- package/build/middlewares/guards/adapters/CustomTokenVerificationPortAdapter.d.ts +271 -0
- package/build/middlewares/guards/adapters/CustomTokenVerificationPortAdapter.js +301 -0
- package/build/middlewares/guards/config/GuardConfiguration.d.ts +50 -0
- package/build/middlewares/guards/config/GuardConfiguration.js +59 -0
- 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 +17 -4
- package/build/middlewares/httpAttributesMiddleware.js +1 -1
- package/build/middlewares/index.d.ts +3 -1
- package/build/middlewares/index.js +6 -1
- package/build/middlewares/rateLimitingMiddleware.d.ts +492 -4
- package/build/middlewares/rateLimitingMiddleware.js +514 -6
- 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 -154
- package/build/middlewares/validationMiddleware.js +0 -185
|
@@ -107,6 +107,37 @@ const getRateLimit = (context, options) => {
|
|
|
107
107
|
* Rate Limiting Middleware with sliding window implementation.
|
|
108
108
|
* Implements comprehensive rate limiting with dynamic limits, custom storage, and security features.
|
|
109
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
|
+
*
|
|
110
141
|
* @implements {BaseMiddleware}
|
|
111
142
|
*
|
|
112
143
|
* @example
|
|
@@ -167,6 +198,99 @@ const getRateLimit = (context, options) => {
|
|
|
167
198
|
* return { success: true, limit: 'applied dynamically' };
|
|
168
199
|
* });
|
|
169
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
|
+
* ```
|
|
170
294
|
*/
|
|
171
295
|
class RateLimitingMiddleware {
|
|
172
296
|
store;
|
|
@@ -243,6 +367,70 @@ exports.RateLimitingMiddleware = RateLimitingMiddleware;
|
|
|
243
367
|
* Factory function that creates a rate limiting middleware.
|
|
244
368
|
* Provides flexible rate limiting with configurable options and presets.
|
|
245
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
|
+
*
|
|
246
434
|
* @param options - Rate limiting configuration options
|
|
247
435
|
* @returns BaseMiddleware instance
|
|
248
436
|
*
|
|
@@ -286,15 +474,114 @@ exports.RateLimitingMiddleware = RateLimitingMiddleware;
|
|
|
286
474
|
* return { success: true, message: 'Request processed' };
|
|
287
475
|
* });
|
|
288
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
|
+
* ```
|
|
289
543
|
*/
|
|
290
544
|
const rateLimiting = (options = {}) => new RateLimitingMiddleware(options);
|
|
291
545
|
exports.rateLimiting = rateLimiting;
|
|
292
546
|
/**
|
|
293
|
-
* 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
|
+
* ```
|
|
294
579
|
*/
|
|
295
580
|
exports.RateLimitPresets = {
|
|
296
581
|
/**
|
|
297
582
|
* Very strict limits for sensitive endpoints
|
|
583
|
+
* Use for: Password resets, account changes, payment operations
|
|
584
|
+
* Infrastructure: Any (universal protection)
|
|
298
585
|
*/
|
|
299
586
|
STRICT: {
|
|
300
587
|
maxRequests: 5,
|
|
@@ -302,14 +589,16 @@ exports.RateLimitPresets = {
|
|
|
302
589
|
message: 'Too many requests to sensitive endpoint',
|
|
303
590
|
},
|
|
304
591
|
/**
|
|
305
|
-
* 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)
|
|
306
595
|
*/
|
|
307
596
|
API: {
|
|
308
|
-
maxRequests: 100,
|
|
597
|
+
maxRequests: 100, // Baseline for unauthenticated/free users
|
|
309
598
|
windowMs: 60000, // 1 minute
|
|
310
599
|
dynamicLimits: {
|
|
311
600
|
authenticated: {
|
|
312
|
-
maxRequests: 1000,
|
|
601
|
+
maxRequests: 1000, // 10x increase for authenticated users
|
|
313
602
|
windowMs: 60000,
|
|
314
603
|
matcher: (context) => !!context.user,
|
|
315
604
|
},
|
|
@@ -317,25 +606,244 @@ exports.RateLimitPresets = {
|
|
|
317
606
|
},
|
|
318
607
|
/**
|
|
319
608
|
* Authentication endpoint limits
|
|
609
|
+
* Use for: Login, registration, token refresh, password operations
|
|
610
|
+
* Infrastructure: Any (essential security protection)
|
|
320
611
|
*/
|
|
321
612
|
AUTH: {
|
|
322
613
|
maxRequests: 10,
|
|
323
614
|
windowMs: 60000, // 1 minute
|
|
324
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
|
+
},
|
|
325
622
|
},
|
|
326
623
|
/**
|
|
327
|
-
* 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)
|
|
328
627
|
*/
|
|
329
628
|
PUBLIC: {
|
|
330
629
|
maxRequests: 50,
|
|
331
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
|
+
},
|
|
332
642
|
},
|
|
333
643
|
/**
|
|
334
|
-
* Development mode - very permissive
|
|
644
|
+
* Development mode - very permissive limits
|
|
645
|
+
* Use for: Development, testing, debugging
|
|
646
|
+
* Infrastructure: Development environment only
|
|
335
647
|
*/
|
|
336
648
|
DEVELOPMENT: {
|
|
337
649
|
maxRequests: 10000,
|
|
338
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
|
+
},
|
|
339
695
|
},
|
|
340
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
|
+
*/
|
|
341
849
|
//# sourceMappingURL=rateLimitingMiddleware.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noony-serverless/core",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "A Middy base framework compatible with Firebase and GCP Cloud Functions with TypeScript",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -40,17 +40,19 @@
|
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"@google-cloud/firestore": "^7.1.0",
|
|
43
|
-
"@google-cloud/functions-framework": "^
|
|
43
|
+
"@google-cloud/functions-framework": "^4.0.0",
|
|
44
44
|
"@google-cloud/pubsub": "^4.1.0",
|
|
45
|
-
"@types/jsonwebtoken": "^9.0.
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"firebase-
|
|
45
|
+
"@types/jsonwebtoken": "^9.0.10",
|
|
46
|
+
"@types/qs": "^6.14.0",
|
|
47
|
+
"axios": "^1.11.0",
|
|
48
|
+
"fastify": "^5.6.0",
|
|
49
|
+
"firebase-admin": "^13.5.0",
|
|
50
|
+
"firebase-functions": "^6.4.0",
|
|
50
51
|
"jsonwebtoken": "^9.0.2",
|
|
52
|
+
"qs": "^6.14.0",
|
|
51
53
|
"reflect-metadata": "^0.2.2",
|
|
52
54
|
"typedi": "^0.10.0",
|
|
53
|
-
"zod": "^
|
|
55
|
+
"zod": "^4.1.5"
|
|
54
56
|
},
|
|
55
57
|
"devDependencies": {
|
|
56
58
|
"@types/jest": "^29.5.11",
|
|
@@ -58,7 +60,7 @@
|
|
|
58
60
|
"@types/node": "^20.10.5",
|
|
59
61
|
"@typescript-eslint/eslint-plugin": "^6.15.0",
|
|
60
62
|
"@typescript-eslint/parser": "^6.15.0",
|
|
61
|
-
"concurrently": "^
|
|
63
|
+
"concurrently": "^9.2.1",
|
|
62
64
|
"eslint": "^8.56.0",
|
|
63
65
|
"eslint-config-prettier": "^9.1.0",
|
|
64
66
|
"eslint-plugin-prettier": "^5.1.2",
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import Container from 'typedi';
|
|
2
|
-
/**
|
|
3
|
-
* Performance optimization: Container Pool for reusing TypeDI containers
|
|
4
|
-
* This reduces object creation overhead and improves memory efficiency
|
|
5
|
-
*/
|
|
6
|
-
declare class ContainerPool {
|
|
7
|
-
private availableContainers;
|
|
8
|
-
private maxPoolSize;
|
|
9
|
-
private createdContainers;
|
|
10
|
-
constructor(maxPoolSize?: number);
|
|
11
|
-
/**
|
|
12
|
-
* Get a container from the pool or create a new one
|
|
13
|
-
*/
|
|
14
|
-
acquire(): Container;
|
|
15
|
-
/**
|
|
16
|
-
* Return a container to the pool for reuse
|
|
17
|
-
*/
|
|
18
|
-
release(container: Container): void;
|
|
19
|
-
/**
|
|
20
|
-
* Reset container state to prevent cross-request contamination
|
|
21
|
-
* Note: TypeDI containers are isolated by default, so we mainly need
|
|
22
|
-
* to clear any manually set values
|
|
23
|
-
*/
|
|
24
|
-
private resetContainer;
|
|
25
|
-
/**
|
|
26
|
-
* Get pool statistics for monitoring
|
|
27
|
-
*/
|
|
28
|
-
getStats(): {
|
|
29
|
-
available: number;
|
|
30
|
-
created: number;
|
|
31
|
-
maxSize: number;
|
|
32
|
-
};
|
|
33
|
-
/**
|
|
34
|
-
* Warm up the pool by pre-creating containers
|
|
35
|
-
*/
|
|
36
|
-
warmUp(count?: number): void;
|
|
37
|
-
/**
|
|
38
|
-
* Clear all containers from the pool
|
|
39
|
-
*/
|
|
40
|
-
clear(): void;
|
|
41
|
-
}
|
|
42
|
-
declare const containerPool: ContainerPool;
|
|
43
|
-
export { ContainerPool, containerPool };
|
|
44
|
-
//# sourceMappingURL=containerPool.d.ts.map
|