@mpen/routekit 0.1.0 → 0.1.2

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 (133) hide show
  1. package/dist/bin.d.mts +4 -0
  2. package/dist/client/react.d.mts +178 -0
  3. package/dist/client/react.mjs +142 -0
  4. package/dist/client.d.mts +433 -0
  5. package/dist/client.mjs +264 -0
  6. package/dist/content-BuDOmhH_.mjs +102 -0
  7. package/dist/core-CzUCxvGk.d.mts +140 -0
  8. package/dist/core-DbmQauwS.mjs +81 -0
  9. package/dist/handlers.d.mts +72 -0
  10. package/dist/handlers.mjs +153 -0
  11. package/dist/index.d.mts +3 -0
  12. package/dist/index.mjs +1152 -0
  13. package/dist/middleware.d.mts +388 -0
  14. package/dist/middleware.mjs +1222 -0
  15. package/dist/request-Dn0zc-xm.mjs +1025 -0
  16. package/dist/response/content.d.mts +79 -0
  17. package/dist/response/content.mjs +2 -0
  18. package/dist/response/json-rpc.d.mts +1 -0
  19. package/dist/response/json-rpc.mjs +1 -0
  20. package/dist/response/problem/valibot.d.mts +230 -0
  21. package/dist/response/problem/valibot.mjs +258 -0
  22. package/dist/response/problem.d.mts +415 -0
  23. package/dist/response/problem.mjs +183 -0
  24. package/dist/response/status.d.mts +45 -0
  25. package/dist/response/status.mjs +2 -0
  26. package/dist/responses-B379Ep9Y.d.mts +296 -0
  27. package/dist/responses-BpVrgeYi.mjs +101 -0
  28. package/dist/router-Cwb7ak0J.d.mts +1819 -0
  29. package/dist/routes.d.mts +282 -0
  30. package/dist/routes.mjs +311 -0
  31. package/dist/status-C-8mw-FB.mjs +59 -0
  32. package/dist/valibot-D7liFYyB.d.mts +290 -0
  33. package/dist/valibot-Du97X-TS.mjs +326 -0
  34. package/package.json +8 -2
  35. package/src/bin/gen-api-client.test.ts +0 -70
  36. package/src/bin/gen-api-client.ts +0 -986
  37. package/src/client/headers.ts +0 -31
  38. package/src/client/index.ts +0 -8
  39. package/src/client/promise.ts +0 -11
  40. package/src/client/react/index.test.tsx +0 -266
  41. package/src/client/react/index.ts +0 -431
  42. package/src/client/responses.test.ts +0 -151
  43. package/src/client/responses.ts +0 -278
  44. package/src/client/transport.ts +0 -74
  45. package/src/client/transports/body-codec.ts +0 -61
  46. package/src/client/transports/fetch.ts +0 -113
  47. package/src/client/tsconfig.json +0 -9
  48. package/src/client/types.ts +0 -15
  49. package/src/client/url.ts +0 -31
  50. package/src/index.ts +0 -63
  51. package/src/router/fetch-types.ts +0 -13
  52. package/src/router/handlers/index.ts +0 -2
  53. package/src/router/handlers/openapi/index.ts +0 -2
  54. package/src/router/handlers/openapi/openapi.ts +0 -293
  55. package/src/router/integration/zod-openapi.test.ts +0 -74
  56. package/src/router/lib/charset.test.ts +0 -22
  57. package/src/router/lib/charset.ts +0 -133
  58. package/src/router/lib/collections.ts +0 -3
  59. package/src/router/lib/format.test.ts +0 -67
  60. package/src/router/lib/format.ts +0 -35
  61. package/src/router/lib/host.ts +0 -4
  62. package/src/router/lib/json-schema.ts +0 -6
  63. package/src/router/lib/media-type.test.ts +0 -122
  64. package/src/router/lib/media-type.ts +0 -289
  65. package/src/router/lib/pathname.test.ts +0 -18
  66. package/src/router/lib/pathname.ts +0 -19
  67. package/src/router/lib/route-names.ts +0 -70
  68. package/src/router/lib/route-normalize.test.ts +0 -36
  69. package/src/router/lib/route-normalize.ts +0 -67
  70. package/src/router/lib/schema-merge.ts +0 -56
  71. package/src/router/middleware/accept-ctx.test.ts +0 -33
  72. package/src/router/middleware/accept-ctx.ts +0 -12
  73. package/src/router/middleware/body-limit.test.ts +0 -112
  74. package/src/router/middleware/body-limit.ts +0 -121
  75. package/src/router/middleware/content-type-context.ts +0 -0
  76. package/src/router/middleware/cors.test.ts +0 -269
  77. package/src/router/middleware/cors.ts +0 -490
  78. package/src/router/middleware/csrf.test.ts +0 -106
  79. package/src/router/middleware/csrf.ts +0 -192
  80. package/src/router/middleware/define.ts +0 -249
  81. package/src/router/middleware/index.ts +0 -34
  82. package/src/router/middleware/jsxhtml-response.ts +0 -0
  83. package/src/router/middleware/oas-swagger.ts +0 -0
  84. package/src/router/middleware/rate-limit.test.ts +0 -886
  85. package/src/router/middleware/rate-limit.ts +0 -920
  86. package/src/router/middleware/request-id-ctx.test.ts +0 -183
  87. package/src/router/middleware/request-id-ctx.ts +0 -135
  88. package/src/router/middleware/request-logger-format.test.ts +0 -16
  89. package/src/router/middleware/request-logger-format.ts +0 -269
  90. package/src/router/middleware/request-logger.test.ts +0 -267
  91. package/src/router/middleware/request-logger.ts +0 -131
  92. package/src/router/middleware/start-time-ctx.ts +0 -5
  93. package/src/router/request.ts +0 -611
  94. package/src/router/response/core.ts +0 -181
  95. package/src/router/response/directives.ts +0 -233
  96. package/src/router/response/formats/content/bodyless.ts +0 -54
  97. package/src/router/response/formats/content/content.ts +0 -79
  98. package/src/router/response/formats/content/index.ts +0 -2
  99. package/src/router/response/formats/json-rpc/index.ts +0 -2
  100. package/src/router/response/formats/problem/badRequest.ts +0 -90
  101. package/src/router/response/formats/problem/conflict.ts +0 -90
  102. package/src/router/response/formats/problem/created.ts +0 -40
  103. package/src/router/response/formats/problem/index.ts +0 -27
  104. package/src/router/response/formats/problem/notFound.ts +0 -90
  105. package/src/router/response/formats/problem/permissionDenied.ts +0 -90
  106. package/src/router/response/formats/problem/problem.test.ts +0 -888
  107. package/src/router/response/formats/problem/rateLimited.ts +0 -90
  108. package/src/router/response/formats/problem/responses.ts +0 -219
  109. package/src/router/response/formats/problem/root-errors.ts +0 -48
  110. package/src/router/response/formats/problem/sessionExpired.ts +0 -90
  111. package/src/router/response/formats/problem/types.ts +0 -170
  112. package/src/router/response/formats/problem/unauthenticated.ts +0 -90
  113. package/src/router/response/formats/problem/valibot.ts +0 -410
  114. package/src/router/response/formats/status/index.ts +0 -1
  115. package/src/router/response/formats/status/responses.ts +0 -59
  116. package/src/router/response/formats/status/status.test.ts +0 -21
  117. package/src/router/response/framers.ts +0 -85
  118. package/src/router/response/index.ts +0 -28
  119. package/src/router/response/openapi.test.ts +0 -96
  120. package/src/router/response/openapi.ts +0 -1
  121. package/src/router/response/serializers.ts +0 -66
  122. package/src/router/response/stream.ts +0 -35
  123. package/src/router/router.test.ts +0 -1571
  124. package/src/router/router.ts +0 -1965
  125. package/src/router/routes/index.ts +0 -46
  126. package/src/router/routes/valibot/index.ts +0 -18
  127. package/src/router/routes/valibot/valibot.ts +0 -1393
  128. package/src/router/routes/valibot.test.ts +0 -286
  129. package/src/router/routes/zod/index.ts +0 -18
  130. package/src/router/routes/zod/zod.ts +0 -1318
  131. package/src/router/routes/zod.test.ts +0 -280
  132. package/src/router/server-interface.ts +0 -31
  133. package/src/router/types.ts +0 -657
