0http-bun 1.1.3 → 1.2.1

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.
@@ -0,0 +1,225 @@
1
+ /**
2
+ * Creates CORS (Cross-Origin Resource Sharing) middleware
3
+ * @param {Object} options - CORS configuration options
4
+ * @param {string|Array<string>|Function} options.origin - Allowed origins
5
+ * @param {Array<string>} options.methods - Allowed HTTP methods
6
+ * @param {Array<string>} options.allowedHeaders - Allowed request headers
7
+ * @param {Array<string>} options.exposedHeaders - Headers exposed to the client
8
+ * @param {boolean} options.credentials - Whether to include credentials
9
+ * @param {number} options.maxAge - Preflight cache time in seconds
10
+ * @param {boolean} options.preflightContinue - Pass control to next handler after preflight
11
+ * @param {number} options.optionsSuccessStatus - Status code for successful OPTIONS requests
12
+ * @returns {Function} Middleware function
13
+ */
14
+ function createCORS(options = {}) {
15
+ const {
16
+ origin = '*',
17
+ methods = ['GET', 'HEAD', 'PUT', 'PATCH', 'POST', 'DELETE'],
18
+ allowedHeaders = ['Content-Type', 'Authorization'],
19
+ exposedHeaders = [],
20
+ credentials = false,
21
+ maxAge = 86400, // 24 hours
22
+ preflightContinue = false,
23
+ optionsSuccessStatus = 204,
24
+ } = options
25
+
26
+ return function corsMiddleware(req, next) {
27
+ const requestOrigin = req.headers.get('origin')
28
+ const allowedOrigin = getAllowedOrigin(origin, requestOrigin, req)
29
+
30
+ const addCorsHeaders = (response) => {
31
+ // Add Vary header for dynamic origins (regardless of whether origin is allowed)
32
+ if (typeof origin === 'function' || Array.isArray(origin)) {
33
+ const existingVary = response.headers.get('Vary')
34
+ if (existingVary) {
35
+ if (!existingVary.includes('Origin')) {
36
+ response.headers.set('Vary', `${existingVary}, Origin`)
37
+ }
38
+ } else {
39
+ response.headers.set('Vary', 'Origin')
40
+ }
41
+ }
42
+
43
+ if (allowedOrigin !== false) {
44
+ response.headers.set('Access-Control-Allow-Origin', allowedOrigin)
45
+ }
46
+
47
+ // Don't allow wildcard origin with credentials
48
+ if (credentials && allowedOrigin !== '*') {
49
+ response.headers.set('Access-Control-Allow-Credentials', 'true')
50
+ }
51
+
52
+ // Handle exposedHeaders (can be string or array)
53
+ const exposedHeadersList = Array.isArray(exposedHeaders)
54
+ ? exposedHeaders
55
+ : typeof exposedHeaders === 'string'
56
+ ? [exposedHeaders]
57
+ : []
58
+ if (exposedHeadersList.length > 0) {
59
+ response.headers.set(
60
+ 'Access-Control-Expose-Headers',
61
+ exposedHeadersList.join(', '),
62
+ )
63
+ }
64
+
65
+ // Add method and header info for all requests (not just OPTIONS)
66
+ response.headers.set(
67
+ 'Access-Control-Allow-Methods',
68
+ (Array.isArray(methods) ? methods : [methods]).join(', '),
69
+ )
70
+
71
+ const resolvedAllowedHeaders =
72
+ typeof allowedHeaders === 'function'
73
+ ? allowedHeaders(req)
74
+ : allowedHeaders
75
+ const allowedHeadersList = Array.isArray(resolvedAllowedHeaders)
76
+ ? resolvedAllowedHeaders
77
+ : typeof resolvedAllowedHeaders === 'string'
78
+ ? [resolvedAllowedHeaders]
79
+ : []
80
+ response.headers.set(
81
+ 'Access-Control-Allow-Headers',
82
+ allowedHeadersList.join(', '),
83
+ )
84
+
85
+ return response
86
+ }
87
+
88
+ if (req.method === 'OPTIONS') {
89
+ // Handle preflight request
90
+ const requestMethod = req.headers.get('access-control-request-method')
91
+ const requestHeaders = req.headers.get('access-control-request-headers')
92
+
93
+ // Check if requested method is allowed
94
+ if (requestMethod && !methods.includes(requestMethod)) {
95
+ return new Response(null, {status: 404})
96
+ }
97
+
98
+ // Check if requested headers are allowed
99
+ if (requestHeaders) {
100
+ const requestedHeaders = requestHeaders.split(',').map((h) => h.trim())
101
+ const resolvedAllowedHeaders =
102
+ typeof allowedHeaders === 'function'
103
+ ? allowedHeaders(req)
104
+ : allowedHeaders
105
+ const allowedHeadersList = Array.isArray(resolvedAllowedHeaders)
106
+ ? resolvedAllowedHeaders
107
+ : []
108
+
109
+ const hasDisallowedHeaders = requestedHeaders.some(
110
+ (header) =>
111
+ !allowedHeadersList.some(
112
+ (allowed) => allowed.toLowerCase() === header.toLowerCase(),
113
+ ),
114
+ )
115
+
116
+ if (hasDisallowedHeaders) {
117
+ return new Response(null, {status: 404})
118
+ }
119
+ }
120
+
121
+ const response = new Response(null, {status: optionsSuccessStatus})
122
+
123
+ if (allowedOrigin !== false) {
124
+ response.headers.set('Access-Control-Allow-Origin', allowedOrigin)
125
+
126
+ // Add Vary header for dynamic origins
127
+ if (typeof origin === 'function' || Array.isArray(origin)) {
128
+ response.headers.set('Vary', 'Origin')
129
+ }
130
+ }
131
+
132
+ // Don't allow wildcard origin with credentials
133
+ if (credentials && allowedOrigin !== '*') {
134
+ response.headers.set('Access-Control-Allow-Credentials', 'true')
135
+ }
136
+
137
+ response.headers.set(
138
+ 'Access-Control-Allow-Methods',
139
+ (Array.isArray(methods) ? methods : [methods]).join(', '),
140
+ )
141
+
142
+ const resolvedAllowedHeaders =
143
+ typeof allowedHeaders === 'function'
144
+ ? allowedHeaders(req)
145
+ : allowedHeaders
146
+ const allowedHeadersList = Array.isArray(resolvedAllowedHeaders)
147
+ ? resolvedAllowedHeaders
148
+ : []
149
+ response.headers.set(
150
+ 'Access-Control-Allow-Headers',
151
+ allowedHeadersList.join(', '),
152
+ )
153
+
154
+ response.headers.set('Access-Control-Max-Age', maxAge.toString())
155
+
156
+ if (preflightContinue) {
157
+ const result = next()
158
+ if (result instanceof Promise) {
159
+ return result.then(addCorsHeaders)
160
+ }
161
+ return addCorsHeaders(result)
162
+ } else {
163
+ return response
164
+ }
165
+ }
166
+
167
+ const result = next()
168
+ if (result instanceof Promise) {
169
+ return result.then(addCorsHeaders)
170
+ }
171
+ return addCorsHeaders(result)
172
+ }
173
+ }
174
+
175
+ /**
176
+ * Determines the allowed origin for CORS
177
+ * @param {string|Array<string>|Function} origin - Origin configuration
178
+ * @param {string} requestOrigin - Origin from request header
179
+ * @param {Request} req - Request object
180
+ * @returns {string|false} Allowed origin or false if not allowed
181
+ */
182
+ function getAllowedOrigin(origin, requestOrigin, req) {
183
+ if (origin === '*') {
184
+ return '*'
185
+ }
186
+
187
+ if (origin === false) {
188
+ return false
189
+ }
190
+
191
+ if (typeof origin === 'string') {
192
+ return origin === requestOrigin ? requestOrigin : false
193
+ }
194
+
195
+ if (Array.isArray(origin)) {
196
+ return origin.includes(requestOrigin) ? requestOrigin : false
197
+ }
198
+
199
+ if (typeof origin === 'function') {
200
+ const result = origin(requestOrigin)
201
+ return result === true ? requestOrigin : result || false
202
+ }
203
+
204
+ return false
205
+ }
206
+
207
+ /**
208
+ * Simple CORS middleware for development
209
+ * Allows all origins, methods, and headers
210
+ * @returns {Function} Middleware function
211
+ */
212
+ function simpleCORS() {
213
+ return createCORS({
214
+ origin: '*',
215
+ methods: ['GET', 'HEAD', 'PUT', 'PATCH', 'POST', 'DELETE', 'OPTIONS'],
216
+ allowedHeaders: ['*'],
217
+ credentials: false,
218
+ })
219
+ }
220
+
221
+ module.exports = {
222
+ createCORS,
223
+ simpleCORS,
224
+ getAllowedOrigin,
225
+ }
@@ -0,0 +1,236 @@
1
+ import {RequestHandler, ZeroRequest, StepFunction} from '../../common'
2
+ import {Logger} from 'pino'
3
+
4
+ // Logger middleware types
5
+ export interface LoggerOptions {
6
+ pinoOptions?: any
7
+ serializers?: Record<string, (obj: any) => any>
8
+ logBody?: boolean
9
+ excludePaths?: string[]
10
+ }
11
+
12
+ export function createLogger(options?: LoggerOptions): RequestHandler
13
+ export function simpleLogger(): RequestHandler
14
+
15
+ // JWT Authentication middleware types
16
+ export interface JWKSLike {
17
+ getKey?: (protectedHeader: any, token: string) => Promise<any>
18
+ [key: string]: any
19
+ }
20
+
21
+ export interface JWTAuthOptions {
22
+ secret?:
23
+ | string
24
+ | Uint8Array
25
+ | ((req: ZeroRequest) => Promise<string | Uint8Array>)
26
+ | ((protectedHeader: any, token: string) => Promise<string | Uint8Array>)
27
+ jwksUri?: string
28
+ jwks?: JWKSLike
29
+ jwtOptions?: {
30
+ algorithms?: string[]
31
+ audience?: string | string[]
32
+ issuer?: string | string[]
33
+ subject?: string
34
+ clockTolerance?: number
35
+ maxTokenAge?: number
36
+ }
37
+ // Token extraction options
38
+ getToken?: (req: ZeroRequest) => string | null
39
+ tokenHeader?: string
40
+ tokenQuery?: string
41
+ // Authentication behavior options
42
+ optional?: boolean
43
+ excludePaths?: string[]
44
+ // API key authentication options
45
+ apiKeys?:
46
+ | string
47
+ | string[]
48
+ | ((
49
+ key: string,
50
+ req: ZeroRequest,
51
+ ) => Promise<boolean | any> | boolean | any)
52
+ apiKeyHeader?: string
53
+ apiKeyValidator?:
54
+ | ((key: string) => Promise<boolean | any> | boolean | any)
55
+ | ((
56
+ key: string,
57
+ req: ZeroRequest,
58
+ ) => Promise<boolean | any> | boolean | any)
59
+ validateApiKey?:
60
+ | ((key: string) => Promise<boolean | any> | boolean | any)
61
+ | ((
62
+ key: string,
63
+ req: ZeroRequest,
64
+ ) => Promise<boolean | any> | boolean | any)
65
+ // JWT specific options (can also be in jwtOptions)
66
+ audience?: string | string[]
67
+ issuer?: string
68
+ algorithms?: string[]
69
+ // Custom response and error handling
70
+ unauthorizedResponse?:
71
+ | Response
72
+ | ((error: Error, req: ZeroRequest) => Response | any)
73
+ | {
74
+ status?: number
75
+ body?: any
76
+ headers?: Record<string, string>
77
+ }
78
+ onError?: (error: Error, req: ZeroRequest) => Response | any
79
+ }
80
+
81
+ export interface APIKeyAuthOptions {
82
+ keys:
83
+ | string
84
+ | string[]
85
+ | ((key: string, req: ZeroRequest) => Promise<boolean> | boolean)
86
+ header?: string
87
+ getKey?: (req: ZeroRequest) => string | null
88
+ }
89
+
90
+ export interface TokenExtractionOptions {
91
+ getToken?: (req: ZeroRequest) => string | null
92
+ tokenHeader?: string
93
+ tokenQuery?: string
94
+ }
95
+
96
+ export function createJWTAuth(options?: JWTAuthOptions): RequestHandler
97
+ export function createAPIKeyAuth(options: APIKeyAuthOptions): RequestHandler
98
+ export function extractTokenFromHeader(req: ZeroRequest): string | null
99
+ export function extractToken(
100
+ req: ZeroRequest,
101
+ options?: TokenExtractionOptions,
102
+ ): string | null
103
+ export function validateApiKeyInternal(
104
+ apiKey: string,
105
+ apiKeys: JWTAuthOptions['apiKeys'],
106
+ apiKeyValidator: JWTAuthOptions['apiKeyValidator'],
107
+ req: ZeroRequest,
108
+ ): Promise<boolean | any>
109
+ export function handleAuthError(
110
+ error: Error,
111
+ handlers: {
112
+ unauthorizedResponse?: JWTAuthOptions['unauthorizedResponse']
113
+ onError?: JWTAuthOptions['onError']
114
+ },
115
+ req: ZeroRequest,
116
+ ): Response
117
+
118
+ // Rate limiting middleware types
119
+ export interface RateLimitOptions {
120
+ windowMs?: number
121
+ max?: number
122
+ keyGenerator?: (req: ZeroRequest) => Promise<string> | string
123
+ handler?: (
124
+ req: ZeroRequest,
125
+ totalHits: number,
126
+ max: number,
127
+ resetTime: Date,
128
+ ) => Promise<Response> | Response
129
+ store?: RateLimitStore
130
+ standardHeaders?: boolean
131
+ excludePaths?: string[]
132
+ skip?: (req: ZeroRequest) => boolean
133
+ }
134
+
135
+ export interface RateLimitStore {
136
+ increment(
137
+ key: string,
138
+ windowMs: number,
139
+ ): Promise<{totalHits: number; resetTime: Date}>
140
+ reset(key: string): Promise<void>
141
+ }
142
+
143
+ export class MemoryStore implements RateLimitStore {
144
+ constructor()
145
+ increment(
146
+ key: string,
147
+ windowMs: number,
148
+ ): Promise<{totalHits: number; resetTime: Date}>
149
+ reset(key: string): Promise<void>
150
+ cleanup(now: number): void
151
+ }
152
+
153
+ export function createRateLimit(options?: RateLimitOptions): RequestHandler
154
+ export function createSlidingWindowRateLimit(
155
+ options?: RateLimitOptions,
156
+ ): RequestHandler
157
+ export function defaultKeyGenerator(req: ZeroRequest): string
158
+ export function defaultHandler(
159
+ req: ZeroRequest,
160
+ totalHits: number,
161
+ max: number,
162
+ resetTime: Date,
163
+ ): Response
164
+
165
+ // CORS middleware types
166
+ export interface CORSOptions {
167
+ origin?:
168
+ | string
169
+ | string[]
170
+ | boolean
171
+ | ((origin: string, req: ZeroRequest) => boolean | string)
172
+ methods?: string[]
173
+ allowedHeaders?: string[]
174
+ exposedHeaders?: string[]
175
+ credentials?: boolean
176
+ maxAge?: number
177
+ preflightContinue?: boolean
178
+ optionsSuccessStatus?: number
179
+ }
180
+
181
+ export function createCORS(options?: CORSOptions): RequestHandler
182
+ export function simpleCORS(): RequestHandler
183
+ export function getAllowedOrigin(
184
+ origin: any,
185
+ requestOrigin: string,
186
+ req: ZeroRequest,
187
+ ): string | false
188
+
189
+ // Body parser middleware types
190
+ export interface JSONParserOptions {
191
+ limit?: number
192
+ reviver?: (key: string, value: any) => any
193
+ strict?: boolean
194
+ type?: string
195
+ }
196
+
197
+ export interface TextParserOptions {
198
+ limit?: number
199
+ type?: string
200
+ defaultCharset?: string
201
+ }
202
+
203
+ export interface URLEncodedParserOptions {
204
+ limit?: number
205
+ extended?: boolean
206
+ }
207
+
208
+ export interface MultipartParserOptions {
209
+ limit?: number
210
+ }
211
+
212
+ export interface BodyParserOptions {
213
+ json?: JSONParserOptions
214
+ text?: TextParserOptions
215
+ urlencoded?: URLEncodedParserOptions
216
+ multipart?: MultipartParserOptions
217
+ }
218
+
219
+ export interface ParsedFile {
220
+ name: string
221
+ size: number
222
+ type: string
223
+ data: File
224
+ }
225
+
226
+ export function createJSONParser(options?: JSONParserOptions): RequestHandler
227
+ export function createTextParser(options?: TextParserOptions): RequestHandler
228
+ export function createURLEncodedParser(
229
+ options?: URLEncodedParserOptions,
230
+ ): RequestHandler
231
+ export function createMultipartParser(
232
+ options?: MultipartParserOptions,
233
+ ): RequestHandler
234
+ export function createBodyParser(options?: BodyParserOptions): RequestHandler
235
+ export function hasBody(req: ZeroRequest): boolean
236
+ export function shouldParse(req: ZeroRequest, type: string): boolean
@@ -0,0 +1,45 @@
1
+ // Export all middleware modules
2
+ const loggerModule = require('./logger')
3
+ const jwtAuthModule = require('./jwt-auth')
4
+ const rateLimitModule = require('./rate-limit')
5
+ const corsModule = require('./cors')
6
+ const bodyParserModule = require('./body-parser')
7
+
8
+ module.exports = {
9
+ // Simple interface for common use cases (matches test expectations)
10
+ logger: loggerModule.createLogger,
11
+ jwtAuth: jwtAuthModule.createJWTAuth,
12
+ rateLimit: rateLimitModule.createRateLimit,
13
+ cors: corsModule.createCORS,
14
+ bodyParser: bodyParserModule.createBodyParser,
15
+
16
+ // Complete factory functions for advanced usage
17
+ createLogger: loggerModule.createLogger,
18
+ simpleLogger: loggerModule.simpleLogger,
19
+
20
+ // Authentication middleware
21
+ createJWTAuth: jwtAuthModule.createJWTAuth,
22
+ createAPIKeyAuth: jwtAuthModule.createAPIKeyAuth,
23
+ extractTokenFromHeader: jwtAuthModule.extractTokenFromHeader,
24
+
25
+ // Rate limiting middleware
26
+ createRateLimit: rateLimitModule.createRateLimit,
27
+ createSlidingWindowRateLimit: rateLimitModule.createSlidingWindowRateLimit,
28
+ MemoryStore: rateLimitModule.MemoryStore,
29
+ defaultKeyGenerator: rateLimitModule.defaultKeyGenerator,
30
+ defaultHandler: rateLimitModule.defaultHandler,
31
+
32
+ // CORS middleware
33
+ createCORS: corsModule.createCORS,
34
+ simpleCORS: corsModule.simpleCORS,
35
+ getAllowedOrigin: corsModule.getAllowedOrigin,
36
+
37
+ // Body parser middleware
38
+ createJSONParser: bodyParserModule.createJSONParser,
39
+ createTextParser: bodyParserModule.createTextParser,
40
+ createURLEncodedParser: bodyParserModule.createURLEncodedParser,
41
+ createMultipartParser: bodyParserModule.createMultipartParser,
42
+ createBodyParser: bodyParserModule.createBodyParser,
43
+ hasBody: bodyParserModule.hasBody,
44
+ shouldParse: bodyParserModule.shouldParse,
45
+ }