@noony-serverless/core
Version:
A Middy base framework compatible with Firebase and GCP Cloud Functions with TypeScript
751 lines • 24 kB
TypeScript
import { BaseMiddleware, Context } from '../core';
export interface RateLimitOptions {
/**
* Maximum number of requests per window
* @default 100
*/
maxRequests?: number;
/**
* Time window in milliseconds
* @default 60000 (1 minute)
*/
windowMs?: number;
/**
* Function to generate rate limiting key
* @default Uses IP address
*/
keyGenerator?: <TBody, TUser>(context: Context<TBody, TUser>) => string;
/**
* Custom error message
* @default 'Too many requests, please try again later'
*/
message?: string;
/**
* HTTP status code for rate limit exceeded
* @default 429
*/
statusCode?: number;
/**
* Skip rate limiting for certain requests
*/
skip?: <TBody, TUser>(context: Context<TBody, TUser>) => boolean;
/**
* Headers to include in response
*/
headers?: boolean;
/**
* Different limits for different request types
*/
dynamicLimits?: {
[key: string]: {
maxRequests: number;
windowMs: number;
matcher: <TBody, TUser>(context: Context<TBody, TUser>) => boolean;
};
};
/**
* Storage backend (default: in-memory)
* Use Redis or other persistent storage in production
*/
store?: RateLimitStore;
}
export interface RateLimitStore {
increment(key: string, windowMs: number): Promise<{
count: number;
resetTime: number;
}>;
get(key: string): Promise<{
count: number;
resetTime: number;
} | null>;
reset(key: string): Promise<void>;
}
export interface RateLimitInfo {
limit: number;
current: number;
remaining: number;
resetTime: number;
}
/**
* In-memory rate limit store (use Redis in production)
*/
declare class MemoryStore implements RateLimitStore {
private store;
private cleanupInterval;
constructor();
increment(key: string, windowMs: number): Promise<{
count: number;
resetTime: number;
}>;
get(key: string): Promise<{
count: number;
resetTime: number;
} | null>;
reset(key: string): Promise<void>;
private cleanup;
destroy(): void;
}
/**
* 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);
* });
* ```
*/
export declare class RateLimitingMiddleware<TBody = unknown, TUser = unknown> implements BaseMiddleware<TBody, TUser> {
private store;
private options;
constructor(options?: RateLimitOptions);
before(context: Context<TBody, TUser>): Promise<void>;
}
/**
* 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);
* });
* ```
*/
export declare const rateLimiting: (options?: RateLimitOptions) => BaseMiddleware;
/**
* 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))
* ```
*/
export declare const RateLimitPresets: {
/**
* Very strict limits for sensitive endpoints
* Use for: Password resets, account changes, payment operations
* Infrastructure: Any (universal protection)
*/
readonly STRICT: {
maxRequests: number;
windowMs: number;
message: string;
};
/**
* 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)
*/
readonly API: {
maxRequests: number;
windowMs: number;
dynamicLimits: {
authenticated: {
maxRequests: number;
windowMs: number;
matcher: (context: Context) => boolean;
};
};
};
/**
* Authentication endpoint limits
* Use for: Login, registration, token refresh, password operations
* Infrastructure: Any (essential security protection)
*/
readonly AUTH: {
maxRequests: number;
windowMs: number;
message: string;
keyGenerator: (context: Context) => string;
};
/**
* Public endpoint limits for direct application exposure
* Use for: Public APIs, webhooks, health checks
* Infrastructure: Application only (no WAF/Gateway protection)
*/
readonly PUBLIC: {
maxRequests: number;
windowMs: number;
dynamicLimits: {
suspicious: {
maxRequests: number;
windowMs: number;
matcher: (context: Context) => boolean;
};
};
};
/**
* Development mode - very permissive limits
* Use for: Development, testing, debugging
* Infrastructure: Development environment only
*/
readonly DEVELOPMENT: {
maxRequests: number;
windowMs: number;
skip: (context: Context) => boolean;
};
/**
* Enterprise-grade configuration with multi-tier support
* Use for: Production SaaS applications, enterprise APIs
* Infrastructure: WAF + Gateway + Application (full stack protection)
*/
readonly ENTERPRISE: {
maxRequests: number;
windowMs: number;
dynamicLimits: {
free: {
maxRequests: number;
windowMs: number;
matcher: (context: Context) => boolean;
};
premium: {
maxRequests: number;
windowMs: number;
matcher: (context: Context) => boolean;
};
enterprise: {
maxRequests: number;
windowMs: number;
matcher: (context: Context) => boolean;
};
admin: {
maxRequests: number;
windowMs: number;
matcher: (context: Context) => boolean;
};
};
keyGenerator: (context: Context) => string;
};
};
/**
* Export memory store for testing and custom implementations
*/
export { MemoryStore };
/**
* 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.d.ts.map