@@ -1,490 +0,0 @@
1
- import { CommonHeaders, HttpStatus, HttpMethod } from '@mpen/http'
2
- import type {
3
- AnyContext,
4
- HandlerFinalResult,
5
- HandlerYield,
6
- MaybePromise,
7
- OneOrMany,
8
- RequestContext,
9
- } from '../types'
10
- import { isLocalhost } from '../lib/host'
11
- import type { RouterBodyInit } from '../fetch-types'
12
- import {
13
- headers as headersDirective,
14
- isChunkDirective,
15
- isHeadersDirective,
16
- isHeadDirective,
17
- isResponseBodyInit,
18
- isRoutekitResponse,
19
- isStreamDirective,
20
- response,
21
- } from '../response'
22
- import { empty } from '../response/formats/content'
23
- import { defineMiddleware, type DeclaredMiddleware } from './define'
24
-
25
- type CorsOriginResolver<Ctx extends object> = (
26
- origin: string | null,
27
- ctx: RequestContext<Ctx>,
28
- ) => MaybePromise<string | null | undefined | false>
29
-
30
- type CorsMethodsResolver<Ctx extends object> = (
31
- origin: string | null,
32
- ctx: RequestContext<Ctx>,
33
- ) => MaybePromise<OneOrMany<string>>
34
-
35
- type AllowedOriginEntry =
36
- | { kind: 'origin'; value: string }
37
- | { kind: 'host'; value: string }
38
- | { kind: 'regex'; value: RegExp }
39
- | { kind: 'null' }
40
-
41
- export interface CorsOptions<Ctx extends object = AnyContext> {
42
- /**
43
- * Allowed origin(s) for cross-origin requests.
44
- * Use `'*'` to allow all origins.
45
- */
46
- origin: OneOrMany<string | URL | RegExp> | CorsOriginResolver<Ctx>
47
- /**
48
- * Allowed methods to echo in preflight responses.
49
- */
50
- allowMethods?: OneOrMany<string> | CorsMethodsResolver<Ctx>
51
- /**
52
- * Allowed headers to echo in preflight responses.
53
- */
54
- allowHeaders?: OneOrMany<string>
55
- /**
56
- * Response headers that should be exposed to the browser.
57
- */
58
- exposeHeaders?: OneOrMany<string>
59
- /**
60
- * Max age (seconds) for caching preflight responses.
61
- */
62
- maxAge?: number
63
- /**
64
- * Whether to set Access-Control-Allow-Credentials.
65
- */
66
- credentials?: boolean
67
- /**
68
- * Allow localhost and loopback origins for local development.
69
- */
70
- allowLocalhost?: boolean
71
- /**
72
- * Convenience flag that enables localhost allowances.
73
- */
74
- dev?: boolean
75
- /**
76
- * HTTP status to use for preflight responses.
77
- */
78
- preflightStatus?: number
79
- }
80
-
81
- const headerOrigin = CommonHeaders.ORIGIN
82
- const headerVary = CommonHeaders.VARY
83
- const headerAccessControlRequestMethod = CommonHeaders.ACCESS_CONTROL_REQUEST_METHOD
84
- const headerAccessControlRequestHeaders = CommonHeaders.ACCESS_CONTROL_REQUEST_HEADERS
85
- const headerAllowOrigin = CommonHeaders.ACCESS_CONTROL_ALLOW_ORIGIN
86
- const headerAllowMethods = CommonHeaders.ACCESS_CONTROL_ALLOW_METHODS
87
- const headerAllowHeaders = CommonHeaders.ACCESS_CONTROL_ALLOW_HEADERS
88
- const headerAllowCredentials = CommonHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS
89
- const headerExposeHeaders = CommonHeaders.ACCESS_CONTROL_EXPOSE_HEADERS
90
- const headerMaxAge = CommonHeaders.ACCESS_CONTROL_MAX_AGE
91
-
92
- const defaultAllowedMethods: OneOrMany<string> = [
93
- HttpMethod.GET,
94
- HttpMethod.HEAD,
95
- HttpMethod.PUT,
96
- HttpMethod.POST,
97
- HttpMethod.DELETE,
98
- HttpMethod.PATCH,
99
- ]
100
-
101
- function normalizeHeaderValue(value: string | null): string | null {
102
- if (!value) return null
103
- const trimmed = value.trim()
104
- return trimmed ? trimmed : null
105
- }
106
-
107
- function normalizeOriginHeader(value: string | null): string | null {
108
- return normalizeHeaderValue(value)
109
- }
110
-
111
- function parseOrigin(originHeader: string | null): URL | null {
112
- if (!originHeader || originHeader === 'null') return null
113
- try {
114
- return new URL(originHeader)
115
- } catch {
116
- return null
117
- }
118
- }
119
-
120
- function normalizeList(value?: OneOrMany<string>): string[] {
121
- if (!value) return []
122
- const list = Array.isArray(value) ? value : [value]
123
- return list.map((entry) => entry.trim()).filter(Boolean)
124
- }
125
-
126
- function normalizeMethods(value?: OneOrMany<string>): string[] {
127
- return normalizeList(value).map((entry) => entry.toUpperCase())
128
- }
129
-
130
- function formatHeaderList(value?: OneOrMany<string>): string | null {
131
- const list = normalizeList(value)
132
- if (!list.length) return null
133
- return list.join(', ')
134
- }
135
-
136
- function formatMethodList(value?: OneOrMany<string>): string | null {
137
- const list = normalizeMethods(value)
138
- if (!list.length) return null
139
- return list.join(', ')
140
- }
141
-
142
- function normalizeAllowedOrigins(value?: OneOrMany<string | URL | RegExp>): AllowedOriginEntry[] {
143
- if (!value) return []
144
- const list = Array.isArray(value) ? value : [value]
145
- const normalized: AllowedOriginEntry[] = []
146
- for (const entry of list) {
147
- if (entry instanceof RegExp) {
148
- normalized.push({ kind: 'regex', value: entry })
149
- continue
150
- }
151
- if (entry instanceof URL) {
152
- normalized.push({ kind: 'origin', value: entry.origin })
153
- continue
154
- }
155
- const trimmed = entry.trim()
156
- if (!trimmed) continue
157
- if (trimmed === 'null') {
158
- normalized.push({ kind: 'null' })
159
- continue
160
- }
161
- if (trimmed.includes('://')) {
162
- try {
163
- normalized.push({ kind: 'origin', value: new URL(trimmed).origin })
164
- } catch {
165
- continue
166
- }
167
- } else {
168
- normalized.push({ kind: 'host', value: trimmed.replace(/\/+$/, '') })
169
- }
170
- }
171
- return normalized
172
- }
173
-
174
- function hasWildcardOrigin(value?: OneOrMany<string | URL | RegExp>): boolean {
175
- if (!value) return false
176
- if (!Array.isArray(value)) {
177
- return typeof value === 'string' && value.trim() === '*'
178
- }
179
- return value.some((entry) => typeof entry === 'string' && entry.trim() === '*')
180
- }
181
-
182
- function isOriginAllowed(
183
- originHeader: string | null,
184
- originUrl: URL | null,
185
- allowlist: AllowedOriginEntry[],
186
- allowLocalhost: boolean,
187
- ): boolean {
188
- if (!originHeader) return false
189
- if (originHeader === 'null') {
190
- return allowlist.some((entry) => entry.kind === 'null')
191
- }
192
- if (originUrl && allowLocalhost && isLocalhost(originUrl.hostname)) return true
193
- for (const entry of allowlist) {
194
- if (entry.kind === 'regex') {
195
- if (entry.value.test(originHeader)) return true
196
- continue
197
- }
198
- if (!originUrl) continue
199
- if (entry.kind === 'origin') {
200
- if (originUrl.origin === entry.value) return true
201
- continue
202
- }
203
- if (entry.kind === 'host') {
204
- if (entry.value.includes(':')) {
205
- if (originUrl.host === entry.value) return true
206
- } else if (originUrl.hostname === entry.value) {
207
- return true
208
- }
209
- }
210
- }
211
- return false
212
- }
213
-
214
- function addVaryHeader(headers: Headers, value: string): void {
215
- const current = headers.get(headerVary)
216
- if (!current) {
217
- headers.set(headerVary, value)
218
- return
219
- }
220
- const existing = current.split(',').map((entry) => entry.trim().toLowerCase())
221
- if (existing.includes(value.toLowerCase())) return
222
- headers.set(headerVary, `${current}, ${value}`)
223
- }
224
-
225
- function applyCorsHeaders(
226
- headers: Headers,
227
- allowOrigin: string,
228
- allowCredentials: boolean,
229
- exposeHeaders?: OneOrMany<string>,
230
- varyOrigin?: boolean,
231
- ): void {
232
- headers.set(headerAllowOrigin, allowOrigin)
233
- if (allowCredentials) {
234
- headers.set(headerAllowCredentials, 'true')
235
- }
236
- const expose = formatHeaderList(exposeHeaders)
237
- if (expose) {
238
- headers.set(headerExposeHeaders, expose)
239
- }
240
- if (varyOrigin) {
241
- addVaryHeader(headers, 'Origin')
242
- }
243
- }
244
-
245
- function isAsyncGenerator(
246
- value: unknown,
247
- ): value is AsyncGenerator<HandlerYield, HandlerFinalResult> {
248
- return (
249
- !!value &&
250
- typeof (value as AsyncGenerator<HandlerYield, HandlerFinalResult>)[Symbol.asyncIterator] ===
251
- 'function'
252
- )
253
- }
254
-
255
- function wrapGeneratorWithCors(
256
- generator: AsyncGenerator<HandlerYield, HandlerFinalResult>,
257
- allowOrigin: string,
258
- allowCredentials: boolean,
259
- exposeHeaders: OneOrMany<string> | undefined,
260
- varyOrigin: boolean,
261
- ): AsyncGenerator<HandlerYield, HandlerFinalResult> {
262
- const apply = (headers: Headers) => {
263
- applyCorsHeaders(headers, allowOrigin, allowCredentials, exposeHeaders, varyOrigin)
264
- }
265
-
266
- async function* wrapped(): AsyncGenerator<HandlerYield, HandlerFinalResult> {
267
- let headersInjected = false
268
- while (true) {
269
- const next = await generator.next()
270
- if (next.done) {
271
- if (!headersInjected) {
272
- const headers = new Headers()
273
- apply(headers)
274
- yield headersDirective(headers)
275
- }
276
- return next.value
277
- }
278
- const value = next.value
279
- if (isHeadersDirective(value)) {
280
- const headers = new Headers(value.headers)
281
- apply(headers)
282
- headersInjected = true
283
- yield headersDirective(headers)
284
- continue
285
- }
286
- if (isHeadDirective(value)) {
287
- const headers = new Headers(value.headers)
288
- apply(headers)
289
- headersInjected = true
290
- yield { ...value, headers }
291
- continue
292
- }
293
- if (isStreamDirective(value)) {
294
- const headers = new Headers(value.headers)
295
- apply(headers)
296
- headersInjected = true
297
- yield { ...value, headers }
298
- continue
299
- }
300
- if (!headersInjected && isChunkDirective(value)) {
301
- const headers = new Headers()
302
- apply(headers)
303
- headersInjected = true
304
- yield headersDirective(headers)
305
- }
306
- yield value
307
- }
308
- }
309
-
310
- return wrapped()
311
- }
312
-
313
- /**
314
- * Attach CORS response headers and handle OPTIONS preflight requests.
315
- *
316
- * @example
317
- * ```ts
318
- * router.use(cors({origin: '*'}))
319
- * router.use(cors({origin: ['https://app.example.com'], credentials: true}))
320
- * router.use(cors({origin: 'https://app.example.com', dev: true}))
321
- * ```
322
- *
323
- * @param options - Configuration for origin, preflight, and header behavior.
324
- * @returns Middleware that applies CORS headers to matching requests.
325
- */
326
- export function cors<Ctx extends object = AnyContext>(
327
- options: CorsOptions<Ctx>,
328
- ): DeclaredMiddleware<{}, Ctx> {
329
- const allowCredentials = options.credentials ?? false
330
- const allowLocalhost = options.allowLocalhost ?? options.dev ?? false
331
- const originOption = options.origin
332
- const originResolver = typeof originOption === 'function' ? originOption : undefined
333
- const originList = originResolver
334
- ? undefined
335
- : (originOption as OneOrMany<string | URL | RegExp>)
336
- const allowlist = originList ? normalizeAllowedOrigins(originList) : []
337
- const hasWildcard = originList ? hasWildcardOrigin(originList) : false
338
- const preflightStatus = options.preflightStatus ?? HttpStatus.NO_CONTENT
339
-
340
- return defineMiddleware({
341
- responses: {
342
- [preflightStatus]: {
343
- schema: { type: 'null' },
344
- parse(value: unknown): undefined {
345
- if (value !== undefined) {
346
- throw new TypeError('CORS preflight responses must not contain a body.')
347
- }
348
- return undefined
349
- },
350
- },
351
- },
352
- schemaAppliesTo: ({ method }) =>
353
- Array.isArray(method)
354
- ? method.includes(HttpMethod.OPTIONS)
355
- : method === HttpMethod.OPTIONS,
356
- async run(ctx, { next, forward, respond }) {
357
- const originHeader = normalizeOriginHeader(ctx.request.headers.get(headerOrigin))
358
- const originUrl = parseOrigin(originHeader)
359
-
360
- let allowOrigin: string | null = null
361
- let varyOrigin = false
362
-
363
- if (originResolver) {
364
- const resolved = await originResolver(originHeader, ctx)
365
- if (resolved) {
366
- const normalized = String(resolved).trim()
367
- if (normalized === '*') {
368
- if (allowCredentials && originHeader) {
369
- allowOrigin = originUrl?.origin ?? originHeader
370
- varyOrigin = true
371
- } else if (!allowCredentials) {
372
- allowOrigin = '*'
373
- }
374
- } else if (normalized) {
375
- allowOrigin = normalized
376
- varyOrigin = Boolean(originHeader)
377
- }
378
- }
379
- } else if (hasWildcard) {
380
- if (allowCredentials && originHeader) {
381
- allowOrigin = originUrl?.origin ?? originHeader
382
- varyOrigin = true
383
- } else if (!allowCredentials) {
384
- allowOrigin = '*'
385
- }
386
- } else if (
387
- originHeader &&
388
- isOriginAllowed(originHeader, originUrl, allowlist, allowLocalhost)
389
- ) {
390
- allowOrigin = originUrl?.origin ?? originHeader
391
- varyOrigin = true
392
- }
393
-
394
- const isPreflight =
395
- ctx.request.method.toUpperCase() === 'OPTIONS' &&
396
- ctx.request.headers.has(headerAccessControlRequestMethod)
397
-
398
- if (isPreflight) {
399
- if (!allowOrigin) {
400
- return respond(empty(preflightStatus))
401
- }
402
- const headers = new Headers()
403
- applyCorsHeaders(headers, allowOrigin, allowCredentials, undefined, varyOrigin)
404
-
405
- const allowMethods =
406
- typeof options.allowMethods === 'function'
407
- ? await options.allowMethods(originHeader, ctx)
408
- : (options.allowMethods ?? defaultAllowedMethods)
409
- const allowMethodsValue = formatMethodList(allowMethods)
410
- if (allowMethodsValue) {
411
- headers.set(headerAllowMethods, allowMethodsValue)
412
- }
413
-
414
- const requestHeaders = normalizeHeaderValue(
415
- ctx.request.headers.get(headerAccessControlRequestHeaders),
416
- )
417
- const allowHeadersValue = formatHeaderList(
418
- options.allowHeaders ?? requestHeaders ?? undefined,
419
- )
420
- if (allowHeadersValue) {
421
- headers.set(headerAllowHeaders, allowHeadersValue)
422
- }
423
-
424
- if (options.maxAge != null) {
425
- headers.set(headerMaxAge, String(options.maxAge))
426
- }
427
- return respond(empty(preflightStatus, { headers }))
428
- }
429
-
430
- const result = await next()
431
- if (!allowOrigin) return forward(result)
432
-
433
- if (result instanceof Response) {
434
- applyCorsHeaders(
435
- result.headers,
436
- allowOrigin,
437
- allowCredentials,
438
- options.exposeHeaders,
439
- varyOrigin,
440
- )
441
- return forward(result)
442
- }
443
-
444
- if (isRoutekitResponse(result)) {
445
- applyCorsHeaders(
446
- result.headers,
447
- allowOrigin,
448
- allowCredentials,
449
- options.exposeHeaders,
450
- varyOrigin,
451
- )
452
- return forward(result)
453
- }
454
-
455
- if (isAsyncGenerator(result)) {
456
- return forward(
457
- wrapGeneratorWithCors(
458
- result,
459
- allowOrigin,
460
- allowCredentials,
461
- options.exposeHeaders,
462
- varyOrigin,
463
- ),
464
- )
465
- }
466
-
467
- if (result != null && isResponseBodyInit(result)) {
468
- const headers = new Headers()
469
- applyCorsHeaders(
470
- headers,
471
- allowOrigin,
472
- allowCredentials,
473
- options.exposeHeaders,
474
- varyOrigin,
475
- )
476
- return forward(new Response(result as RouterBodyInit, { headers }))
477
- }
478
-
479
- const headers = new Headers()
480
- applyCorsHeaders(
481
- headers,
482
- allowOrigin,
483
- allowCredentials,
484
- options.exposeHeaders,
485
- varyOrigin,
486
- )
487
- return forward(response(result, { headers }))
488
- },
489
- })
490
- }
@@ -1,106 +0,0 @@
1
- #!/usr/bin/env -S bun test
2
- import { describe, expect, it } from 'bun:test'
3
- import { HttpMethod, HttpStatus } from '@mpen/http'
4
- import { Router } from '../router'
5
- import { csrf } from './csrf'
6
-
7
- describe(csrf.name, () => {
8
- it('allows same-site fetch requests by default', async () => {
9
- const router = new Router()
10
- router.use(csrf())
11
- router.add({
12
- method: HttpMethod.POST,
13
- path: '/submit',
14
- handler: () => new Response('ok'),
15
- })
16
-
17
- const request = new Request('https://api.example.com/submit', {
18
- method: HttpMethod.POST,
19
- headers: {
20
- origin: 'https://app.example.com',
21
- 'sec-fetch-dest': 'empty',
22
- 'sec-fetch-mode': 'cors',
23
- 'sec-fetch-site': 'same-site',
24
- },
25
- })
26
-
27
- const response = await router.fetch(request)
28
-
29
- expect(response.status).toBe(HttpStatus.OK)
30
- })
31
-
32
- it('rejects cross-site origins by default', async () => {
33
- const router = new Router()
34
- router.use(csrf())
35
- router.add({
36
- method: HttpMethod.POST,
37
- path: '/submit',
38
- handler: () => new Response('ok'),
39
- })
40
-
41
- const request = new Request('https://api.example.com/submit', {
42
- method: HttpMethod.POST,
43
- headers: {
44
- origin: 'https://evil.example',
45
- 'sec-fetch-dest': 'empty',
46
- 'sec-fetch-mode': 'cors',
47
- 'sec-fetch-site': 'cross-site',
48
- },
49
- })
50
-
51
- const response = await router.fetch(request)
52
-
53
- expect(response.status).toBe(HttpStatus.FORBIDDEN)
54
- })
55
-
56
- it('allows whitelisted origins even when cross-site', async () => {
57
- const router = new Router()
58
- router.use(csrf({ allowedOrigins: ['https://evil.example'] }))
59
- router.add({
60
- method: HttpMethod.POST,
61
- path: '/submit',
62
- handler: () => new Response('ok'),
63
- })
64
-
65
- const request = new Request('https://api.example.com/submit', {
66
- method: HttpMethod.POST,
67
- headers: {
68
- origin: 'https://evil.example',
69
- 'sec-fetch-dest': 'empty',
70
- 'sec-fetch-mode': 'cors',
71
- 'sec-fetch-site': 'cross-site',
72
- },
73
- })
74
-
75
- const response = await router.fetch(request)
76
-
77
- expect(response.status).toBe(HttpStatus.OK)
78
- })
79
-
80
- it('allows local dev requests without fetch metadata or origin when dev is enabled', async () => {
81
- const router = new Router()
82
- router.use(csrf({ dev: true }))
83
- router.add({
84
- method: HttpMethod.POST,
85
- path: '/submit',
86
- handler: () => new Response('ok'),
87
- })
88
-
89
- const request = new Request('http://localhost:8787/submit', {
90
- method: HttpMethod.POST,
91
- })
92
-
93
- const response = await router.fetch(request)
94
-
95
- expect(response.status).toBe(HttpStatus.OK)
96
- })
97
-
98
- it('declares its rejection response on affected routes', () => {
99
- const router = new Router().use(csrf())
100
- router.add({ method: HttpMethod.POST, path: '/submit', handler: () => new Response() })
101
-
102
- expect(router.getRoutes()[0]?.schema?.response?.body).toEqual({
103
- [HttpStatus.FORBIDDEN]: { type: 'string' },
104
- })
105
- })
106
- })