@stacksjs/bun-router 0.0.17 → 0.0.19

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.
Files changed (153) hide show
  1. package/dist/chunk-2mx7fq49.js +4 -0
  2. package/dist/chunk-a8p83068.js +103 -0
  3. package/dist/{chunk-cgptvjdf.js → chunk-y3xq6wq6.js} +1 -1
  4. package/dist/cli.js +4 -3
  5. package/dist/container/index.js +1149 -211
  6. package/dist/container/service-provider.d.ts +3 -3
  7. package/dist/development/performance-profiler.d.ts +3 -3
  8. package/dist/development/route-debugger.d.ts +1 -1
  9. package/dist/development/route-inspector.d.ts +2 -2
  10. package/dist/index.js +369 -2209
  11. package/dist/observability/integration.d.ts +1 -1
  12. package/dist/response/macros.d.ts +1 -1
  13. package/dist/router/index.d.ts +7 -7
  14. package/dist/router/router.d.ts +0 -32
  15. package/dist/testing/performance-testing.d.ts +2 -2
  16. package/dist/testing/test-client.d.ts +1 -1
  17. package/dist/testing/websocket-testing.d.ts +1 -1
  18. package/package.json +7 -7
  19. package/dist/chunk-1ahs68ys.js +0 -18
  20. package/dist/chunk-g3ybefhg.js +0 -1923
  21. package/src/auth.ts +0 -489
  22. package/src/cache/lru-cache.ts +0 -457
  23. package/src/cache/middleware-memoization.ts +0 -531
  24. package/src/cache/route-cache-warmer.ts +0 -486
  25. package/src/cache/sqlite-cache.ts +0 -783
  26. package/src/cache/streaming-cache.ts +0 -572
  27. package/src/cli/colors.ts +0 -29
  28. package/src/cli/index.ts +0 -287
  29. package/src/cli/middleware.ts +0 -291
  30. package/src/cli/openapi.ts +0 -407
  31. package/src/cli/router.ts +0 -188
  32. package/src/cli/routes.ts +0 -265
  33. package/src/cli/utils.ts +0 -531
  34. package/src/cli.ts +0 -5
  35. package/src/config.ts +0 -322
  36. package/src/container/container.ts +0 -786
  37. package/src/container/contextual-binding.ts +0 -606
  38. package/src/container/decorators.ts +0 -368
  39. package/src/container/index.ts +0 -49
  40. package/src/container/service-provider.ts +0 -596
  41. package/src/development/hot-reload.ts +0 -673
  42. package/src/development/index.ts +0 -499
  43. package/src/development/performance-profiler.ts +0 -717
  44. package/src/development/route-debugger.ts +0 -527
  45. package/src/development/route-inspector.ts +0 -749
  46. package/src/development/typescript-utilities.ts +0 -682
  47. package/src/docs.ts +0 -397
  48. package/src/errors/circuit-breaker.ts +0 -733
  49. package/src/errors/error-handler.ts +0 -569
  50. package/src/errors/error-reporting.ts +0 -672
  51. package/src/errors/exceptions.ts +0 -536
  52. package/src/errors/graceful-degradation.ts +0 -623
  53. package/src/errors/index.ts +0 -21
  54. package/src/errors/router-errors.ts +0 -632
  55. package/src/file-serving/static-files.ts +0 -718
  56. package/src/index.ts +0 -66
  57. package/src/middleware/auth.ts +0 -220
  58. package/src/middleware/content_security_policy.ts +0 -215
  59. package/src/middleware/cors.ts +0 -142
  60. package/src/middleware/csrf.ts +0 -166
  61. package/src/middleware/ddos_protection.ts +0 -279
  62. package/src/middleware/file_security.ts +0 -191
  63. package/src/middleware/file_upload.ts +0 -275
  64. package/src/middleware/helmet.ts +0 -268
  65. package/src/middleware/index.ts +0 -117
  66. package/src/middleware/input_validation.ts +0 -453
  67. package/src/middleware/json_body.ts +0 -37
  68. package/src/middleware/performance_alerting.ts +0 -540
  69. package/src/middleware/performance_dashboard.ts +0 -661
  70. package/src/middleware/performance_monitor.ts +0 -945
  71. package/src/middleware/pipeline.ts +0 -489
  72. package/src/middleware/rate_limit.ts +0 -251
  73. package/src/middleware/request_id.ts +0 -36
  74. package/src/middleware/request_signing.ts +0 -638
  75. package/src/middleware/request_tracer.ts +0 -653
  76. package/src/middleware/response_cache.ts +0 -778
  77. package/src/middleware/security.ts +0 -482
  78. package/src/middleware/security_suite.ts +0 -257
  79. package/src/middleware/session.ts +0 -91
  80. package/src/model-binding/index.ts +0 -18
  81. package/src/model-binding/model-middleware.ts +0 -425
  82. package/src/model-binding/model-registry.ts +0 -550
  83. package/src/model-binding.ts +0 -370
  84. package/src/model-resolver-factory.ts +0 -106
  85. package/src/observability/correlation.ts +0 -691
  86. package/src/observability/health-checks.ts +0 -729
  87. package/src/observability/index.ts +0 -184
  88. package/src/observability/integration.ts +0 -548
  89. package/src/observability/metrics.ts +0 -753
  90. package/src/observability/tracing.ts +0 -638
  91. package/src/optimization/bun-utilities.ts +0 -778
  92. package/src/query-builder-integration.ts +0 -137
  93. package/src/request/context.ts +0 -109
  94. package/src/request/enhanced-request.ts +0 -876
  95. package/src/request/macros.ts +0 -822
  96. package/src/response/macros.ts +0 -665
  97. package/src/response/response-factory.ts +0 -596
  98. package/src/router/api-routes.ts +0 -243
  99. package/src/router/file-based-routing.ts +0 -697
  100. package/src/router/file-streaming.ts +0 -383
  101. package/src/router/fluent-routing.ts +0 -866
  102. package/src/router/group-organization.ts +0 -229
  103. package/src/router/handler-resolver.ts +0 -327
  104. package/src/router/http-methods.ts +0 -407
  105. package/src/router/index.ts +0 -193
  106. package/src/router/middleware-groups.ts +0 -222
  107. package/src/router/middleware-integration.ts +0 -399
  108. package/src/router/middleware.ts +0 -234
  109. package/src/router/model-binding.ts +0 -215
  110. package/src/router/optimized-route-matching.ts +0 -302
  111. package/src/router/route-building.ts +0 -221
  112. package/src/router/route-compiler.ts +0 -691
  113. package/src/router/route-matching.ts +0 -368
  114. package/src/router/route-trie.ts +0 -450
  115. package/src/router/router.ts +0 -1616
  116. package/src/router/server.ts +0 -684
  117. package/src/router/validation-integration.ts +0 -449
  118. package/src/router/view-rendering.ts +0 -233
  119. package/src/router/websocket.ts +0 -100
  120. package/src/routing/route-caching.ts +0 -402
  121. package/src/routing/route-throttling.ts +0 -469
  122. package/src/routing/subdomain-routing.ts +0 -492
  123. package/src/session/database-store.ts +0 -109
  124. package/src/session/file-store.ts +0 -148
  125. package/src/session/index.ts +0 -244
  126. package/src/session/memory-store.ts +0 -88
  127. package/src/session/redis-store.ts +0 -93
  128. package/src/streaming/index.ts +0 -17
  129. package/src/streaming/sse-handler.ts +0 -482
  130. package/src/streaming/stream-handler.ts +0 -552
  131. package/src/testing/auth-testing.ts +0 -448
  132. package/src/testing/file-upload-testing.ts +0 -543
  133. package/src/testing/index.ts +0 -10
  134. package/src/testing/middleware-testing.ts +0 -322
  135. package/src/testing/model-binding-testing.ts +0 -645
  136. package/src/testing/performance-testing.ts +0 -738
  137. package/src/testing/test-client.ts +0 -310
  138. package/src/testing/test-request.ts +0 -315
  139. package/src/testing/test-response.ts +0 -332
  140. package/src/testing/types.ts +0 -224
  141. package/src/testing/websocket-testing.ts +0 -590
  142. package/src/types/controller-types.ts +0 -385
  143. package/src/types/core.ts +0 -683
  144. package/src/types/middleware-types.ts +0 -422
  145. package/src/types/request-response-augmentation.ts +0 -489
  146. package/src/types/route-inference.ts +0 -372
  147. package/src/types.ts +0 -2123
  148. package/src/url.ts +0 -131
  149. package/src/utils/index.ts +0 -1
  150. package/src/utils/query-preservation.ts +0 -201
  151. package/src/utils.ts +0 -395
  152. package/src/validation/validator.ts +0 -688
  153. package/src/websocket/clustering.ts +0 -769
