@stacksjs/bun-router 0.0.12 → 0.0.14
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/CHANGELOG.md +770 -0
- package/README.md +584 -261
- package/package.json +43 -48
- package/bin/cli.ts +0 -9
- package/dist/auth.d.ts +0 -134
- package/dist/cache/lru-cache.d.ts +0 -139
- package/dist/cache/middleware-memoization.d.ts +0 -151
- package/dist/cache/route-cache-warmer.d.ts +0 -161
- package/dist/cache/sqlite-cache.d.ts +0 -209
- package/dist/cache/streaming-cache.d.ts +0 -132
- package/dist/chunk-1mvtv6jw.js +0 -13012
- package/dist/cli/colors.d.ts +0 -15
- package/dist/cli/index.d.ts +0 -10
- package/dist/cli/middleware.d.ts +0 -31
- package/dist/cli/openapi.d.ts +0 -17
- package/dist/cli/router.d.ts +0 -15
- package/dist/cli/routes.d.ts +0 -29
- package/dist/cli/utils.d.ts +0 -74
- package/dist/cli.d.ts +0 -1
- package/dist/cli.js +0 -3195
- package/dist/config.d.ts +0 -7
- package/dist/container/container.d.ts +0 -273
- package/dist/container/contextual-binding.d.ts +0 -240
- package/dist/container/decorators.d.ts +0 -141
- package/dist/container/service-provider.d.ts +0 -285
- package/dist/development/hot-reload.d.ts +0 -187
- package/dist/development/index.d.ts +0 -167
- package/dist/development/performance-profiler.d.ts +0 -217
- package/dist/development/route-debugger.d.ts +0 -154
- package/dist/development/route-inspector.d.ts +0 -211
- package/dist/development/typescript-utilities.d.ts +0 -209
- package/dist/docs.d.ts +0 -11
- package/dist/errors/circuit-breaker.d.ts +0 -195
- package/dist/errors/error-handler.d.ts +0 -88
- package/dist/errors/error-reporting.d.ts +0 -144
- package/dist/errors/exceptions.d.ts +0 -184
- package/dist/errors/graceful-degradation.d.ts +0 -154
- package/dist/errors/index.d.ts +0 -9
- package/dist/errors/router-errors.d.ts +0 -110
- package/dist/file-serving/static-files.d.ts +0 -143
- package/dist/index.d.ts +0 -14
- package/dist/index.js +0 -13933
- package/dist/middleware/auth.d.ts +0 -63
- package/dist/middleware/content_security_policy.d.ts +0 -53
- package/dist/middleware/cors.d.ts +0 -5
- package/dist/middleware/csrf.d.ts +0 -7
- package/dist/middleware/ddos_protection.d.ts +0 -39
- package/dist/middleware/file_security.d.ts +0 -26
- package/dist/middleware/file_upload.d.ts +0 -38
- package/dist/middleware/helmet.d.ts +0 -54
- package/dist/middleware/index.d.ts +0 -44
- package/dist/middleware/input_validation.d.ts +0 -45
- package/dist/middleware/json_body.d.ts +0 -4
- package/dist/middleware/performance_alerting.d.ts +0 -87
- package/dist/middleware/performance_dashboard.d.ts +0 -87
- package/dist/middleware/performance_monitor.d.ts +0 -209
- package/dist/middleware/pipeline.d.ts +0 -131
- package/dist/middleware/rate_limit.d.ts +0 -36
- package/dist/middleware/request_id.d.ts +0 -4
- package/dist/middleware/request_signing.d.ts +0 -153
- package/dist/middleware/request_tracer.d.ts +0 -72
- package/dist/middleware/response_cache.d.ts +0 -97
- package/dist/middleware/security.d.ts +0 -69
- package/dist/middleware/security_suite.d.ts +0 -46
- package/dist/middleware/session.d.ts +0 -23
- package/dist/model-binding/index.d.ts +0 -2
- package/dist/model-binding/model-middleware.d.ts +0 -118
- package/dist/model-binding/model-registry.d.ts +0 -164
- package/dist/model-binding.d.ts +0 -185
- package/dist/model-resolver-factory.d.ts +0 -31
- package/dist/observability/correlation.d.ts +0 -187
- package/dist/observability/health-checks.d.ts +0 -186
- package/dist/observability/index.d.ts +0 -60
- package/dist/observability/integration.d.ts +0 -147
- package/dist/observability/metrics.d.ts +0 -184
- package/dist/observability/tracing.d.ts +0 -187
- package/dist/optimization/bun-utilities.d.ts +0 -224
- package/dist/query-builder-integration.d.ts +0 -36
- package/dist/request/context.d.ts +0 -34
- package/dist/request/enhanced-request.d.ts +0 -213
- package/dist/request/macros.d.ts +0 -300
- package/dist/response/macros.d.ts +0 -245
- package/dist/response/response-factory.d.ts +0 -133
- package/dist/router/api-routes.d.ts +0 -5
- package/dist/router/file-based-routing.d.ts +0 -118
- package/dist/router/file-streaming.d.ts +0 -5
- package/dist/router/fluent-router.d.ts +0 -315
- package/dist/router/fluent-routing.d.ts +0 -271
- package/dist/router/group-organization.d.ts +0 -5
- package/dist/router/handler-resolver.d.ts +0 -24
- package/dist/router/http-methods.d.ts +0 -10
- package/dist/router/index.d.ts +0 -114
- package/dist/router/middleware-groups.d.ts +0 -94
- package/dist/router/middleware-integration.d.ts +0 -112
- package/dist/router/middleware.d.ts +0 -5
- package/dist/router/model-binding.d.ts +0 -5
- package/dist/router/optimized-route-matching.d.ts +0 -6
- package/dist/router/route-building.d.ts +0 -5
- package/dist/router/route-compiler.d.ts +0 -153
- package/dist/router/route-matching.d.ts +0 -5
- package/dist/router/route-trie.d.ts +0 -114
- package/dist/router/router.d.ts +0 -294
- package/dist/router/server.d.ts +0 -5
- package/dist/router/validation-integration.d.ts +0 -170
- package/dist/router/view-rendering.d.ts +0 -5
- package/dist/router/websocket.d.ts +0 -5
- package/dist/routing/route-caching.d.ts +0 -129
- package/dist/routing/route-throttling.d.ts +0 -150
- package/dist/routing/subdomain-routing.d.ts +0 -206
- package/dist/session/database-store.d.ts +0 -51
- package/dist/session/file-store.d.ts +0 -22
- package/dist/session/index.d.ts +0 -106
- package/dist/session/memory-store.d.ts +0 -21
- package/dist/session/redis-store.d.ts +0 -31
- package/dist/streaming/index.d.ts +0 -3
- package/dist/streaming/sse-handler.d.ts +0 -138
- package/dist/streaming/stream-handler.d.ts +0 -114
- package/dist/testing/auth-testing.d.ts +0 -156
- package/dist/testing/file-upload-testing.d.ts +0 -190
- package/dist/testing/index.d.ts +0 -10
- package/dist/testing/middleware-testing.d.ts +0 -138
- package/dist/testing/model-binding-testing.d.ts +0 -186
- package/dist/testing/performance-testing.d.ts +0 -235
- package/dist/testing/test-client.d.ts +0 -117
- package/dist/testing/test-request.d.ts +0 -85
- package/dist/testing/test-response.d.ts +0 -90
- package/dist/testing/types.d.ts +0 -207
- package/dist/testing/websocket-testing.d.ts +0 -227
- package/dist/types/controller-types.d.ts +0 -208
- package/dist/types/core.d.ts +0 -543
- package/dist/types/middleware-types.d.ts +0 -227
- package/dist/types/request-response-augmentation.d.ts +0 -261
- package/dist/types/route-inference.d.ts +0 -168
- package/dist/types.d.ts +0 -1782
- package/dist/url.d.ts +0 -56
- package/dist/utils/index.d.ts +0 -1
- package/dist/utils/query-preservation.d.ts +0 -48
- package/dist/utils.d.ts +0 -69
- package/dist/validation/validator.d.ts +0 -140
- package/dist/websocket/clustering.d.ts +0 -185
- package/src/auth.ts +0 -469
- package/src/cache/lru-cache.ts +0 -457
- package/src/cache/middleware-memoization.ts +0 -531
- package/src/cache/route-cache-warmer.ts +0 -486
- package/src/cache/sqlite-cache.ts +0 -783
- package/src/cache/streaming-cache.ts +0 -572
- package/src/cli/colors.ts +0 -29
- package/src/cli/index.ts +0 -287
- package/src/cli/middleware.ts +0 -291
- package/src/cli/openapi.ts +0 -407
- package/src/cli/router.ts +0 -188
- package/src/cli/routes.ts +0 -265
- package/src/cli/utils.ts +0 -531
- package/src/cli.ts +0 -5
- package/src/config.ts +0 -322
- package/src/container/container.ts +0 -740
- package/src/container/contextual-binding.ts +0 -603
- package/src/container/decorators.ts +0 -359
- package/src/container/service-provider.ts +0 -596
- package/src/development/hot-reload.ts +0 -673
- package/src/development/index.ts +0 -499
- package/src/development/performance-profiler.ts +0 -717
- package/src/development/route-debugger.ts +0 -527
- package/src/development/route-inspector.ts +0 -749
- package/src/development/typescript-utilities.ts +0 -682
- package/src/docs.ts +0 -397
- package/src/errors/circuit-breaker.ts +0 -732
- package/src/errors/error-handler.ts +0 -569
- package/src/errors/error-reporting.ts +0 -672
- package/src/errors/exceptions.ts +0 -536
- package/src/errors/graceful-degradation.ts +0 -621
- package/src/errors/index.ts +0 -21
- package/src/errors/router-errors.ts +0 -632
- package/src/file-serving/static-files.ts +0 -581
- package/src/index.ts +0 -14
- package/src/middleware/auth.ts +0 -219
- package/src/middleware/content_security_policy.ts +0 -215
- package/src/middleware/cors.ts +0 -74
- package/src/middleware/csrf.ts +0 -108
- package/src/middleware/ddos_protection.ts +0 -255
- package/src/middleware/file_security.ts +0 -191
- package/src/middleware/file_upload.ts +0 -264
- package/src/middleware/helmet.ts +0 -268
- package/src/middleware/index.ts +0 -117
- package/src/middleware/input_validation.ts +0 -449
- package/src/middleware/json_body.ts +0 -37
- package/src/middleware/performance_alerting.ts +0 -538
- package/src/middleware/performance_dashboard.ts +0 -661
- package/src/middleware/performance_monitor.ts +0 -943
- package/src/middleware/pipeline.ts +0 -489
- package/src/middleware/rate_limit.ts +0 -245
- package/src/middleware/request_id.ts +0 -36
- package/src/middleware/request_signing.ts +0 -636
- package/src/middleware/request_tracer.ts +0 -636
- package/src/middleware/response_cache.ts +0 -743
- package/src/middleware/security.ts +0 -482
- package/src/middleware/security_suite.ts +0 -257
- package/src/middleware/session.ts +0 -91
- package/src/model-binding/index.ts +0 -18
- package/src/model-binding/model-middleware.ts +0 -425
- package/src/model-binding/model-registry.ts +0 -550
- package/src/model-binding.ts +0 -370
- package/src/model-resolver-factory.ts +0 -106
- package/src/observability/correlation.ts +0 -691
- package/src/observability/health-checks.ts +0 -729
- package/src/observability/index.ts +0 -184
- package/src/observability/integration.ts +0 -548
- package/src/observability/metrics.ts +0 -753
- package/src/observability/tracing.ts +0 -638
- package/src/optimization/bun-utilities.ts +0 -778
- package/src/query-builder-integration.ts +0 -137
- package/src/request/context.ts +0 -62
- package/src/request/enhanced-request.ts +0 -857
- package/src/request/macros.ts +0 -688
- package/src/response/macros.ts +0 -612
- package/src/response/response-factory.ts +0 -596
- package/src/router/api-routes.ts +0 -243
- package/src/router/file-based-routing.ts +0 -670
- package/src/router/file-streaming.ts +0 -372
- package/src/router/fluent-router.ts +0 -927
- package/src/router/fluent-routing.ts +0 -797
- package/src/router/group-organization.ts +0 -213
- package/src/router/handler-resolver.ts +0 -291
- package/src/router/http-methods.ts +0 -377
- package/src/router/index.ts +0 -187
- package/src/router/middleware-groups.ts +0 -222
- package/src/router/middleware-integration.ts +0 -399
- package/src/router/middleware.ts +0 -231
- package/src/router/model-binding.ts +0 -215
- package/src/router/optimized-route-matching.ts +0 -253
- package/src/router/route-building.ts +0 -212
- package/src/router/route-compiler.ts +0 -702
- package/src/router/route-matching.ts +0 -349
- package/src/router/route-trie.ts +0 -464
- package/src/router/router.ts +0 -1581
- package/src/router/server.ts +0 -462
- package/src/router/validation-integration.ts +0 -443
- package/src/router/view-rendering.ts +0 -233
- package/src/router/websocket.ts +0 -100
- package/src/routing/route-caching.ts +0 -402
- package/src/routing/route-throttling.ts +0 -469
- package/src/routing/subdomain-routing.ts +0 -492
- package/src/session/database-store.ts +0 -109
- package/src/session/file-store.ts +0 -148
- package/src/session/index.ts +0 -244
- package/src/session/memory-store.ts +0 -88
- package/src/session/redis-store.ts +0 -93
- package/src/streaming/index.ts +0 -17
- package/src/streaming/sse-handler.ts +0 -482
- package/src/streaming/stream-handler.ts +0 -552
- package/src/testing/auth-testing.ts +0 -446
- package/src/testing/file-upload-testing.ts +0 -543
- package/src/testing/index.ts +0 -10
- package/src/testing/middleware-testing.ts +0 -322
- package/src/testing/model-binding-testing.ts +0 -645
- package/src/testing/performance-testing.ts +0 -738
- package/src/testing/test-client.ts +0 -310
- package/src/testing/test-request.ts +0 -315
- package/src/testing/test-response.ts +0 -332
- package/src/testing/types.ts +0 -224
- package/src/testing/websocket-testing.ts +0 -590
- package/src/types/controller-types.ts +0 -385
- package/src/types/core.ts +0 -683
- package/src/types/middleware-types.ts +0 -419
- package/src/types/request-response-augmentation.ts +0 -489
- package/src/types/route-inference.ts +0 -357
- package/src/types.ts +0 -2055
- package/src/url.ts +0 -126
- package/src/utils/index.ts +0 -1
- package/src/utils/query-preservation.ts +0 -201
- package/src/utils.ts +0 -327
- package/src/validation/validator.ts +0 -685
- package/src/websocket/clustering.ts +0 -762
|
@@ -1,469 +0,0 @@
|
|
|
1
|
-
import type { EnhancedRequest, MiddlewareHandler, NextFunction, ThrottlePattern } from '../types'
|
|
2
|
-
import { LRUCache } from '../cache/lru-cache'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Throttle configuration for rate limiting
|
|
6
|
-
*/
|
|
7
|
-
export interface ThrottleConfig {
|
|
8
|
-
maxAttempts: number // Maximum attempts allowed
|
|
9
|
-
windowMs: number // Time window in milliseconds
|
|
10
|
-
keyGenerator?: (req: EnhancedRequest) => string // Custom key generator
|
|
11
|
-
skipIf?: (req: EnhancedRequest) => boolean // Skip rate limiting condition
|
|
12
|
-
onLimitReached?: (req: EnhancedRequest, info: RateLimitInfo) => Response // Custom response when limit reached
|
|
13
|
-
headers?: boolean // Include rate limit headers in response
|
|
14
|
-
maxRequests?: number
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Rate limit information
|
|
19
|
-
*/
|
|
20
|
-
export interface RateLimitInfo {
|
|
21
|
-
totalHits: number
|
|
22
|
-
totalHits24h?: number
|
|
23
|
-
resetTime: Date
|
|
24
|
-
remaining: number
|
|
25
|
-
limit: number
|
|
26
|
-
windowMs: number
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Rate limit entry stored in cache
|
|
31
|
-
*/
|
|
32
|
-
interface RateLimitEntry {
|
|
33
|
-
count: number
|
|
34
|
-
resetTime: number
|
|
35
|
-
firstHit: number
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Rate limiter implementation
|
|
40
|
-
*/
|
|
41
|
-
export class RateLimiter {
|
|
42
|
-
private cache: LRUCache<RateLimitEntry>
|
|
43
|
-
private config: ThrottleConfig
|
|
44
|
-
|
|
45
|
-
constructor(config: ThrottleConfig, cacheSize: number = 10000) {
|
|
46
|
-
this.config = config
|
|
47
|
-
this.cache = new LRUCache<RateLimitEntry>({
|
|
48
|
-
maxSize: cacheSize,
|
|
49
|
-
ttl: config.windowMs * 2, // Keep entries longer than window for cleanup
|
|
50
|
-
})
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Check if request should be rate limited
|
|
55
|
-
*/
|
|
56
|
-
async checkLimit(req: EnhancedRequest): Promise<{ allowed: boolean, info: RateLimitInfo }> {
|
|
57
|
-
// Skip if condition is met
|
|
58
|
-
if (this.config.skipIf && this.config.skipIf(req)) {
|
|
59
|
-
return {
|
|
60
|
-
allowed: true,
|
|
61
|
-
info: {
|
|
62
|
-
totalHits: 0,
|
|
63
|
-
resetTime: new Date(Date.now() + this.config.windowMs),
|
|
64
|
-
remaining: this.config.maxAttempts,
|
|
65
|
-
limit: this.config.maxAttempts,
|
|
66
|
-
windowMs: this.config.windowMs,
|
|
67
|
-
},
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const key = this.generateKey(req)
|
|
72
|
-
const now = Date.now()
|
|
73
|
-
|
|
74
|
-
let entry = this.cache.get(key)
|
|
75
|
-
|
|
76
|
-
// Clean up expired entry or create new one
|
|
77
|
-
if (!entry || now >= entry.resetTime) {
|
|
78
|
-
entry = {
|
|
79
|
-
count: 1,
|
|
80
|
-
resetTime: now + this.config.windowMs,
|
|
81
|
-
firstHit: now,
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
else {
|
|
85
|
-
entry.count++
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Update cache
|
|
89
|
-
this.cache.set(key, entry)
|
|
90
|
-
|
|
91
|
-
const info: RateLimitInfo = {
|
|
92
|
-
totalHits: entry.count,
|
|
93
|
-
resetTime: new Date(entry.resetTime),
|
|
94
|
-
remaining: Math.max(0, this.config.maxAttempts - entry.count),
|
|
95
|
-
limit: this.config.maxAttempts,
|
|
96
|
-
windowMs: this.config.windowMs,
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const allowed = entry.count <= this.config.maxAttempts
|
|
100
|
-
|
|
101
|
-
return { allowed, info }
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Generate cache key for rate limiting
|
|
106
|
-
*/
|
|
107
|
-
private generateKey(req: EnhancedRequest): string {
|
|
108
|
-
if (this.config.keyGenerator) {
|
|
109
|
-
return this.config.keyGenerator(req)
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Default key generation: IP + User ID (if available)
|
|
113
|
-
const ip = this.getClientIP(req)
|
|
114
|
-
const userId = req.user?.id
|
|
115
|
-
|
|
116
|
-
if (userId) {
|
|
117
|
-
return `user:${userId}`
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
return `ip:${ip}`
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Extract client IP from request
|
|
125
|
-
*/
|
|
126
|
-
private getClientIP(req: EnhancedRequest): string {
|
|
127
|
-
// Check common headers for real IP
|
|
128
|
-
const forwardedFor = req.headers.get('x-forwarded-for')
|
|
129
|
-
if (forwardedFor) {
|
|
130
|
-
return forwardedFor.split(',')[0].trim()
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const realIP = req.headers.get('x-real-ip')
|
|
134
|
-
if (realIP) {
|
|
135
|
-
return realIP
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const cfConnectingIP = req.headers.get('cf-connecting-ip')
|
|
139
|
-
if (cfConnectingIP) {
|
|
140
|
-
return cfConnectingIP
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Fallback to a default IP if none found
|
|
144
|
-
return 'unknown'
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Get current statistics
|
|
149
|
-
*/
|
|
150
|
-
getStats(): ReturnType<LRUCache<RateLimitEntry>['getStats']> & { config: { maxAttempts: number, windowMs: number } } {
|
|
151
|
-
return {
|
|
152
|
-
...this.cache.getStats(),
|
|
153
|
-
config: {
|
|
154
|
-
maxAttempts: this.config.maxAttempts,
|
|
155
|
-
windowMs: this.config.windowMs,
|
|
156
|
-
},
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Global rate limiters registry
|
|
163
|
-
*/
|
|
164
|
-
export class RateLimitRegistry {
|
|
165
|
-
private limiters = new Map<string, RateLimiter>()
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Register a rate limiter
|
|
169
|
-
*/
|
|
170
|
-
register(name: string, config: ThrottleConfig): RateLimiter {
|
|
171
|
-
const limiter = new RateLimiter(config)
|
|
172
|
-
this.limiters.set(name, limiter)
|
|
173
|
-
return limiter
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/**
|
|
177
|
-
* Get rate limiter by name
|
|
178
|
-
*/
|
|
179
|
-
get(name: string): RateLimiter | undefined {
|
|
180
|
-
return this.limiters.get(name)
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Create or get rate limiter
|
|
185
|
-
*/
|
|
186
|
-
getOrCreate(name: string, config: ThrottleConfig): RateLimiter {
|
|
187
|
-
const existing = this.limiters.get(name)
|
|
188
|
-
if (existing) {
|
|
189
|
-
return existing
|
|
190
|
-
}
|
|
191
|
-
return this.register(name, config)
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/**
|
|
196
|
-
* Global rate limit registry instance
|
|
197
|
-
*/
|
|
198
|
-
export const rateLimitRegistry: RateLimitRegistry = new RateLimitRegistry()
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* Create rate limiting middleware
|
|
202
|
-
*/
|
|
203
|
-
export function createRateLimitMiddleware(config: ThrottleConfig, name?: string): MiddlewareHandler {
|
|
204
|
-
const limiter = name
|
|
205
|
-
? rateLimitRegistry.getOrCreate(name, config)
|
|
206
|
-
: new RateLimiter(config)
|
|
207
|
-
|
|
208
|
-
return async (req: EnhancedRequest, next: NextFunction): Promise<Response | null> => {
|
|
209
|
-
const { allowed, info } = await limiter.checkLimit(req)
|
|
210
|
-
|
|
211
|
-
if (!allowed) {
|
|
212
|
-
// Use custom response if provided
|
|
213
|
-
if (config.onLimitReached) {
|
|
214
|
-
return config.onLimitReached(req, info)
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// Default rate limit response
|
|
218
|
-
const headers = new Headers({
|
|
219
|
-
'Content-Type': 'application/json',
|
|
220
|
-
'Retry-After': Math.ceil((info.resetTime.getTime() - Date.now()) / 1000).toString(),
|
|
221
|
-
})
|
|
222
|
-
|
|
223
|
-
if (config.headers !== false) {
|
|
224
|
-
headers.set('X-RateLimit-Limit', info.limit.toString())
|
|
225
|
-
headers.set('X-RateLimit-Remaining', info.remaining.toString())
|
|
226
|
-
headers.set('X-RateLimit-Reset', Math.ceil(info.resetTime.getTime() / 1000).toString())
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
return new Response(
|
|
230
|
-
JSON.stringify({
|
|
231
|
-
error: 'Too Many Requests',
|
|
232
|
-
message: `Rate limit exceeded. Try again in ${Math.ceil((info.resetTime.getTime() - Date.now()) / 1000)} seconds.`,
|
|
233
|
-
retryAfter: Math.ceil((info.resetTime.getTime() - Date.now()) / 1000),
|
|
234
|
-
}),
|
|
235
|
-
{
|
|
236
|
-
status: 429,
|
|
237
|
-
headers,
|
|
238
|
-
},
|
|
239
|
-
)
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// Continue to next middleware/handler
|
|
243
|
-
const response = await next()
|
|
244
|
-
|
|
245
|
-
// Add rate limit headers to successful responses
|
|
246
|
-
if (response && config.headers !== false && response.status < 400) {
|
|
247
|
-
const headers = new Headers(response.headers)
|
|
248
|
-
headers.set('X-RateLimit-Limit', info.limit.toString())
|
|
249
|
-
headers.set('X-RateLimit-Remaining', info.remaining.toString())
|
|
250
|
-
headers.set('X-RateLimit-Reset', Math.ceil(info.resetTime.getTime() / 1000).toString())
|
|
251
|
-
|
|
252
|
-
return new Response(response.body, {
|
|
253
|
-
status: response.status,
|
|
254
|
-
statusText: response.statusText,
|
|
255
|
-
headers,
|
|
256
|
-
})
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
return response
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
/**
|
|
264
|
-
* Parse throttle string (Laravel-style) with enhanced time unit support
|
|
265
|
-
* Examples: '60,1' -> 60 requests per 1 minute
|
|
266
|
-
* '100,30s' -> 100 requests per 30 seconds
|
|
267
|
-
* '1000,1h' -> 1000 requests per 1 hour
|
|
268
|
-
* '60' -> 60 requests per 1 minute (default)
|
|
269
|
-
*/
|
|
270
|
-
export function parseThrottleString(throttleStr: ThrottlePattern): ThrottleConfig {
|
|
271
|
-
const parts = throttleStr.split(',')
|
|
272
|
-
const maxAttempts = Number.parseInt(parts[0], 10)
|
|
273
|
-
|
|
274
|
-
if (Number.isNaN(maxAttempts) || maxAttempts <= 0) {
|
|
275
|
-
throw new Error(`Invalid throttle max attempts: ${parts[0]}`)
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
let windowMs: number
|
|
279
|
-
|
|
280
|
-
if (parts.length === 1) {
|
|
281
|
-
// Default to 1 minute if no time window specified
|
|
282
|
-
windowMs = 60 * 1000
|
|
283
|
-
}
|
|
284
|
-
else {
|
|
285
|
-
const timeStr = parts[1]
|
|
286
|
-
windowMs = parseTimeString(timeStr)
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
return {
|
|
290
|
-
maxAttempts,
|
|
291
|
-
windowMs,
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
/**
|
|
296
|
-
* Parse time string with units into milliseconds
|
|
297
|
-
*/
|
|
298
|
-
function parseTimeString(timeStr: string): number {
|
|
299
|
-
const timePattern = /^(\d+)([smh]|sec|min|hour)?$/
|
|
300
|
-
const match = timeStr.match(timePattern)
|
|
301
|
-
|
|
302
|
-
if (!match) {
|
|
303
|
-
throw new Error(`Invalid time format: ${timeStr}`)
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
const value = Number.parseInt(match[1], 10)
|
|
307
|
-
const unit = match[2] || 'm' // Default to minutes
|
|
308
|
-
|
|
309
|
-
if (Number.isNaN(value) || value <= 0) {
|
|
310
|
-
throw new Error(`Invalid time value: ${match[1]}`)
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
switch (unit) {
|
|
314
|
-
case 's':
|
|
315
|
-
case 'sec':
|
|
316
|
-
return value * 1000
|
|
317
|
-
case 'm':
|
|
318
|
-
case 'min':
|
|
319
|
-
return value * 60 * 1000
|
|
320
|
-
case 'h':
|
|
321
|
-
case 'hour':
|
|
322
|
-
return value * 60 * 60 * 1000
|
|
323
|
-
default:
|
|
324
|
-
// Fallback: treat as minutes if no unit or unknown unit
|
|
325
|
-
return value * 60 * 1000
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
/**
|
|
330
|
-
* Factory functions for common rate limiting scenarios
|
|
331
|
-
*/
|
|
332
|
-
export const ThrottleFactory = {
|
|
333
|
-
/**
|
|
334
|
-
* API rate limiting (60 requests per minute)
|
|
335
|
-
*/
|
|
336
|
-
api: (maxAttempts: number = 60, windowMinutes: number = 1): ThrottleConfig => ({
|
|
337
|
-
maxAttempts,
|
|
338
|
-
windowMs: windowMinutes * 60 * 1000,
|
|
339
|
-
headers: true,
|
|
340
|
-
}),
|
|
341
|
-
|
|
342
|
-
/**
|
|
343
|
-
* Authentication rate limiting (5 attempts per 15 minutes)
|
|
344
|
-
*/
|
|
345
|
-
auth: (): ThrottleConfig => ({
|
|
346
|
-
maxAttempts: 5,
|
|
347
|
-
windowMs: 15 * 60 * 1000,
|
|
348
|
-
keyGenerator: (req) => {
|
|
349
|
-
const ip = req.headers.get('x-forwarded-for')?.split(',')[0] || 'unknown'
|
|
350
|
-
const email = (req.jsonBody as any)?.email || (req.formBody as any)?.email || (req.query as any)?.email || 'unknown'
|
|
351
|
-
return `auth:${ip}:${email}`
|
|
352
|
-
},
|
|
353
|
-
onLimitReached: (req, info) => new Response(
|
|
354
|
-
JSON.stringify({
|
|
355
|
-
error: 'Too many authentication attempts',
|
|
356
|
-
message: 'Please try again later',
|
|
357
|
-
retryAfter: Math.ceil((info.resetTime.getTime() - Date.now()) / 1000),
|
|
358
|
-
}),
|
|
359
|
-
{
|
|
360
|
-
status: 429,
|
|
361
|
-
headers: {
|
|
362
|
-
'Content-Type': 'application/json',
|
|
363
|
-
'Retry-After': Math.ceil((info.resetTime.getTime() - Date.now()) / 1000).toString(),
|
|
364
|
-
},
|
|
365
|
-
},
|
|
366
|
-
),
|
|
367
|
-
}),
|
|
368
|
-
|
|
369
|
-
/**
|
|
370
|
-
* Upload rate limiting (10 uploads per hour)
|
|
371
|
-
*/
|
|
372
|
-
upload: (): ThrottleConfig => ({
|
|
373
|
-
maxAttempts: 10,
|
|
374
|
-
windowMs: 60 * 60 * 1000,
|
|
375
|
-
keyGenerator: (req) => {
|
|
376
|
-
const userId = req.user?.id
|
|
377
|
-
if (userId) {
|
|
378
|
-
return `upload:user:${userId}`
|
|
379
|
-
}
|
|
380
|
-
const ip = req.headers.get('x-forwarded-for')?.split(',')[0] || 'unknown'
|
|
381
|
-
return `upload:ip:${ip}`
|
|
382
|
-
},
|
|
383
|
-
}),
|
|
384
|
-
|
|
385
|
-
/**
|
|
386
|
-
* Search rate limiting (100 searches per 10 minutes)
|
|
387
|
-
*/
|
|
388
|
-
search: (): ThrottleConfig => ({
|
|
389
|
-
maxAttempts: 100,
|
|
390
|
-
windowMs: 10 * 60 * 1000,
|
|
391
|
-
skipIf: (req) => {
|
|
392
|
-
// Skip rate limiting for authenticated premium users
|
|
393
|
-
return req.user?.plan === 'premium'
|
|
394
|
-
},
|
|
395
|
-
}),
|
|
396
|
-
|
|
397
|
-
/**
|
|
398
|
-
* Per-user rate limiting
|
|
399
|
-
*/
|
|
400
|
-
perUser: (maxAttempts: number, windowMinutes: number): ThrottleConfig => ({
|
|
401
|
-
maxAttempts,
|
|
402
|
-
windowMs: windowMinutes * 60 * 1000,
|
|
403
|
-
keyGenerator: (req) => {
|
|
404
|
-
const userId = req.user?.id
|
|
405
|
-
if (!userId) {
|
|
406
|
-
throw new Error('User authentication required for per-user rate limiting')
|
|
407
|
-
}
|
|
408
|
-
return `user:${userId}`
|
|
409
|
-
},
|
|
410
|
-
skipIf: req => !req.user?.id, // Skip if not authenticated
|
|
411
|
-
}),
|
|
412
|
-
|
|
413
|
-
/**
|
|
414
|
-
* Per-IP rate limiting
|
|
415
|
-
*/
|
|
416
|
-
perIP: (maxAttempts: number, windowMinutes: number): ThrottleConfig => ({
|
|
417
|
-
maxAttempts,
|
|
418
|
-
windowMs: windowMinutes * 60 * 1000,
|
|
419
|
-
keyGenerator: (req) => {
|
|
420
|
-
const ip = req.headers.get('x-forwarded-for')?.split(',')[0]
|
|
421
|
-
|| req.headers.get('x-real-ip')
|
|
422
|
-
|| req.headers.get('cf-connecting-ip')
|
|
423
|
-
|| 'unknown'
|
|
424
|
-
return `ip:${ip}`
|
|
425
|
-
},
|
|
426
|
-
}),
|
|
427
|
-
|
|
428
|
-
/**
|
|
429
|
-
* Global rate limiting (shared across all users)
|
|
430
|
-
*/
|
|
431
|
-
global: (maxAttempts: number, windowMinutes: number): ThrottleConfig => ({
|
|
432
|
-
maxAttempts,
|
|
433
|
-
windowMs: windowMinutes * 60 * 1000,
|
|
434
|
-
keyGenerator: () => 'global',
|
|
435
|
-
}),
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
/**
|
|
439
|
-
* Rate limit utilities
|
|
440
|
-
*/
|
|
441
|
-
export const RateLimitUtils = {
|
|
442
|
-
/**
|
|
443
|
-
* Create throttle middleware from string (Laravel-style)
|
|
444
|
-
*/
|
|
445
|
-
fromString: (throttleStr: string, name?: string): MiddlewareHandler => {
|
|
446
|
-
const config = parseThrottleString(throttleStr as ThrottlePattern)
|
|
447
|
-
return createRateLimitMiddleware(config, name)
|
|
448
|
-
},
|
|
449
|
-
|
|
450
|
-
/**
|
|
451
|
-
* Get rate limit info for a request without incrementing
|
|
452
|
-
*/
|
|
453
|
-
getInfo: async (req: EnhancedRequest, config: ThrottleConfig): Promise<RateLimitInfo> => {
|
|
454
|
-
const limiter: RateLimiter = new RateLimiter(config)
|
|
455
|
-
const { info } = await limiter.checkLimit(req)
|
|
456
|
-
return info
|
|
457
|
-
},
|
|
458
|
-
|
|
459
|
-
/**
|
|
460
|
-
* Reset rate limit for a specific key
|
|
461
|
-
*/
|
|
462
|
-
reset: (limiterName: string, key: string): boolean => {
|
|
463
|
-
const limiter = rateLimitRegistry.get(limiterName)
|
|
464
|
-
if (limiter) {
|
|
465
|
-
return (limiter as any).cache.delete(key)
|
|
466
|
-
}
|
|
467
|
-
return false
|
|
468
|
-
},
|
|
469
|
-
}
|