@stacksjs/bun-router 0.0.17 → 0.0.18
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/dist/cli.js +1 -1
- package/dist/container/service-provider.d.ts +3 -3
- package/dist/development/performance-profiler.d.ts +3 -3
- package/dist/development/route-debugger.d.ts +1 -1
- package/dist/development/route-inspector.d.ts +2 -2
- package/dist/observability/integration.d.ts +1 -1
- package/dist/response/macros.d.ts +1 -1
- package/dist/router/index.d.ts +7 -7
- package/dist/router/router.d.ts +0 -32
- package/dist/testing/performance-testing.d.ts +2 -2
- package/dist/testing/test-client.d.ts +1 -1
- package/dist/testing/websocket-testing.d.ts +1 -1
- package/package.json +2 -3
- package/src/auth.ts +0 -489
- 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 -786
- package/src/container/contextual-binding.ts +0 -606
- package/src/container/decorators.ts +0 -368
- package/src/container/index.ts +0 -49
- 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 -733
- 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 -623
- package/src/errors/index.ts +0 -21
- package/src/errors/router-errors.ts +0 -632
- package/src/file-serving/static-files.ts +0 -718
- package/src/index.ts +0 -66
- package/src/middleware/auth.ts +0 -220
- package/src/middleware/content_security_policy.ts +0 -215
- package/src/middleware/cors.ts +0 -142
- package/src/middleware/csrf.ts +0 -166
- package/src/middleware/ddos_protection.ts +0 -279
- package/src/middleware/file_security.ts +0 -191
- package/src/middleware/file_upload.ts +0 -275
- package/src/middleware/helmet.ts +0 -268
- package/src/middleware/index.ts +0 -117
- package/src/middleware/input_validation.ts +0 -453
- package/src/middleware/json_body.ts +0 -37
- package/src/middleware/performance_alerting.ts +0 -540
- package/src/middleware/performance_dashboard.ts +0 -661
- package/src/middleware/performance_monitor.ts +0 -945
- package/src/middleware/pipeline.ts +0 -489
- package/src/middleware/rate_limit.ts +0 -251
- package/src/middleware/request_id.ts +0 -36
- package/src/middleware/request_signing.ts +0 -638
- package/src/middleware/request_tracer.ts +0 -653
- package/src/middleware/response_cache.ts +0 -778
- 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 -109
- package/src/request/enhanced-request.ts +0 -876
- package/src/request/macros.ts +0 -822
- package/src/response/macros.ts +0 -665
- package/src/response/response-factory.ts +0 -596
- package/src/router/api-routes.ts +0 -243
- package/src/router/file-based-routing.ts +0 -697
- package/src/router/file-streaming.ts +0 -383
- package/src/router/fluent-routing.ts +0 -866
- package/src/router/group-organization.ts +0 -229
- package/src/router/handler-resolver.ts +0 -327
- package/src/router/http-methods.ts +0 -407
- package/src/router/index.ts +0 -193
- package/src/router/middleware-groups.ts +0 -222
- package/src/router/middleware-integration.ts +0 -399
- package/src/router/middleware.ts +0 -234
- package/src/router/model-binding.ts +0 -215
- package/src/router/optimized-route-matching.ts +0 -302
- package/src/router/route-building.ts +0 -221
- package/src/router/route-compiler.ts +0 -691
- package/src/router/route-matching.ts +0 -368
- package/src/router/route-trie.ts +0 -450
- package/src/router/router.ts +0 -1616
- package/src/router/server.ts +0 -684
- package/src/router/validation-integration.ts +0 -449
- 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 -448
- 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 -422
- package/src/types/request-response-augmentation.ts +0 -489
- package/src/types/route-inference.ts +0 -372
- package/src/types.ts +0 -2123
- package/src/url.ts +0 -131
- package/src/utils/index.ts +0 -1
- package/src/utils/query-preservation.ts +0 -201
- package/src/utils.ts +0 -395
- package/src/validation/validator.ts +0 -688
- package/src/websocket/clustering.ts +0 -769
|
@@ -1,275 +0,0 @@
|
|
|
1
|
-
import type { EnhancedRequest, NextFunction } from '../types'
|
|
2
|
-
import type { FileSecurityConfig } from './file_security'
|
|
3
|
-
import { randomUUID } from 'node:crypto'
|
|
4
|
-
import { existsSync, mkdirSync, writeFileSync } from 'node:fs'
|
|
5
|
-
import { basename, extname, join } from 'node:path'
|
|
6
|
-
import FileSecurity from './file_security'
|
|
7
|
-
|
|
8
|
-
export interface UploadedFile {
|
|
9
|
-
fieldName: string
|
|
10
|
-
originalName: string
|
|
11
|
-
filename: string
|
|
12
|
-
path: string
|
|
13
|
-
size: number
|
|
14
|
-
mimetype: string
|
|
15
|
-
buffer: ArrayBuffer
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface FileUploadConfig extends FileSecurityConfig {
|
|
19
|
-
destination?: string
|
|
20
|
-
maxFileSize?: number // in bytes
|
|
21
|
-
maxFiles?: number
|
|
22
|
-
allowedMimeTypes?: string[]
|
|
23
|
-
allowedExtensions?: string[]
|
|
24
|
-
preserveOriginalName?: boolean
|
|
25
|
-
generateUniqueFilename?: boolean
|
|
26
|
-
createDestination?: boolean
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export interface FileUploadOptions extends FileUploadConfig {
|
|
30
|
-
fieldName?: string | string[]
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export default class FileUpload {
|
|
34
|
-
private config: Required<FileUploadConfig>
|
|
35
|
-
|
|
36
|
-
private fileSecurity: FileSecurity
|
|
37
|
-
|
|
38
|
-
constructor(config: FileUploadConfig = {}) {
|
|
39
|
-
this.config = {
|
|
40
|
-
destination: config.destination || './uploads',
|
|
41
|
-
maxFileSize: config.maxFileSize || 10 * 1024 * 1024, // 10MB default
|
|
42
|
-
maxFiles: config.maxFiles || 10,
|
|
43
|
-
allowedMimeTypes: config.allowedMimeTypes || [],
|
|
44
|
-
allowedExtensions: config.allowedExtensions || [],
|
|
45
|
-
preserveOriginalName: config.preserveOriginalName ?? false,
|
|
46
|
-
generateUniqueFilename: config.generateUniqueFilename ?? true,
|
|
47
|
-
createDestination: config.createDestination ?? true,
|
|
48
|
-
scanForMalware: config.scanForMalware ?? false,
|
|
49
|
-
checkMagicNumbers: config.checkMagicNumbers ?? true,
|
|
50
|
-
sanitizeFilenames: config.sanitizeFilenames ?? true,
|
|
51
|
-
preventExecutableUploads: config.preventExecutableUploads ?? true,
|
|
52
|
-
maxFilenameLength: config.maxFilenameLength ?? 255,
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
this.fileSecurity = new FileSecurity(config)
|
|
56
|
-
|
|
57
|
-
// Create destination directory if it doesn't exist
|
|
58
|
-
if (this.config.createDestination && !existsSync(this.config.destination)) {
|
|
59
|
-
mkdirSync(this.config.destination, { recursive: true })
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
async handle(req: EnhancedRequest, next: NextFunction): Promise<Response> {
|
|
64
|
-
const contentType = req.headers.get('content-type')
|
|
65
|
-
|
|
66
|
-
if (!contentType || !contentType.includes('multipart/form-data')) {
|
|
67
|
-
const response = await next()
|
|
68
|
-
return response || new Response('Not Found', { status: 404 })
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
try {
|
|
72
|
-
const formData = await req.formData()
|
|
73
|
-
const uploadedFiles: UploadedFile[] = []
|
|
74
|
-
const fields: Record<string, any> = {}
|
|
75
|
-
|
|
76
|
-
for (const [key, value] of (formData as any).entries()) {
|
|
77
|
-
if (value instanceof File) {
|
|
78
|
-
// Validate file count
|
|
79
|
-
if (uploadedFiles.length >= this.config.maxFiles) {
|
|
80
|
-
return this.createErrorResponse(`Maximum ${this.config.maxFiles} files allowed`, 400)
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Validate file size
|
|
84
|
-
if (value.size > this.config.maxFileSize) {
|
|
85
|
-
return this.createErrorResponse(
|
|
86
|
-
`File ${value.name} exceeds maximum size of ${this.formatBytes(this.config.maxFileSize)}`,
|
|
87
|
-
413,
|
|
88
|
-
)
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Validate MIME type
|
|
92
|
-
if (this.config.allowedMimeTypes.length > 0) {
|
|
93
|
-
const baseType = value.type.split(';')[0] // Remove charset info
|
|
94
|
-
if (!this.config.allowedMimeTypes.includes(baseType)) {
|
|
95
|
-
return this.createErrorResponse(
|
|
96
|
-
`File type ${baseType} not allowed. Allowed types: ${this.config.allowedMimeTypes.join(', ')}`,
|
|
97
|
-
415,
|
|
98
|
-
)
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Validate file extension
|
|
103
|
-
const fileExtension = extname(value.name).toLowerCase()
|
|
104
|
-
if (this.config.allowedExtensions.length > 0 && !this.config.allowedExtensions.includes(fileExtension)) {
|
|
105
|
-
return this.createErrorResponse(
|
|
106
|
-
`File extension ${fileExtension} not allowed. Allowed extensions: ${this.config.allowedExtensions.join(', ')}`,
|
|
107
|
-
415,
|
|
108
|
-
)
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// Generate filename
|
|
112
|
-
const filename = this.generateFilename(value.name)
|
|
113
|
-
const filePath = join(this.config.destination, filename)
|
|
114
|
-
|
|
115
|
-
// Convert file to buffer and save
|
|
116
|
-
const buffer = await value.arrayBuffer()
|
|
117
|
-
|
|
118
|
-
const uploadedFile: UploadedFile = {
|
|
119
|
-
fieldName: key,
|
|
120
|
-
originalName: value.name,
|
|
121
|
-
filename,
|
|
122
|
-
path: filePath,
|
|
123
|
-
size: value.size,
|
|
124
|
-
mimetype: value.type,
|
|
125
|
-
buffer,
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Validate file security
|
|
129
|
-
const securityCheck = this.fileSecurity.validateFile(uploadedFile)
|
|
130
|
-
if (!securityCheck.isValid) {
|
|
131
|
-
return this.createErrorResponse(
|
|
132
|
-
`Security validation failed: ${securityCheck.errors.join(', ')}`,
|
|
133
|
-
400,
|
|
134
|
-
)
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
writeFileSync(filePath, new Uint8Array(buffer))
|
|
138
|
-
uploadedFiles.push(uploadedFile)
|
|
139
|
-
}
|
|
140
|
-
else {
|
|
141
|
-
// Handle regular form fields
|
|
142
|
-
if (fields[key]) {
|
|
143
|
-
// Convert to array if multiple values
|
|
144
|
-
if (Array.isArray(fields[key])) {
|
|
145
|
-
fields[key].push(value)
|
|
146
|
-
}
|
|
147
|
-
else {
|
|
148
|
-
fields[key] = [fields[key], value]
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
else {
|
|
152
|
-
fields[key] = value
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Attach files and fields to request
|
|
158
|
-
Object.defineProperty(req, 'files', {
|
|
159
|
-
value: uploadedFiles,
|
|
160
|
-
writable: true,
|
|
161
|
-
enumerable: true,
|
|
162
|
-
configurable: true,
|
|
163
|
-
})
|
|
164
|
-
|
|
165
|
-
// Typed lookup by form field name. Mirrors Laravel's
|
|
166
|
-
// `$request->file('avatar')` ergonomics: returns the matching
|
|
167
|
-
// UploadedFile or null. See stacksjs/stacks#1856.
|
|
168
|
-
Object.defineProperty(req, 'file', {
|
|
169
|
-
value: (fieldName: string): UploadedFile | null =>
|
|
170
|
-
uploadedFiles.find(f => f.fieldName === fieldName) ?? null,
|
|
171
|
-
writable: true,
|
|
172
|
-
enumerable: false,
|
|
173
|
-
configurable: true,
|
|
174
|
-
})
|
|
175
|
-
|
|
176
|
-
Object.defineProperty(req, 'formBody', {
|
|
177
|
-
value: fields,
|
|
178
|
-
writable: true,
|
|
179
|
-
enumerable: true,
|
|
180
|
-
configurable: true,
|
|
181
|
-
})
|
|
182
|
-
|
|
183
|
-
const response = await next()
|
|
184
|
-
return response || new Response('Not Found', { status: 404 })
|
|
185
|
-
}
|
|
186
|
-
catch (error) {
|
|
187
|
-
return this.createErrorResponse(
|
|
188
|
-
`File upload failed: ${error instanceof Error ? error.message : 'Unknown error'}`,
|
|
189
|
-
500,
|
|
190
|
-
)
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
private generateFilename(originalName: string): string {
|
|
195
|
-
// Always sanitize the filename first
|
|
196
|
-
const sanitizedName = this.fileSecurity.getSafeFilename(originalName)
|
|
197
|
-
|
|
198
|
-
if (this.config.preserveOriginalName && !this.config.generateUniqueFilename) {
|
|
199
|
-
return sanitizedName
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
const extension = extname(sanitizedName)
|
|
203
|
-
const baseName = basename(sanitizedName, extension)
|
|
204
|
-
|
|
205
|
-
if (this.config.generateUniqueFilename) {
|
|
206
|
-
const uuid = randomUUID()
|
|
207
|
-
return this.config.preserveOriginalName
|
|
208
|
-
? `${baseName}_${uuid}${extension}`
|
|
209
|
-
: `${uuid}${extension}`
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
return sanitizedName
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
private formatBytes(bytes: number): string {
|
|
216
|
-
if (bytes === 0)
|
|
217
|
-
return '0 Bytes'
|
|
218
|
-
const k = 1024
|
|
219
|
-
const sizes = ['Bytes', 'KB', 'MB', 'GB']
|
|
220
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
|
221
|
-
return `${Number.parseFloat((bytes / k ** i).toFixed(2))} ${sizes[i]}`
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
private createErrorResponse(message: string, status: number): Response {
|
|
225
|
-
return new Response(
|
|
226
|
-
JSON.stringify({ error: message }),
|
|
227
|
-
{
|
|
228
|
-
status,
|
|
229
|
-
headers: { 'Content-Type': 'application/json' },
|
|
230
|
-
},
|
|
231
|
-
)
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// Factory function for easier usage
|
|
236
|
-
export function fileUpload(config: FileUploadConfig = {}): FileUpload {
|
|
237
|
-
return new FileUpload(config)
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// Specialized factory functions
|
|
241
|
-
export function singleFileUpload(_fieldName: string, config: FileUploadConfig = {}): FileUpload {
|
|
242
|
-
return new FileUpload({ ...config, maxFiles: 1 })
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
export function multipleFileUpload(maxFiles: number = 10, config: FileUploadConfig = {}): FileUpload {
|
|
246
|
-
return new FileUpload({ ...config, maxFiles })
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
export function imageUpload(config: FileUploadConfig = {}): FileUpload {
|
|
250
|
-
return new FileUpload({
|
|
251
|
-
...config,
|
|
252
|
-
allowedMimeTypes: [
|
|
253
|
-
'image/jpeg',
|
|
254
|
-
'image/jpg',
|
|
255
|
-
'image/png',
|
|
256
|
-
'image/gif',
|
|
257
|
-
'image/webp',
|
|
258
|
-
'image/svg+xml',
|
|
259
|
-
],
|
|
260
|
-
allowedExtensions: ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.svg'],
|
|
261
|
-
})
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
export function documentUpload(config: FileUploadConfig = {}): FileUpload {
|
|
265
|
-
return new FileUpload({
|
|
266
|
-
...config,
|
|
267
|
-
allowedMimeTypes: [
|
|
268
|
-
'application/pdf',
|
|
269
|
-
'application/msword',
|
|
270
|
-
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
271
|
-
'text/plain',
|
|
272
|
-
],
|
|
273
|
-
allowedExtensions: ['.pdf', '.doc', '.docx', '.txt'],
|
|
274
|
-
})
|
|
275
|
-
}
|
package/src/middleware/helmet.ts
DELETED
|
@@ -1,268 +0,0 @@
|
|
|
1
|
-
import type { EnhancedRequest, NextFunction } from '../types'
|
|
2
|
-
import { config } from '../config'
|
|
3
|
-
|
|
4
|
-
export interface HelmetOptions {
|
|
5
|
-
contentSecurityPolicy?: boolean | {
|
|
6
|
-
directives?: Record<string, string[]>
|
|
7
|
-
reportOnly?: boolean
|
|
8
|
-
reportUri?: string
|
|
9
|
-
upgradeInsecureRequests?: boolean
|
|
10
|
-
blockAllMixedContent?: boolean
|
|
11
|
-
}
|
|
12
|
-
crossOriginEmbedderPolicy?: boolean | {
|
|
13
|
-
policy?: 'require-corp' | 'credentialless'
|
|
14
|
-
}
|
|
15
|
-
crossOriginOpenerPolicy?: boolean | {
|
|
16
|
-
policy?: 'same-origin' | 'same-origin-allow-popups' | 'unsafe-none'
|
|
17
|
-
}
|
|
18
|
-
crossOriginResourcePolicy?: boolean | {
|
|
19
|
-
policy?: 'same-site' | 'same-origin' | 'cross-origin'
|
|
20
|
-
}
|
|
21
|
-
dnsPrefetchControl?: boolean | {
|
|
22
|
-
allow?: boolean
|
|
23
|
-
}
|
|
24
|
-
expectCt?: boolean | {
|
|
25
|
-
enforce?: boolean
|
|
26
|
-
maxAge?: number
|
|
27
|
-
reportUri?: string
|
|
28
|
-
}
|
|
29
|
-
frameguard?: boolean | {
|
|
30
|
-
action?: 'deny' | 'sameorigin'
|
|
31
|
-
domain?: string
|
|
32
|
-
}
|
|
33
|
-
hidePoweredBy?: boolean
|
|
34
|
-
hsts?: boolean | {
|
|
35
|
-
maxAge?: number
|
|
36
|
-
includeSubDomains?: boolean
|
|
37
|
-
preload?: boolean
|
|
38
|
-
}
|
|
39
|
-
ieNoOpen?: boolean
|
|
40
|
-
noSniff?: boolean
|
|
41
|
-
originAgentCluster?: boolean
|
|
42
|
-
permittedCrossDomainPolicies?: boolean | {
|
|
43
|
-
permittedPolicies?: 'none' | 'master-only' | 'by-content-type' | 'all'
|
|
44
|
-
}
|
|
45
|
-
referrerPolicy?: boolean | {
|
|
46
|
-
policy?: string | string[]
|
|
47
|
-
}
|
|
48
|
-
xssFilter?: boolean | {
|
|
49
|
-
setOnOldIE?: boolean
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export default class Helmet {
|
|
54
|
-
private options: HelmetOptions
|
|
55
|
-
|
|
56
|
-
constructor(options: HelmetOptions = {}) {
|
|
57
|
-
const helmetConfig = (config.server?.security?.helmet || {}) as any
|
|
58
|
-
|
|
59
|
-
this.options = {
|
|
60
|
-
contentSecurityPolicy: options.contentSecurityPolicy ?? helmetConfig.contentSecurityPolicy ?? {
|
|
61
|
-
directives: {
|
|
62
|
-
'default-src': ['\'self\''],
|
|
63
|
-
'script-src': ['\'self\'', '\'unsafe-inline\''],
|
|
64
|
-
'style-src': ['\'self\'', '\'unsafe-inline\''],
|
|
65
|
-
'img-src': ['\'self\'', 'data:', 'https:'],
|
|
66
|
-
'font-src': ['\'self\''],
|
|
67
|
-
'connect-src': ['\'self\''],
|
|
68
|
-
'media-src': ['\'self\''],
|
|
69
|
-
'object-src': ['\'none\''],
|
|
70
|
-
'child-src': ['\'self\''],
|
|
71
|
-
'worker-src': ['\'self\''],
|
|
72
|
-
'frame-ancestors': ['\'none\''],
|
|
73
|
-
'form-action': ['\'self\''],
|
|
74
|
-
'base-uri': ['\'self\''],
|
|
75
|
-
'manifest-src': ['\'self\''],
|
|
76
|
-
},
|
|
77
|
-
},
|
|
78
|
-
crossOriginEmbedderPolicy: options.crossOriginEmbedderPolicy ?? true,
|
|
79
|
-
crossOriginOpenerPolicy: options.crossOriginOpenerPolicy ?? true,
|
|
80
|
-
crossOriginResourcePolicy: options.crossOriginResourcePolicy ?? true,
|
|
81
|
-
dnsPrefetchControl: options.dnsPrefetchControl ?? true,
|
|
82
|
-
expectCt: options.expectCt ?? helmetConfig.expectCt ?? false,
|
|
83
|
-
frameguard: options.frameguard ?? {
|
|
84
|
-
action: helmetConfig.frameOptions?.toLowerCase() as 'deny' | 'sameorigin' || 'deny',
|
|
85
|
-
},
|
|
86
|
-
hidePoweredBy: options.hidePoweredBy ?? helmetConfig.hidePoweredBy ?? true,
|
|
87
|
-
hsts: options.hsts ?? helmetConfig.hsts ?? {
|
|
88
|
-
maxAge: 31536000, // 1 year
|
|
89
|
-
includeSubDomains: true,
|
|
90
|
-
preload: true,
|
|
91
|
-
},
|
|
92
|
-
ieNoOpen: options.ieNoOpen ?? true,
|
|
93
|
-
noSniff: options.noSniff ?? helmetConfig.noSniff ?? true,
|
|
94
|
-
originAgentCluster: options.originAgentCluster ?? true,
|
|
95
|
-
permittedCrossDomainPolicies: options.permittedCrossDomainPolicies ?? {
|
|
96
|
-
permittedPolicies: 'none',
|
|
97
|
-
},
|
|
98
|
-
referrerPolicy: options.referrerPolicy ?? helmetConfig.referrerPolicy ?? {
|
|
99
|
-
policy: 'strict-origin-when-cross-origin',
|
|
100
|
-
},
|
|
101
|
-
xssFilter: options.xssFilter ?? helmetConfig.xssFilter ?? true,
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
async handle(req: EnhancedRequest, next: NextFunction): Promise<Response> {
|
|
106
|
-
const helmetConfig = (config.server?.security?.helmet || {}) as any
|
|
107
|
-
|
|
108
|
-
// If helmet is disabled, continue to next middleware
|
|
109
|
-
if (helmetConfig.enabled === false) {
|
|
110
|
-
const response = await next()
|
|
111
|
-
return response || new Response('Not Found', { status: 404 })
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const response = await next()
|
|
115
|
-
if (!response) {
|
|
116
|
-
return new Response('Not Found', { status: 404 })
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const headers = new Headers(response.headers)
|
|
120
|
-
|
|
121
|
-
// Content Security Policy
|
|
122
|
-
if (this.options.contentSecurityPolicy) {
|
|
123
|
-
const csp = this.options.contentSecurityPolicy
|
|
124
|
-
if (typeof csp === 'object' && csp.directives) {
|
|
125
|
-
const directives = Object.entries(csp.directives)
|
|
126
|
-
.map(([key, values]) => `${key} ${values.join(' ')}`)
|
|
127
|
-
.join('; ')
|
|
128
|
-
|
|
129
|
-
const headerName = csp.reportOnly ? 'Content-Security-Policy-Report-Only' : 'Content-Security-Policy'
|
|
130
|
-
headers.set(headerName, directives)
|
|
131
|
-
|
|
132
|
-
if (csp.reportUri) {
|
|
133
|
-
headers.set('Report-To', JSON.stringify({
|
|
134
|
-
group: 'csp-endpoint',
|
|
135
|
-
max_age: 10886400,
|
|
136
|
-
endpoints: [{ url: csp.reportUri }],
|
|
137
|
-
}))
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// Cross-Origin Embedder Policy
|
|
143
|
-
if (this.options.crossOriginEmbedderPolicy) {
|
|
144
|
-
const coep = this.options.crossOriginEmbedderPolicy
|
|
145
|
-
const policy = typeof coep === 'object' ? coep.policy || 'require-corp' : 'require-corp'
|
|
146
|
-
headers.set('Cross-Origin-Embedder-Policy', policy)
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// Cross-Origin Opener Policy
|
|
150
|
-
if (this.options.crossOriginOpenerPolicy) {
|
|
151
|
-
const coop = this.options.crossOriginOpenerPolicy
|
|
152
|
-
const policy = typeof coop === 'object' ? coop.policy || 'same-origin' : 'same-origin'
|
|
153
|
-
headers.set('Cross-Origin-Opener-Policy', policy)
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Cross-Origin Resource Policy
|
|
157
|
-
if (this.options.crossOriginResourcePolicy) {
|
|
158
|
-
const corp = this.options.crossOriginResourcePolicy
|
|
159
|
-
const policy = typeof corp === 'object' ? corp.policy || 'same-origin' : 'same-origin'
|
|
160
|
-
headers.set('Cross-Origin-Resource-Policy', policy)
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// DNS Prefetch Control
|
|
164
|
-
if (this.options.dnsPrefetchControl) {
|
|
165
|
-
const dnsPrefetch = this.options.dnsPrefetchControl
|
|
166
|
-
const allow = typeof dnsPrefetch === 'object' ? dnsPrefetch.allow : false
|
|
167
|
-
headers.set('X-DNS-Prefetch-Control', allow ? 'on' : 'off')
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// Expect-CT
|
|
171
|
-
if (this.options.expectCt) {
|
|
172
|
-
const expectCt = this.options.expectCt
|
|
173
|
-
if (typeof expectCt === 'object') {
|
|
174
|
-
const parts = []
|
|
175
|
-
if (expectCt.enforce)
|
|
176
|
-
parts.push('enforce')
|
|
177
|
-
if (expectCt.maxAge)
|
|
178
|
-
parts.push(`max-age=${expectCt.maxAge}`)
|
|
179
|
-
if (expectCt.reportUri)
|
|
180
|
-
parts.push(`report-uri="${expectCt.reportUri}"`)
|
|
181
|
-
headers.set('Expect-CT', parts.join(', '))
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// X-Frame-Options
|
|
186
|
-
if (this.options.frameguard) {
|
|
187
|
-
const frameguard = this.options.frameguard
|
|
188
|
-
if (typeof frameguard === 'object') {
|
|
189
|
-
const action = frameguard.action || 'deny'
|
|
190
|
-
const value = action === 'sameorigin' && frameguard.domain
|
|
191
|
-
? `ALLOW-FROM ${frameguard.domain}`
|
|
192
|
-
: action.toUpperCase()
|
|
193
|
-
headers.set('X-Frame-Options', value)
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// Hide X-Powered-By
|
|
198
|
-
if (this.options.hidePoweredBy) {
|
|
199
|
-
headers.delete('X-Powered-By')
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// HTTP Strict Transport Security
|
|
203
|
-
if (this.options.hsts) {
|
|
204
|
-
const hsts = this.options.hsts
|
|
205
|
-
if (typeof hsts === 'object') {
|
|
206
|
-
const parts = [`max-age=${hsts.maxAge || 31536000}`]
|
|
207
|
-
if (hsts.includeSubDomains)
|
|
208
|
-
parts.push('includeSubDomains')
|
|
209
|
-
if (hsts.preload)
|
|
210
|
-
parts.push('preload')
|
|
211
|
-
headers.set('Strict-Transport-Security', parts.join('; '))
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// X-Download-Options
|
|
216
|
-
if (this.options.ieNoOpen) {
|
|
217
|
-
headers.set('X-Download-Options', 'noopen')
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
// X-Content-Type-Options
|
|
221
|
-
if (this.options.noSniff) {
|
|
222
|
-
headers.set('X-Content-Type-Options', 'nosniff')
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// Origin-Agent-Cluster
|
|
226
|
-
if (this.options.originAgentCluster) {
|
|
227
|
-
headers.set('Origin-Agent-Cluster', '?1')
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// X-Permitted-Cross-Domain-Policies
|
|
231
|
-
if (this.options.permittedCrossDomainPolicies) {
|
|
232
|
-
const crossDomain = this.options.permittedCrossDomainPolicies
|
|
233
|
-
const policy = typeof crossDomain === 'object'
|
|
234
|
-
? crossDomain.permittedPolicies || 'none'
|
|
235
|
-
: 'none'
|
|
236
|
-
headers.set('X-Permitted-Cross-Domain-Policies', policy)
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// Referrer-Policy
|
|
240
|
-
if (this.options.referrerPolicy) {
|
|
241
|
-
const referrer = this.options.referrerPolicy
|
|
242
|
-
if (typeof referrer === 'object') {
|
|
243
|
-
const policy = Array.isArray(referrer.policy)
|
|
244
|
-
? referrer.policy.join(', ')
|
|
245
|
-
: referrer.policy || 'strict-origin-when-cross-origin'
|
|
246
|
-
headers.set('Referrer-Policy', policy)
|
|
247
|
-
}
|
|
248
|
-
else {
|
|
249
|
-
headers.set('Referrer-Policy', 'strict-origin-when-cross-origin')
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// X-XSS-Protection
|
|
254
|
-
if (this.options.xssFilter) {
|
|
255
|
-
const xss = this.options.xssFilter
|
|
256
|
-
const value = typeof xss === 'object' && xss.setOnOldIE
|
|
257
|
-
? '1; mode=block'
|
|
258
|
-
: '0'
|
|
259
|
-
headers.set('X-XSS-Protection', value)
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
return new Response(response.body, {
|
|
263
|
-
status: response.status,
|
|
264
|
-
statusText: response.statusText,
|
|
265
|
-
headers,
|
|
266
|
-
})
|
|
267
|
-
}
|
|
268
|
-
}
|
package/src/middleware/index.ts
DELETED
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
import Auth, {
|
|
2
|
-
apiKeyAuth,
|
|
3
|
-
basicAuth,
|
|
4
|
-
bearerAuth,
|
|
5
|
-
extractApiKey,
|
|
6
|
-
extractBasicAuth,
|
|
7
|
-
extractBearerToken,
|
|
8
|
-
jwtAuth,
|
|
9
|
-
oauth2Auth,
|
|
10
|
-
} from './auth'
|
|
11
|
-
import Cors from './cors'
|
|
12
|
-
import Csrf from './csrf'
|
|
13
|
-
import JsonBody from './json_body'
|
|
14
|
-
import { rateLimit as createRateLimit } from './rate_limit'
|
|
15
|
-
import RequestId from './request_id'
|
|
16
|
-
import RequestSigning from './request_signing'
|
|
17
|
-
import Session from './session'
|
|
18
|
-
|
|
19
|
-
// Export middleware classes
|
|
20
|
-
export { default as Auth } from './auth'
|
|
21
|
-
export { default as ContentSecurityPolicy } from './content_security_policy'
|
|
22
|
-
export { contentSecurityPolicy } from './content_security_policy'
|
|
23
|
-
export { default as Cors } from './cors'
|
|
24
|
-
export { default as Csrf } from './csrf'
|
|
25
|
-
export { default as DDoSProtection } from './ddos_protection'
|
|
26
|
-
export { ddosProtection } from './ddos_protection'
|
|
27
|
-
export { default as FileSecurity } from './file_security'
|
|
28
|
-
export { default as FileUpload } from './file_upload'
|
|
29
|
-
export { documentUpload, fileUpload, imageUpload, multipleFileUpload, singleFileUpload } from './file_upload'
|
|
30
|
-
export { default as Helmet } from './helmet'
|
|
31
|
-
export { default as InputValidation } from './input_validation'
|
|
32
|
-
export { inputValidation } from './input_validation'
|
|
33
|
-
export { default as JsonBody } from './json_body'
|
|
34
|
-
export { default as PerformanceAlerting, performanceAlerting } from './performance_alerting'
|
|
35
|
-
export { default as PerformanceDashboard, performanceDashboard } from './performance_dashboard'
|
|
36
|
-
export { default as PerformanceMonitor, performanceMonitor } from './performance_monitor'
|
|
37
|
-
export { default as RateLimit } from './rate_limit'
|
|
38
|
-
// Export factory functions for easier usage
|
|
39
|
-
export { rateLimit } from './rate_limit'
|
|
40
|
-
export { default as RequestId } from './request_id'
|
|
41
|
-
export { InMemoryNonceStore, default as RequestSigning, requestSigning, signRequest, verifySignature } from './request_signing'
|
|
42
|
-
export type { NonceStore, RequestSigningOptions, SignatureAlgorithm, SignedPart } from './request_signing'
|
|
43
|
-
export { SignatureError } from './request_signing'
|
|
44
|
-
export { default as RequestTracer, requestTracer } from './request_tracer'
|
|
45
|
-
export { default as Security } from './security'
|
|
46
|
-
export { security } from './security'
|
|
47
|
-
export { default as SecuritySuite } from './security_suite'
|
|
48
|
-
export { securitySuite } from './security_suite'
|
|
49
|
-
|
|
50
|
-
export { default as Session } from './session'
|
|
51
|
-
|
|
52
|
-
// Authentication helper exports
|
|
53
|
-
export {
|
|
54
|
-
apiKeyAuth,
|
|
55
|
-
basicAuth,
|
|
56
|
-
bearerAuth,
|
|
57
|
-
extractApiKey,
|
|
58
|
-
extractBasicAuth,
|
|
59
|
-
extractBearerToken,
|
|
60
|
-
jwtAuth,
|
|
61
|
-
oauth2Auth,
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Factory functions for easier middleware creation
|
|
65
|
-
export const cors = (): Cors => new Cors()
|
|
66
|
-
export const jsonBody = (): JsonBody => new JsonBody()
|
|
67
|
-
export const requestId = (): RequestId => new RequestId()
|
|
68
|
-
export const session = (): Session => new Session()
|
|
69
|
-
export const csrf = (): Csrf => new Csrf()
|
|
70
|
-
export function auth(): Auth {
|
|
71
|
-
return new Auth({
|
|
72
|
-
type: 'bearer',
|
|
73
|
-
validator: () => true, // Default just passes through - must be configured correctly
|
|
74
|
-
})
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Named middleware mapping for string-based middleware references (lazy-loaded)
|
|
78
|
-
let _middleware: Record<string, any> | null = null
|
|
79
|
-
|
|
80
|
-
function getMiddleware(): Record<string, any> {
|
|
81
|
-
if (!_middleware) {
|
|
82
|
-
_middleware = {
|
|
83
|
-
'Middleware/Cors': new Cors(),
|
|
84
|
-
'Middleware/JsonBody': new JsonBody(),
|
|
85
|
-
'Middleware/RequestId': new RequestId(),
|
|
86
|
-
'Middleware/Session': new Session(),
|
|
87
|
-
'Middleware/Csrf': new Csrf(),
|
|
88
|
-
'Middleware/Auth': new Auth({
|
|
89
|
-
type: 'bearer',
|
|
90
|
-
validator: () => true,
|
|
91
|
-
}),
|
|
92
|
-
'Middleware/RateLimit': createRateLimit(),
|
|
93
|
-
'Middleware/RequestSigning': new RequestSigning({
|
|
94
|
-
secret: '',
|
|
95
|
-
}),
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
return _middleware
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
export const middleware: Record<string, any> = new Proxy({} as Record<string, any>, {
|
|
102
|
-
get(_target, prop) {
|
|
103
|
-
return getMiddleware()[prop as string]
|
|
104
|
-
},
|
|
105
|
-
ownKeys() {
|
|
106
|
-
return Object.keys(getMiddleware())
|
|
107
|
-
},
|
|
108
|
-
getOwnPropertyDescriptor(_target, prop) {
|
|
109
|
-
const value = getMiddleware()[prop as string]
|
|
110
|
-
if (value !== undefined) {
|
|
111
|
-
return { configurable: true, enumerable: true, value }
|
|
112
|
-
}
|
|
113
|
-
return undefined
|
|
114
|
-
},
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
export default middleware
|