package/src/index.ts DELETED
@@ -1,66 +0,0 @@
1
- /**
2
- * @stacksjs/bun-router — a fast, type-safe router for Bun.
3
- *
4
- * Main entry point. The most commonly used exports:
5
- * - `Router` — the primary router class (from `./router`)
6
- * - `FluentRouter` / `router` — chainable routing API
7
- * - `Auth`, `JWT`, `ApiKeyManager`, `OAuth2Helper` — auth helpers
8
- * - `Container`, `createContainer` — dependency-injection container
9
- * - `url()` / `registerNamedRoute()` — named-route URL generation
10
- * - `Errors` — namespaced error/exception types
11
- */
12
-
13
- // Authentication (JWT, API keys, OAuth2)
14
- export { default as Auth } from './auth'
15
- export * from './auth'
16
-
17
- // Runtime configuration
18
- export * from './config'
19
-
20
- // Dependency-injection container. Exported by name — the decorator module
21
- // (`Get`, `Post`, `Controller`, …) would collide with router exports if
22
- // star-exported; import those from `@stacksjs/bun-router/container/decorators`.
23
- export {
24
- BindingBuilder,
25
- Container,
26
- createContainer,
27
- getContainer,
28
- setContainer,
29
- } from './container/container'
30
- export type { Binding, BindingScope, ContainerOptions, ResolutionContext, Token } from './container/container'
31
- export { ContextualContainer, EnvironmentManager } from './container/contextual-binding'
32
- export { DefaultServiceProviderManager } from './container/service-provider'
33
-
34
- // Error types and helpers (namespaced — many generic names)
35
- export * as Errors from './errors'
36
-
37
- // Built-in middleware (CORS, CSRF, sessions, rate limiting, …)
38
- export * from './middleware'
39
-
40
- // Request context (AsyncLocalStorage-backed current request; lazy — see enableRequestContext)
41
- export { enableRequestContext, getCurrentRequest, request, runWithRequest, setCurrentRequest } from './request/context'
42
-
43
- // Session stores and manager
44
- export * from './session'
45
-
46
- // Response factory helpers
47
- export * from './response/response-factory'
48
-
49
- // The Router class and routing features (fluent API, throttling, caching, …)
50
- export * from './router'
51
- export { MiddlewareGroupRegistry, middlewareGroups } from './router/middleware-groups'
52
-
53
- // Testing utilities
54
- export * from './testing'
55
-
56
- // Public types
57
- export * from './types'
58
-
59
- // Validation (Laravel-style rules, custom rules, middleware)
60
- export * from './validation/validator'
61
-
62
- // Named-route URL generation
63
- export { clearNamedRoutes, getNamedRoutePath, getNamedRoutes, registerNamedRoute, url, type UrlOptions } from './url'
64
-
65
- // Path/template utilities
66
- export * from './utils'
@@ -1,220 +0,0 @@
1
- import type { EnhancedRequest, Middleware, NextFunction } from '../types'
2
-
3
- export interface AuthOptions {
4
- /**
5
- * Authentication type to use
6
- */
7
- type: 'basic' | 'bearer' | 'api-key' | 'oauth2' | 'jwt' | 'custom'
8
-
9
- /**
10
- * Function to validate credentials
11
- */
12
- validator: (credentials: any, req: EnhancedRequest) => Promise<boolean> | boolean
13
-
14
- /**
15
- * Extract user information after successful authentication
16
- */
17
- userExtractor?: (credentials: any, req: EnhancedRequest) => Promise<any> | any
18
-
19
- /**
20
- * Where to look for auth information
21
- */
22
- source?: 'header' | 'query' | 'cookie'
23
-
24
- /**
25
- * Key name for the source (header name, query param, cookie name)
26
- */
27
- key?: string
28
-
29
- /**
30
- * Realm for Basic authentication
31
- */
32
- realm?: string
33
-
34
- /**
35
- * Custom credentials extractor
36
- */
37
- credentialsExtractor?: (req: EnhancedRequest) => Promise<any> | any
38
-
39
- /**
40
- * Whether to attach user info to request
41
- */
42
- attachUser?: boolean
43
- }
44
-
45
- /**
46
- * Extracts and decodes Basic Auth credentials from Authorization header
47
- */
48
- export function extractBasicAuth(authHeader: string): { username: string, password: string } | null {
49
- if (!authHeader || !authHeader.startsWith('Basic ')) {
50
- return null
51
- }
52
-
53
- try {
54
- const base64Credentials = authHeader.split(' ')[1]
55
- const credentials = atob(base64Credentials)
56
- const [username, password] = credentials.split(':')
57
-
58
- return { username, password }
59
- }
60
- catch (error) {
61
- void error
62
-
63
- return null
64
- }
65
- }
66
-
67
- /**
68
- * Extracts Bearer token from Authorization header
69
- */
70
- export function extractBearerToken(authHeader: string): string | null {
71
- if (!authHeader || !authHeader.startsWith('Bearer ')) {
72
- return null
73
- }
74
-
75
- return authHeader.split(' ')[1]
76
- }
77
-
78
- /**
79
- * Extracts API key from various sources
80
- */
81
- export function extractApiKey(req: EnhancedRequest, source: 'header' | 'query' | 'cookie' = 'header', key: string = 'X-API-Key'): string | null {
82
- switch (source) {
83
- case 'header':
84
- return req.headers.get(key) || null
85
- case 'query':
86
- return new URL(req.url).searchParams.get(key)
87
- case 'cookie':
88
- // Handle both Map-like cookies (with .get()) and plain object cookies
89
- if (req.cookies) {
90
- if (typeof req.cookies.get === 'function') {
91
- return req.cookies.get(key) || null
92
- }
93
- return (req.cookies as unknown as Record<string, string>)[key] || null
94
- }
95
- return null
96
- default:
97
- return null
98
- }
99
- }
100
-
101
- export default class AuthMiddleware implements Middleware {
102
- private options: AuthOptions
103
-
104
- constructor(options: AuthOptions) {
105
- this.options = {
106
- source: 'header',
107
- key: 'Authorization',
108
- attachUser: true,
109
- ...options,
110
- }
111
- }
112
-
113
- async handle(req: EnhancedRequest, next: NextFunction): Promise<Response> {
114
- let credentials: any = null
115
-
116
- // Extract credentials based on auth type
117
- if (this.options.credentialsExtractor) {
118
- credentials = await this.options.credentialsExtractor(req)
119
- }
120
- else {
121
- switch (this.options.type) {
122
- case 'basic': {
123
- const authHeader = req.headers.get(this.options.key || 'Authorization')
124
- credentials = extractBasicAuth(authHeader || '')
125
- break
126
- }
127
- case 'bearer':
128
- case 'jwt': {
129
- const authHeader = req.headers.get(this.options.key || 'Authorization')
130
- credentials = extractBearerToken(authHeader || '')
131
- break
132
- }
133
- case 'api-key': {
134
- credentials = extractApiKey(req, this.options.source, this.options.key)
135
- break
136
- }
137
- case 'oauth2': {
138
- // OAuth tokens are typically sent as Bearer tokens
139
- const authHeader = req.headers.get(this.options.key || 'Authorization')
140
- credentials = extractBearerToken(authHeader || '')
141
- break
142
- }
143
- }
144
- }
145
-
146
- // If no credentials were found, return 401
147
- if (!credentials) {
148
- const headers: Record<string, string> = {}
149
-
150
- // For Basic auth, include WWW-Authenticate header
151
- if (this.options.type === 'basic') {
152
- const realm = this.options.realm || 'Secure Area'
153
- headers['WWW-Authenticate'] = `Basic realm="${realm}"`
154
- }
155
-
156
- return new Response('Unauthorized', {
157
- status: 401,
158
- headers,
159
- })
160
- }
161
-
162
- // Validate credentials
163
- const isValid = await this.options.validator(credentials, req)
164
-
165
- if (!isValid) {
166
- return new Response('Unauthorized', { status: 401 })
167
- }
168
-
169
- // If user extractor provided and attachUser is true, attach user to request
170
- if (this.options.userExtractor && this.options.attachUser) {
171
- const user = await this.options.userExtractor(credentials, req)
172
- ;(req as any).user = user
173
- }
174
-
175
- // Authentication successful, continue to next middleware or route handler
176
- const response = await next()
177
- return response || new Response('Not Found', { status: 404 })
178
- }
179
- }
180
-
181
- // Convenience factory functions for common auth types
182
- export function basicAuth(validator: (credentials: { username: string, password: string }, req: EnhancedRequest) => Promise<boolean> | boolean, options: Partial<AuthOptions> = {}): AuthMiddleware {
183
- return new AuthMiddleware({
184
- type: 'basic',
185
- validator,
186
- ...options,
187
- })
188
- }
189
-
190
- export function bearerAuth(validator: (token: string, req: EnhancedRequest) => Promise<boolean> | boolean, options: Partial<AuthOptions> = {}): AuthMiddleware {
191
- return new AuthMiddleware({
192
- type: 'bearer',
193
- validator,
194
- ...options,
195
- })
196
- }
197
-
198
- export function jwtAuth(validator: (token: string, req: EnhancedRequest) => Promise<boolean> | boolean, options: Partial<AuthOptions> = {}): AuthMiddleware {
199
- return new AuthMiddleware({
200
- type: 'jwt',
201
- validator,
202
- ...options,
203
- })
204
- }
205
-
206
- export function apiKeyAuth(validator: (apiKey: string, req: EnhancedRequest) => Promise<boolean> | boolean, options: Partial<AuthOptions> = {}): AuthMiddleware {
207
- return new AuthMiddleware({
208
- type: 'api-key',
209
- validator,
210
- ...options,
211
- })
212
- }
213
-
214
- export function oauth2Auth(validator: (token: string, req: EnhancedRequest) => Promise<boolean> | boolean, options: Partial<AuthOptions> = {}): AuthMiddleware {
215
- return new AuthMiddleware({
216
- type: 'oauth2',
217
- validator,
218
- ...options,
219
- })
220
- }
@@ -1,215 +0,0 @@
1
- import type { EnhancedRequest, NextFunction } from '../types'
2
- import { config } from '../config'
3
-
4
- export interface CSPDirectives {
5
- 'default-src'?: string[]
6
- 'script-src'?: string[]
7
- 'script-src-elem'?: string[]
8
- 'script-src-attr'?: string[]
9
- 'style-src'?: string[]
10
- 'style-src-elem'?: string[]
11
- 'style-src-attr'?: string[]
12
- 'img-src'?: string[]
13
- 'font-src'?: string[]
14
- 'connect-src'?: string[]
15
- 'media-src'?: string[]
16
- 'object-src'?: string[]
17
- 'child-src'?: string[]
18
- 'frame-src'?: string[]
19
- 'worker-src'?: string[]
20
- 'frame-ancestors'?: string[]
21
- 'form-action'?: string[]
22
- 'base-uri'?: string[]
23
- 'manifest-src'?: string[]
24
- 'prefetch-src'?: string[]
25
- 'navigate-to'?: string[]
26
- 'report-uri'?: string[]
27
- 'report-to'?: string[]
28
- 'require-trusted-types-for'?: string[]
29
- 'trusted-types'?: string[]
30
- 'upgrade-insecure-requests'?: boolean
31
- 'block-all-mixed-content'?: boolean
32
- 'plugin-types'?: string[]
33
- 'sandbox'?: string[]
34
- 'disown-opener'?: boolean
35
- }
36
-
37
- export interface CSPOptions {
38
- directives?: CSPDirectives
39
- reportOnly?: boolean
40
- reportUri?: string
41
- reportTo?: string
42
- useNonces?: boolean
43
- nonceLength?: number
44
- upgradeInsecureRequests?: boolean
45
- blockAllMixedContent?: boolean
46
- }
47
-
48
- export default class ContentSecurityPolicy {
49
- private options: CSPOptions
50
- private nonces: Map<string, string> = new Map()
51
-
52
- constructor(options: CSPOptions = {}) {
53
- const cspConfig = config.server?.security?.helmet?.contentSecurityPolicy
54
- const defaultDirectives: CSPDirectives = {
55
- 'default-src': ['\'self\''],
56
- 'script-src': ['\'self\''],
57
- 'style-src': ['\'self\''],
58
- 'img-src': ['\'self\'', 'data:', 'https:'],
59
- 'font-src': ['\'self\''],
60
- 'connect-src': ['\'self\''],
61
- 'media-src': ['\'self\''],
62
- 'object-src': ['\'none\''],
63
- 'child-src': ['\'self\''],
64
- 'worker-src': ['\'self\''],
65
- 'frame-ancestors': ['\'none\''],
66
- 'form-action': ['\'self\''],
67
- 'base-uri': ['\'self\''],
68
- 'manifest-src': ['\'self\''],
69
- }
70
-
71
- this.options = {
72
- directives: options.directives || (typeof cspConfig === 'object' && cspConfig.directives) || defaultDirectives,
73
- reportOnly: options.reportOnly ?? (typeof cspConfig === 'object' && cspConfig.reportOnly) ?? false,
74
- reportUri: options.reportUri,
75
- reportTo: options.reportTo,
76
- useNonces: options.useNonces ?? false,
77
- nonceLength: options.nonceLength ?? 16,
78
- upgradeInsecureRequests: options.upgradeInsecureRequests ?? true,
79
- blockAllMixedContent: options.blockAllMixedContent ?? false,
80
- }
81
- }
82
-
83
- private generateNonce(): string {
84
- const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
85
- let result = ''
86
- for (let i = 0; i < (this.options.nonceLength || 16); i++) {
87
- result += chars.charAt(Math.floor(Math.random() * chars.length))
88
- }
89
- return result
90
- }
91
-
92
- private buildCSPHeader(): string {
93
- const directives: string[] = []
94
- const dirs = this.options.directives || {}
95
-
96
- // Handle regular directives
97
- for (const [directive, values] of Object.entries(dirs)) {
98
- if (directive === 'upgrade-insecure-requests' || directive === 'block-all-mixed-content' || directive === 'disown-opener') {
99
- if (values === true) {
100
- directives.push(directive.replace(/([A-Z])/g, '-$1').toLowerCase())
101
- }
102
- continue
103
- }
104
-
105
- if (Array.isArray(values) && values.length > 0) {
106
- const directiveName = directive.replace(/([A-Z])/g, '-$1').toLowerCase()
107
- directives.push(`${directiveName} ${values.join(' ')}`)
108
- }
109
- }
110
-
111
- // Add upgrade-insecure-requests if enabled
112
- if (this.options.upgradeInsecureRequests) {
113
- directives.push('upgrade-insecure-requests')
114
- }
115
-
116
- // Add block-all-mixed-content if enabled
117
- if (this.options.blockAllMixedContent) {
118
- directives.push('block-all-mixed-content')
119
- }
120
-
121
- // Add report-uri if specified
122
- if (this.options.reportUri) {
123
- directives.push(`report-uri ${this.options.reportUri}`)
124
- }
125
-
126
- // Add report-to if specified
127
- if (this.options.reportTo) {
128
- directives.push(`report-to ${this.options.reportTo}`)
129
- }
130
-
131
- return directives.join('; ')
132
- }
133
-
134
- async handle(req: EnhancedRequest, next: NextFunction): Promise<Response> {
135
- const helmetConfig = (config.server?.security?.helmet || {}) as any
136
-
137
- // If CSP is disabled, continue to next middleware
138
- if (helmetConfig.contentSecurityPolicy === false) {
139
- const response = await next()
140
- return response || new Response('Not Found', { status: 404 })
141
- }
142
-
143
- // Generate nonce if needed
144
- let nonce: string | undefined
145
- if (this.options.useNonces) {
146
- nonce = this.generateNonce()
147
- this.nonces.set(req.url, nonce)
148
-
149
- // Add nonce to script-src and style-src if they exist
150
- if (this.options.directives?.['script-src']) {
151
- this.options.directives['script-src'] = [
152
- ...this.options.directives['script-src'],
153
- `'nonce-${nonce}'`,
154
- ]
155
- }
156
- if (this.options.directives?.['style-src']) {
157
- this.options.directives['style-src'] = [
158
- ...this.options.directives['style-src'],
159
- `'nonce-${nonce}'`,
160
- ]
161
- }
162
- }
163
-
164
- // Add nonce to request context for use in templates
165
- if (nonce) {
166
- req.nonce = nonce
167
- }
168
-
169
- const response = await next()
170
- if (!response) {
171
- return new Response('Not Found', { status: 404 })
172
- }
173
-
174
- const headers = new Headers(response.headers)
175
- const cspHeader = this.buildCSPHeader()
176
-
177
- if (cspHeader) {
178
- const headerName = this.options.reportOnly
179
- ? 'Content-Security-Policy-Report-Only'
180
- : 'Content-Security-Policy'
181
- headers.set(headerName, cspHeader)
182
- }
183
-
184
- // Set up reporting endpoint if specified
185
- if (this.options.reportTo) {
186
- headers.set('Report-To', JSON.stringify({
187
- group: this.options.reportTo,
188
- max_age: 10886400,
189
- endpoints: [{ url: this.options.reportUri || '/csp-report' }],
190
- }))
191
- }
192
-
193
- // Clean up nonce after use
194
- if (nonce) {
195
- this.nonces.delete(req.url)
196
- }
197
-
198
- return new Response(response.body, {
199
- status: response.status,
200
- statusText: response.statusText,
201
- headers,
202
- })
203
- }
204
-
205
- // Helper method to get nonce for templates
206
- getNonce(url: string): string | undefined {
207
- return this.nonces.get(url)
208
- }
209
- }
210
-
211
- // Factory function for easy use
212
- export function contentSecurityPolicy(options: CSPOptions = {}): (req: EnhancedRequest, next: NextFunction) => Promise<Response> {
213
- const instance = new ContentSecurityPolicy(options)
214
- return async (req: EnhancedRequest, next: NextFunction) => instance.handle(req, next)
215
- }
@@ -1,142 +0,0 @@
1
- import type { EnhancedRequest, NextFunction } from '../types'
2
- import { config } from '../config'
3
-
4
- // Default CORS headers.
5
- //
6
- // IMPORTANT: when credentials are NOT enabled it is safe to allow any origin
7
- // with `*`. The default config below deliberately keeps credentials disabled.
8
- // The `*` + `Allow-Credentials: true` combination is forbidden by the CORS
9
- // spec and is dangerous (it lets any site read credentialed responses), so the
10
- // logic in `getCorsHeaders` guarantees that combination can never be emitted.
11
- const DEFAULT_CORS_HEADERS: Record<string, string> = {
12
- 'Access-Control-Allow-Origin': '*',
13
- 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS',
14
- 'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Requested-With, Accept, Origin',
15
- 'Access-Control-Max-Age': '86400',
16
- }
17
-
18
- export default class Cors {
19
- /**
20
- * Resolve the `Access-Control-Allow-Origin` value for a request.
21
- *
22
- * Supports a string origin (`'*'` or an explicit origin) or an array of
23
- * allowed origins. For an allowlist, the request `Origin` is reflected back
24
- * only when it is present in the list. When credentials are enabled a literal
25
- * `*` is never returned (it would be both spec-violating and dangerous).
26
- */
27
- private resolveAllowOrigin(
28
- configuredOrigin: string | string[] | undefined,
29
- credentials: boolean,
30
- requestOrigin: string | null,
31
- ): { value: string | null, vary: boolean } {
32
- // Explicit allowlist: only reflect the request origin when it matches.
33
- if (Array.isArray(configuredOrigin)) {
34
- if (requestOrigin && configuredOrigin.includes(requestOrigin)) {
35
- return { value: requestOrigin, vary: true }
36
- }
37
- // Origin not allowed: do not emit an Allow-Origin header at all.
38
- return { value: null, vary: true }
39
- }
40
-
41
- const origin = configuredOrigin || '*'
42
-
43
- if (origin === '*') {
44
- // The `*` wildcard cannot be combined with credentials. When credentials
45
- // are enabled, reflect the specific request origin instead (which is the
46
- // only spec-compliant way to support credentialed cross-origin requests).
47
- if (credentials) {
48
- if (requestOrigin) {
49
- return { value: requestOrigin, vary: true }
50
- }
51
- // No request origin to reflect — omit the header rather than send `*`.
52
- return { value: null, vary: true }
53
- }
54
- return { value: '*', vary: false }
55
- }
56
-
57
- // A single explicit origin was configured.
58
- return { value: origin, vary: false }
59
- }
60
-
61
- private getCorsHeaders(req: EnhancedRequest): Record<string, string> {
62
- const corsConfig = config.server?.cors
63
- const requestOrigin = req.headers.get('origin')
64
-
65
- if (corsConfig && corsConfig.enabled) {
66
- const credentials = Boolean(corsConfig.credentials)
67
- const { value: allowOrigin, vary } = this.resolveAllowOrigin(corsConfig.origin, credentials, requestOrigin)
68
-
69
- const headers: Record<string, string> = {
70
- 'Access-Control-Allow-Methods': Array.isArray(corsConfig.methods) ? corsConfig.methods.join(', ') : DEFAULT_CORS_HEADERS['Access-Control-Allow-Methods'],
71
- 'Access-Control-Allow-Headers': Array.isArray(corsConfig.allowedHeaders) ? corsConfig.allowedHeaders.join(', ') : DEFAULT_CORS_HEADERS['Access-Control-Allow-Headers'],
72
- 'Access-Control-Max-Age': String(corsConfig.maxAge || 86400),
73
- }
74
-
75
- if (allowOrigin !== null) {
76
- headers['Access-Control-Allow-Origin'] = allowOrigin
77
- }
78
-
79
- // Only advertise credentials when an origin is actually allowed. Never
80
- // pair credentials with a `*` wildcard.
81
- if (credentials && allowOrigin !== null && allowOrigin !== '*') {
82
- headers['Access-Control-Allow-Credentials'] = 'true'
83
- }
84
-
85
- if (vary) {
86
- headers.Vary = 'Origin'
87
- }
88
-
89
- return headers
90
- }
91
-
92
- // No CORS config: fall back to the permissive (but credential-free) defaults.
93
- return { ...DEFAULT_CORS_HEADERS }
94
- }
95
-
96
- async handle(req: EnhancedRequest, next: NextFunction): Promise<Response> {
97
- const corsHeaders = this.getCorsHeaders(req)
98
-
99
- // Handle preflight OPTIONS request
100
- if (req.method === 'OPTIONS') {
101
- return new Response(null, { status: 204, headers: corsHeaders })
102
- }
103
-
104
- // Helper to add CORS headers to any response
105
- const addCorsHeaders = (response: Response): Response => {
106
- const newHeaders = new Headers(response.headers)
107
- Object.entries(corsHeaders).forEach(([key, value]) => {
108
- newHeaders.set(key, value)
109
- })
110
- return new Response(response.body, {
111
- status: response.status,
112
- statusText: response.statusText,
113
- headers: newHeaders,
114
- })
115
- }
116
-
117
- try {
118
- // For non-OPTIONS requests, add CORS headers to the response
119
- const response = await next()
120
- if (!response) {
121
- return new Response(JSON.stringify({ error: 'Not Found' }), {
122
- status: 404,
123
- headers: { ...corsHeaders, 'Content-Type': 'application/json' },
124
- })
125
- }
126
-
127
- // Add CORS headers to the response
128
- return addCorsHeaders(response)
129
- }
130
- catch (error) {
131
- // Error occurred - return error response WITH CORS headers
132
- console.error('[CORS] Error in middleware chain:', error)
133
- return new Response(JSON.stringify({
134
- error: 'Internal Server Error',
135
- message: error instanceof Error ? error.message : String(error),
136
- }), {
137
- status: 500,
138
- headers: { ...corsHeaders, 'Content-Type': 'application/json' },
139
- })
140
- }
141
- }
142
- }