@noony-serverless/core
Version:
A Middy base framework compatible with Firebase and GCP Cloud Functions with TypeScript
851 lines • 28.8 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.MemoryStore = exports.RateLimitPresets = exports.rateLimiting = exports.RateLimitingMiddleware = void 0;
const core_1 = require("../core");
const logger_1 = require("../core/logger");
/**
* In-memory rate limit store (use Redis in production)
*/
class MemoryStore {
store = new Map();
cleanupInterval;
constructor() {
// Cleanup expired entries every 5 minutes
this.cleanupInterval = setInterval(() => {
this.cleanup();
}, 5 * 60 * 1000);
}
async increment(key, windowMs) {
const now = Date.now();
const resetTime = now + windowMs;
const existing = this.store.get(key);
if (existing && now < existing.resetTime) {
existing.count++;
return existing;
}
else {
const newEntry = { count: 1, resetTime };
this.store.set(key, newEntry);
return newEntry;
}
}
async get(key) {
const entry = this.store.get(key);
if (entry && Date.now() < entry.resetTime) {
return entry;
}
if (entry) {
this.store.delete(key);
}
return null;
}
async reset(key) {
this.store.delete(key);
}
cleanup() {
const now = Date.now();
for (const [key, entry] of this.store.entries()) {
if (now >= entry.resetTime) {
this.store.delete(key);
}
}
}
destroy() {
clearInterval(this.cleanupInterval);
this.store.clear();
}
}
exports.MemoryStore = MemoryStore;
/**
* Default key generator using IP address with user identification
*/
const defaultKeyGenerator = (context) => {
const ip = context.req.ip ||
(Array.isArray(context.req.headers?.['x-forwarded-for'])
? context.req.headers['x-forwarded-for'][0]
: context.req.headers?.['x-forwarded-for']) ||
'unknown';
// Include user ID if authenticated for per-user limits
const userId = context.user && typeof context.user === 'object' && 'sub' in context.user
? context.user.sub
: null;
return userId ? `user:${userId}` : `ip:${ip}`;
};
/**
* Apply rate limit headers to response
*/
const setRateLimitHeaders = (context, info) => {
context.res.header('X-RateLimit-Limit', String(info.limit));
context.res.header('X-RateLimit-Remaining', String(Math.max(0, info.remaining)));
context.res.header('X-RateLimit-Reset', String(Math.ceil(info.resetTime / 1000)));
context.res.header('Retry-After', String(Math.ceil((info.resetTime - Date.now()) / 1000)));
};
/**
* Determine the appropriate rate limit for the request
*/
const getRateLimit = (context, options) => {
// Check dynamic limits first
if (options.dynamicLimits) {
for (const [name, config] of Object.entries(options.dynamicLimits)) {
if (config.matcher(context)) {
logger_1.logger.debug('Applied dynamic rate limit', {
limitName: name,
maxRequests: config.maxRequests,
windowMs: config.windowMs,
});
return { maxRequests: config.maxRequests, windowMs: config.windowMs };
}
}
}
// Default limits
return {
maxRequests: options.maxRequests || 100,
windowMs: options.windowMs || 60000,
};
};
/**
* Rate Limiting Middleware with sliding window implementation.
* Implements comprehensive rate limiting with dynamic limits, custom storage, and security features.
*
* ## When to Use RateLimitingMiddleware
*
* ### ✅ Recommended Use Cases:
* - **Business Logic Rate Limiting**: User-specific quotas, subscription-based limits
* - **Authentication & Security**: Login attempts, password resets, token refresh
* - **Content Creation**: Post creation, comment submission, profile updates
* - **Resource-Intensive Operations**: File uploads, data processing, complex queries
* - **Advanced Scenarios**: A/B testing limits, geographic restrictions, time-based rules
*
* ### ❌ Not Recommended Use Cases:
* - **Basic DDoS Protection**: Use WAF/CloudFlare instead
* - **Static Asset Protection**: Use CDN rate limiting
* - **Simple Volumetric Attacks**: Network-level solutions more effective
*
* ## Architecture Integration
*
* ### With WAF (Web Application Firewall):
* - WAF handles: IP blocking, DDoS protection, bot detection
* - Application handles: Business logic, user-aware limits, complex rules
* - Focus on complementary functionality, not duplication
*
* ### With API Gateway:
* - Gateway handles: Service-level limits, routing quotas, load balancing
* - Application handles: User context, subscription limits, feature-specific rules
* - Different layers for different concerns
*
* ### Direct Exposure (No WAF/Gateway):
* - Application must handle comprehensive protection
* - Implement multiple protection layers within middleware
* - Critical for security in simple deployments
*
* @template TBody - The type of the request body payload (preserves type chain)
* @template TUser - The type of the authenticated user (preserves type chain)
* @implements {BaseMiddleware<TBody, TUser>}
*
* @example
* Basic API rate limiting:
* ```typescript
* import { Handler, RateLimitingMiddleware } from '@noony-serverless/core';
*
* const apiHandler = new Handler()
* .use(new RateLimitingMiddleware({
* maxRequests: 100,
* windowMs: 60000, // 1 minute
* message: 'Too many API requests'
* }))
* .handle(async (context) => {
* const data = await getApiData();
* return { success: true, data };
* });
* ```
*
* @example
* Authentication endpoint with strict limits:
* ```typescript
* const loginHandler = new Handler()
* .use(new RateLimitingMiddleware({
* maxRequests: 5,
* windowMs: 60000, // 1 minute
* message: 'Too many login attempts',
* statusCode: 429
* }))
* .handle(async (context) => {
* const { email, password } = context.req.parsedBody;
* const token = await authenticate(email, password);
* return { success: true, token };
* });
* ```
*
* @example
* Dynamic limits based on user authentication:
* ```typescript
* const smartApiHandler = new Handler()
* .use(new RateLimitingMiddleware({
* maxRequests: 50, // Default for unauthenticated
* windowMs: 60000,
* dynamicLimits: {
* authenticated: {
* maxRequests: 1000,
* windowMs: 60000,
* matcher: (context) => !!context.user
* },
* premium: {
* maxRequests: 5000,
* windowMs: 60000,
* matcher: (context) => context.user?.plan === 'premium'
* }
* }
* }))
* .handle(async (context) => {
* return { success: true, limit: 'applied dynamically' };
* });
* ```
*
* @example
* Multi-layer defense (with WAF):
* ```typescript
* // WAF handles basic IP limits (10,000/min), DDoS protection
* // Application refines with business logic
* const wafAwareHandler = new Handler()
* .use(new RateLimitingMiddleware({
* maxRequests: 100, // Refined limit after WAF filtering
* windowMs: 60000,
* dynamicLimits: {
* premium: {
* maxRequests: 500,
* windowMs: 60000,
* matcher: (context) => context.user?.plan === 'premium'
* }
* },
* keyGenerator: (context) => `user:${context.user?.id || context.req.ip}`
* }))
* .handle(async (context) => {
* // Business logic with user-aware limits
* return await processUserRequest(context);
* });
* ```
*
* @example
* API Gateway integration:
* ```typescript
* // Gateway: 10,000 req/hour per service, 1,000 req/min per endpoint
* // Application: User-specific limits within gateway envelope
* const gatewayAwareHandler = new Handler()
* .use(new RateLimitingMiddleware({
* maxRequests: 100, // Per user within gateway limits
* windowMs: 60000,
* dynamicLimits: {
* freeTrial: {
* maxRequests: 10,
* windowMs: 60000,
* matcher: (context) => context.user?.trialExpired === false
* },
* enterprise: {
* maxRequests: 5000,
* windowMs: 60000,
* matcher: (context) => context.user?.plan === 'enterprise'
* }
* },
* keyGenerator: (context) => `user:${context.user?.id}`
* }))
* .handle(async (context) => {
* // Refined business logic limits
* return await handleApiRequest(context);
* });
* ```
*
* @example
* Comprehensive protection (no WAF/Gateway):
* ```typescript
* // Application must handle all protection layers
* const comprehensiveHandler = new Handler()
* .use(new RateLimitingMiddleware({
* maxRequests: 100,
* windowMs: 60000,
* dynamicLimits: {
* // IP-based protection (WAF-like)
* suspicious_ip: {
* maxRequests: 10,
* windowMs: 60000,
* matcher: (context) => detectSuspiciousIP(context.req.ip)
* },
* // Endpoint-specific (Gateway-like)
* auth_endpoint: {
* maxRequests: 5,
* windowMs: 60000,
* matcher: (context) => context.req.path?.includes('/auth/')
* },
* // Business logic (Application-specific)
* user_specific: {
* maxRequests: 1000,
* windowMs: 60000,
* matcher: (context) => !!context.user
* }
* },
* keyGenerator: (context) => {
* const user = context.user?.id;
* const ip = context.req.ip;
* const endpoint = context.req.path;
* return user ? `user:${user}` : `ip:${ip}:${endpoint}`;
* }
* }))
* .handle(async (context) => {
* return await processRequest(context);
* });
* ```
*/
class RateLimitingMiddleware {
store;
options;
constructor(options = {}) {
this.store = options.store || new MemoryStore();
this.options = {
maxRequests: 100,
windowMs: 60000,
message: 'Too many requests, please try again later',
statusCode: 429,
headers: true,
keyGenerator: options.keyGenerator || defaultKeyGenerator,
skip: options.skip,
dynamicLimits: options.dynamicLimits,
store: options.store,
};
}
async before(context) {
// Skip rate limiting if configured
if (this.options.skip && this.options.skip(context)) {
return;
}
const key = this.options.keyGenerator(context);
const { maxRequests, windowMs } = getRateLimit(context, this.options);
try {
const result = await this.store.increment(key, windowMs);
const rateLimitInfo = {
limit: maxRequests,
current: result.count,
remaining: Math.max(0, maxRequests - result.count),
resetTime: result.resetTime,
};
// Set rate limit headers
if (this.options.headers) {
setRateLimitHeaders(context, rateLimitInfo);
}
// Check if limit exceeded
if (result.count > maxRequests) {
logger_1.logger.warn('Rate limit exceeded', {
key,
count: result.count,
limit: maxRequests,
resetTime: new Date(result.resetTime).toISOString(),
userAgent: context.req.headers?.['user-agent'],
endpoint: context.req.path || context.req.url,
});
throw new core_1.SecurityError(this.options.message);
}
// Log approaching limit
if (result.count > maxRequests * 0.8) {
logger_1.logger.debug('Approaching rate limit', {
key,
count: result.count,
limit: maxRequests,
percentage: Math.round((result.count / maxRequests) * 100),
});
}
}
catch (error) {
if (error instanceof core_1.SecurityError) {
throw error;
}
// Log store errors but don't block requests
logger_1.logger.error('Rate limiting store error', {
error: error instanceof Error ? error.message : 'Unknown error',
key,
});
}
}
}
exports.RateLimitingMiddleware = RateLimitingMiddleware;
/**
* Factory function that creates a rate limiting middleware.
* Provides flexible rate limiting with configurable options and presets.
*
* ## Architecture Decision Matrix
*
* | Infrastructure | WAF Rate Limiting | Gateway Rate Limiting | Application Rate Limiting |
* |----------------|------------------|---------------------|-------------------------|
* | **WAF + Gateway + App** | ✅ Basic DDoS protection | ✅ Service-level limits | ✅ Business logic |
* | **Gateway + App** | ❌ Not available | ✅ Service + IP limits | ✅ User context + business |
* | **WAF + App** | ✅ Network protection | ❌ Not available | ✅ All business logic |
* | **App Only** | ❌ Must implement | ❌ Must implement | ✅ Everything |
*
* ## Implementation Strategy by Architecture
*
* ### Multi-Layer Defense (Recommended for Enterprise)
* ```typescript
* // WAF Layer: 10,000 req/min per IP (CloudFlare/AWS WAF)
* // Gateway Layer: 1,000 req/min per API key (Kong/AWS API Gateway)
* // Application Layer: User-specific business rules (This middleware)
*
* const enterprise = rateLimiting({
* maxRequests: 100, // Refined after other layers
* dynamicLimits: {
* premium: { maxRequests: 500, matcher: (ctx) => ctx.user?.plan === 'premium' }
* }
* });
* ```
*
* ### Gateway + Application (Good for Most Applications)
* ```typescript
* // Gateway: Service capacity protection
* // Application: Business logic enforcement
*
* const standard = rateLimiting({
* maxRequests: 200, // Higher since Gateway pre-filters
* dynamicLimits: {
* authenticated: { maxRequests: 1000, matcher: (ctx) => !!ctx.user }
* }
* });
* ```
*
* ### Application Only (Comprehensive Protection Required)
* ```typescript
* // Must handle all layers of protection
*
* const comprehensive = rateLimiting({
* maxRequests: 50, // Conservative default
* dynamicLimits: {
* // WAF-like: IP protection
* suspicious: { maxRequests: 5, matcher: (ctx) => detectSuspicious(ctx.req.ip) },
* // Gateway-like: Endpoint protection
* auth: { maxRequests: 10, matcher: (ctx) => ctx.req.path?.includes('/auth/') },
* // Business: User-specific
* premium: { maxRequests: 1000, matcher: (ctx) => ctx.user?.plan === 'premium' }
* }
* });
* ```
*
* ## Cost-Benefit Analysis
*
* | Architecture | Setup Complexity | Runtime Cost | Protection Level | Maintenance |
* |-------------|-----------------|-------------|-----------------|-------------|
* | **WAF + Gateway + App** | High | High | Maximum | Medium |
* | **Gateway + App** | Medium | Medium | Good | Low |
* | **WAF + App** | Medium | Medium | Good | Medium |
* | **App Only** | Low | Low | Variable | High |
*
* @param options - Rate limiting configuration options
* @returns BaseMiddleware instance
*
* @example
* Using preset configurations:
* ```typescript
* import { Handler, rateLimiting, RateLimitPresets } from '@noony-serverless/core';
*
* // Strict limits for sensitive endpoints
* const authHandler = new Handler()
* .use(rateLimiting(RateLimitPresets.AUTH))
* .handle(async (context) => {
* return await handleAuthentication(context.req.parsedBody);
* });
*
* // Standard API limits
* const apiHandler = new Handler()
* .use(rateLimiting(RateLimitPresets.API))
* .handle(async (context) => {
* return await handleApiRequest(context);
* });
* ```
*
* @example
* Custom rate limiting with skip conditions:
* ```typescript
* const conditionalHandler = new Handler()
* .use(rateLimiting({
* maxRequests: 100,
* windowMs: 60000,
* skip: (context) => {
* // Skip rate limiting for admin users
* return context.user?.role === 'admin';
* },
* keyGenerator: (context) => {
* // Rate limit per user instead of IP
* return context.user?.id || context.req.ip || 'anonymous';
* }
* }))
* .handle(async (context) => {
* return { success: true, message: 'Request processed' };
* });
* ```
*
* @example
* Production Redis store integration:
* ```typescript
* import Redis from 'ioredis';
*
* class RedisRateLimitStore implements RateLimitStore {
* constructor(private redis: Redis) {}
*
* async increment(key: string, windowMs: number) {
* const multi = this.redis.multi();
* multi.incr(key);
* multi.expire(key, Math.ceil(windowMs / 1000));
* const results = await multi.exec();
* return { count: results![0][1] as number, resetTime: Date.now() + windowMs };
* }
* }
*
* const productionHandler = new Handler()
* .use(rateLimiting({
* store: new RedisRateLimitStore(redisClient),
* maxRequests: 1000,
* windowMs: 60000
* }))
* .handle(async (context) => {
* return await handleHighVolumeAPI(context);
* });
* ```
*
* @example
* Multi-dimensional rate limiting:
* ```typescript
* const advancedHandler = new Handler()
* .use(rateLimiting({
* maxRequests: 100,
* windowMs: 60000,
* dynamicLimits: {
* // Different limits by operation type
* read_operations: {
* maxRequests: 1000,
* windowMs: 60000,
* matcher: (context) => context.req.method === 'GET'
* },
* write_operations: {
* maxRequests: 50,
* windowMs: 60000,
* matcher: (context) => ['POST', 'PUT', 'DELETE'].includes(context.req.method || '')
* },
* // Different limits by user tier
* enterprise_users: {
* maxRequests: 5000,
* windowMs: 60000,
* matcher: (context) => context.user?.tier === 'enterprise'
* }
* },
* keyGenerator: (context) => {
* // Multi-dimensional key: user + operation type
* const userId = context.user?.id || context.req.ip;
* const operation = context.req.method === 'GET' ? 'read' : 'write';
* return `${operation}:${userId}`;
* }
* }))
* .handle(async (context) => {
* return await processAdvancedRequest(context);
* });
* ```
*/
const rateLimiting = (options = {}) => new RateLimitingMiddleware(options);
exports.rateLimiting = rateLimiting;
/**
* Predefined rate limit configurations for common use cases.
*
* These presets are designed to work well in different infrastructure scenarios:
* - WAF + Application: Higher limits since WAF pre-filters traffic
* - Gateway + Application: Moderate limits complementing gateway quotas
* - Application Only: Conservative limits for comprehensive protection
*
* ## Preset Selection Guide
*
* | Preset | Use Case | Infrastructure | Requests/Min |
* |--------|----------|---------------|-------------|
* | `STRICT` | Sensitive operations | Any | 5 |
* | `AUTH` | Authentication endpoints | Any | 10 |
* | `PUBLIC` | Public/unauthenticated | App Only | 50 |
* | `API` | Standard API endpoints | WAF/Gateway + App | 100-1000 |
* | `DEVELOPMENT` | Development/testing | Development | 10,000 |
*
* @example
* Choosing the right preset:
* ```typescript
* // High-security endpoint (password reset)
* .use(rateLimiting(RateLimitPresets.STRICT))
*
* // Login/registration
* .use(rateLimiting(RateLimitPresets.AUTH))
*
* // Public API with WAF protection
* .use(rateLimiting(RateLimitPresets.API))
*
* // Public API without WAF (direct exposure)
* .use(rateLimiting(RateLimitPresets.PUBLIC))
* ```
*/
exports.RateLimitPresets = {
/**
* Very strict limits for sensitive endpoints
* Use for: Password resets, account changes, payment operations
* Infrastructure: Any (universal protection)
*/
STRICT: {
maxRequests: 5,
windowMs: 60000, // 1 minute
message: 'Too many requests to sensitive endpoint',
},
/**
* Standard API limits with dynamic scaling for authenticated users
* Use for: Main API endpoints, data retrieval, business operations
* Infrastructure: Best with WAF or Gateway (higher baseline limits)
*/
API: {
maxRequests: 100, // Baseline for unauthenticated/free users
windowMs: 60000, // 1 minute
dynamicLimits: {
authenticated: {
maxRequests: 1000, // 10x increase for authenticated users
windowMs: 60000,
matcher: (context) => !!context.user,
},
},
},
/**
* Authentication endpoint limits
* Use for: Login, registration, token refresh, password operations
* Infrastructure: Any (essential security protection)
*/
AUTH: {
maxRequests: 10,
windowMs: 60000, // 1 minute
message: 'Too many authentication attempts',
keyGenerator: (context) => {
// Rate limit per IP + email combination for better security
const ip = context.req.ip || 'unknown';
const email = context.req.parsedBody?.email;
return email ? `auth:${email}:${ip}` : `auth:${ip}`;
},
},
/**
* Public endpoint limits for direct application exposure
* Use for: Public APIs, webhooks, health checks
* Infrastructure: Application only (no WAF/Gateway protection)
*/
PUBLIC: {
maxRequests: 50,
windowMs: 60000, // 1 minute
dynamicLimits: {
// Be more restrictive with suspicious traffic patterns
suspicious: {
maxRequests: 10,
windowMs: 60000,
matcher: (context) => {
const userAgent = context.req.headers?.['user-agent'] || '';
return (!userAgent || userAgent.includes('bot') || userAgent.length < 10);
},
},
},
},
/**
* Development mode - very permissive limits
* Use for: Development, testing, debugging
* Infrastructure: Development environment only
*/
DEVELOPMENT: {
maxRequests: 10000,
windowMs: 60000, // 1 minute
skip: (context) => {
// Skip rate limiting for localhost and development IPs
const ip = context.req.ip || '';
return (ip.startsWith('127.') ||
ip.startsWith('::1') ||
ip.startsWith('192.168.'));
},
},
/**
* Enterprise-grade configuration with multi-tier support
* Use for: Production SaaS applications, enterprise APIs
* Infrastructure: WAF + Gateway + Application (full stack protection)
*/
ENTERPRISE: {
maxRequests: 200, // Higher baseline with multiple protection layers
windowMs: 60000,
dynamicLimits: {
free: {
maxRequests: 100,
windowMs: 60000,
matcher: (context) => !context.user || context.user?.plan === 'free',
},
premium: {
maxRequests: 1000,
windowMs: 60000,
matcher: (context) => context.user?.plan === 'premium',
},
enterprise: {
maxRequests: 5000,
windowMs: 60000,
matcher: (context) => context.user?.plan === 'enterprise',
},
admin: {
maxRequests: 10000,
windowMs: 60000,
matcher: (context) => context.user?.role === 'admin',
},
},
keyGenerator: (context) => {
// Use user ID for authenticated, IP for anonymous
return context.user?.id
? `user:${context.user.id}`
: `ip:${context.req.ip}`;
},
},
};
/**
* Configuration helpers and utilities for rate limiting setup
*
* ## Best Practices for Production
*
* ### 1. Store Selection
* - **Development**: Use default `MemoryStore` (built-in)
* - **Production Single Instance**: Use `MemoryStore` with cleanup
* - **Production Multi-Instance**: Use Redis-based store
* - **Serverless**: Use external store (Redis/DynamoDB) for state persistence
*
* ### 2. Key Generation Strategy
* ```typescript
* // Bad: Too generic, easy to abuse
* keyGenerator: () => 'global'
*
* // Good: Multi-dimensional keys
* keyGenerator: (context) => {
* const user = context.user?.id;
* const endpoint = context.req.path?.split('/')[2]; // /api/users -> users
* const method = context.req.method;
* return user ? `${user}:${endpoint}:${method}` : `${context.req.ip}:${endpoint}`;
* }
* ```
*
* ### 3. Dynamic Limits Best Practices
* ```typescript
* // Order matchers from most specific to least specific
* dynamicLimits: {
* admin: { maxRequests: 10000, matcher: (ctx) => ctx.user?.role === 'admin' },
* enterprise: { maxRequests: 5000, matcher: (ctx) => ctx.user?.plan === 'enterprise' },
* premium: { maxRequests: 1000, matcher: (ctx) => ctx.user?.plan === 'premium' },
* authenticated: { maxRequests: 500, matcher: (ctx) => !!ctx.user },
* // Default fallback handled by maxRequests
* }
* ```
*
* ### 4. Error Handling and Fallback
* ```typescript
* const resilientRateLimit = rateLimiting({
* maxRequests: 100,
* windowMs: 60000,
*
* // Custom store with fallback
* store: new ResilientStore({
* primary: redisStore,
* fallback: new MemoryStore(),
* timeout: 500 // ms
* }),
*
* // Graceful degradation on errors
* onError: (error, context) => {
* logger.warn('Rate limiting error, allowing request', { error, ip: context.req.ip });
* return false; // Don't block request on store errors
* }
* });
* ```
*
* ### 5. Monitoring and Alerting
* ```typescript
* // Monitor rate limit effectiveness
* const monitoredRateLimit = rateLimiting({
* maxRequests: 100,
* windowMs: 60000,
*
* onRateLimit: (context, info) => {
* // Alert on high rate limit hits
* metrics.increment('rate_limit.exceeded', {
* endpoint: context.req.path,
* user: context.user?.id || 'anonymous'
* });
*
* // Log suspicious patterns
* if (info.current > info.limit * 2) {
* logger.warn('Potential abuse detected', {
* ip: context.req.ip,
* userAgent: context.req.headers?.['user-agent'],
* attempts: info.current
* });
* }
* }
* });
* ```
*
* ### 6. Testing Rate Limits
* ```typescript
* // Test helper for rate limit validation
* export const testRateLimit = async (
* handler: Handler,
* requests: number,
* shouldSucceed: number
* ) => {
* const results = await Promise.all(
* Array(requests).fill(0).map(() => handler.execute(mockRequest, mockResponse))
* );
*
* const successful = results.filter(r => r.statusCode !== 429).length;
* expect(successful).toBe(shouldSucceed);
* };
* ```
*
* ## Troubleshooting Common Issues
*
* ### Issue: Rate limits not working
* **Solution**: Check key generation and store connection
* ```typescript
* // Debug key generation
* keyGenerator: (context) => {
* const key = generateKey(context);
* console.log('Rate limit key:', key); // Remove in production
* return key;
* }
* ```
*
* ### Issue: Too many false positives
* **Solution**: Refine dynamic limits and key generation
* ```typescript
* // More granular limits
* dynamicLimits: {
* read: { maxRequests: 1000, matcher: (ctx) => ctx.req.method === 'GET' },
* write: { maxRequests: 100, matcher: (ctx) => ctx.req.method !== 'GET' }
* }
* ```
*
* ### Issue: Memory leaks in MemoryStore
* **Solution**: Ensure proper cleanup interval and limits
* ```typescript
* // Monitor store size
* setInterval(() => {
* const storeSize = memoryStore.size();
* if (storeSize > 10000) {
* logger.warn('Rate limit store size growing', { size: storeSize });
* }
* }, 60000);
* ```
*
* ### Issue: Rate limits too restrictive
* **Solution**: Implement gradual enforcement
* ```typescript
* const gradualLimit = rateLimiting({
* maxRequests: 100,
* windowMs: 60000,
*
* // Warn before blocking
* onApproachingLimit: (context, info) => {
* if (info.remaining < 10) {
* context.res.header('X-Rate-Limit-Warning', 'Approaching limit');
* }
* }
* });
* ```
*/
//# sourceMappingURL=rateLimitingMiddleware.js.map