@getsoda/core 0.1.0 → 0.2.0
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/README.md +58 -0
- package/dist/app/app.d.ts +17 -1
- package/dist/app/app.d.ts.map +1 -1
- package/dist/app/app.js +29 -10
- package/dist/app/app.js.map +1 -1
- package/dist/di/container.d.ts +5 -0
- package/dist/di/container.d.ts.map +1 -1
- package/dist/di/container.js +15 -1
- package/dist/di/container.js.map +1 -1
- package/dist/di/graph.d.ts +2 -2
- package/dist/di/graph.d.ts.map +1 -1
- package/dist/di/graph.js +20 -10
- package/dist/di/graph.js.map +1 -1
- package/dist/http/arguments.d.ts +20 -2
- package/dist/http/arguments.d.ts.map +1 -1
- package/dist/http/arguments.js +29 -0
- package/dist/http/arguments.js.map +1 -1
- package/dist/http/body.d.ts +28 -0
- package/dist/http/body.d.ts.map +1 -1
- package/dist/http/body.js +96 -1
- package/dist/http/body.js.map +1 -1
- package/dist/http/context-key.d.ts +33 -0
- package/dist/http/context-key.d.ts.map +1 -0
- package/dist/http/context-key.js +37 -0
- package/dist/http/context-key.js.map +1 -0
- package/dist/http/context.d.ts +37 -3
- package/dist/http/context.d.ts.map +1 -1
- package/dist/http/context.js +63 -5
- package/dist/http/context.js.map +1 -1
- package/dist/http/cookies.d.ts +49 -0
- package/dist/http/cookies.d.ts.map +1 -0
- package/dist/http/cookies.js +77 -0
- package/dist/http/cookies.js.map +1 -0
- package/dist/http/cors.d.ts +41 -0
- package/dist/http/cors.d.ts.map +1 -0
- package/dist/http/cors.js +97 -0
- package/dist/http/cors.js.map +1 -0
- package/dist/http/index.d.ts +7 -1
- package/dist/http/index.d.ts.map +1 -1
- package/dist/http/index.js +4 -1
- package/dist/http/index.js.map +1 -1
- package/dist/http/middleware.d.ts +56 -0
- package/dist/http/middleware.d.ts.map +1 -0
- package/dist/http/middleware.js +83 -0
- package/dist/http/middleware.js.map +1 -0
- package/dist/http/parsers.d.ts +3 -0
- package/dist/http/parsers.d.ts.map +1 -1
- package/dist/http/parsers.js +2 -1
- package/dist/http/parsers.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/jwt/algorithms.d.ts +19 -0
- package/dist/jwt/algorithms.d.ts.map +1 -0
- package/dist/jwt/algorithms.js +60 -0
- package/dist/jwt/algorithms.js.map +1 -0
- package/dist/jwt/errors.d.ts +24 -0
- package/dist/jwt/errors.d.ts.map +1 -0
- package/dist/jwt/errors.js +14 -0
- package/dist/jwt/errors.js.map +1 -0
- package/dist/jwt/guard.d.ts +30 -0
- package/dist/jwt/guard.d.ts.map +1 -0
- package/dist/jwt/guard.js +61 -0
- package/dist/jwt/guard.js.map +1 -0
- package/dist/jwt/index.d.ts +14 -0
- package/dist/jwt/index.d.ts.map +1 -0
- package/dist/jwt/index.js +10 -0
- package/dist/jwt/index.js.map +1 -0
- package/dist/jwt/module.d.ts +36 -0
- package/dist/jwt/module.d.ts.map +1 -0
- package/dist/jwt/module.js +38 -0
- package/dist/jwt/module.js.map +1 -0
- package/dist/jwt/service.d.ts +66 -0
- package/dist/jwt/service.d.ts.map +1 -0
- package/dist/jwt/service.js +138 -0
- package/dist/jwt/service.js.map +1 -0
- package/dist/logger/console-logger.d.ts +28 -0
- package/dist/logger/console-logger.d.ts.map +1 -0
- package/dist/logger/console-logger.js +87 -0
- package/dist/logger/console-logger.js.map +1 -0
- package/dist/logger/index.d.ts +9 -0
- package/dist/logger/index.d.ts.map +1 -0
- package/dist/logger/index.js +7 -0
- package/dist/logger/index.js.map +1 -0
- package/dist/logger/logger.d.ts +55 -0
- package/dist/logger/logger.d.ts.map +1 -0
- package/dist/logger/logger.js +66 -0
- package/dist/logger/logger.js.map +1 -0
- package/dist/routing/types.d.ts +1 -1
- package/dist/routing/types.d.ts.map +1 -1
- package/package.json +6 -12
- package/src/app/app.ts +53 -12
- package/src/di/container.ts +24 -2
- package/src/di/graph.ts +26 -11
- package/src/http/arguments.ts +42 -2
- package/src/http/body.ts +142 -1
- package/src/http/context-key.ts +35 -0
- package/src/http/context.ts +94 -8
- package/src/http/cookies.ts +96 -0
- package/src/http/cors.ts +143 -0
- package/src/http/index.ts +7 -1
- package/src/http/middleware.ts +124 -0
- package/src/http/parsers.ts +2 -1
- package/src/index.ts +1 -0
- package/src/jwt/algorithms.ts +113 -0
- package/src/jwt/errors.ts +30 -0
- package/src/jwt/guard.ts +74 -0
- package/src/jwt/index.ts +14 -0
- package/src/jwt/module.ts +58 -0
- package/src/jwt/service.ts +216 -0
- package/src/logger/console-logger.ts +119 -0
- package/src/logger/index.ts +9 -0
- package/src/logger/logger.ts +96 -0
- package/src/routing/types.ts +1 -1
package/dist/http/body.d.ts
CHANGED
|
@@ -23,4 +23,32 @@ export declare const parseJson: (raw: string) => unknown;
|
|
|
23
23
|
* invalid JSON, `undefined` for an empty body (a schema decides if that is allowed).
|
|
24
24
|
*/
|
|
25
25
|
export declare const readJsonBody: (req: Request) => Promise<unknown>;
|
|
26
|
+
/** A form value: text, or a file of a multipart body. */
|
|
27
|
+
export type FormValue = string | File;
|
|
28
|
+
/** Fields of a form; repeated fields and fields named `name[]` are arrays. */
|
|
29
|
+
export type FormFields = Record<string, FormValue | FormValue[]>;
|
|
30
|
+
/**
|
|
31
|
+
* Limits of a form body, beyond the server's `maxRequestBodySize`; each one
|
|
32
|
+
* exceeded is a `413`. Like the `limits` of multer and `@fastify/multipart`.
|
|
33
|
+
*/
|
|
34
|
+
export interface FormLimits {
|
|
35
|
+
/** Bytes of the whole body, refused as soon as they are exceeded, before the body is read entirely. */
|
|
36
|
+
readonly maxBodySize?: number;
|
|
37
|
+
/** Bytes of each file. */
|
|
38
|
+
readonly maxFileSize?: number;
|
|
39
|
+
/** Number of files. */
|
|
40
|
+
readonly maxFiles?: number;
|
|
41
|
+
/** Number of fields, files included. */
|
|
42
|
+
readonly maxFields?: number;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Reads a `multipart/form-data` or `application/x-www-form-urlencoded` body:
|
|
46
|
+
* `415` for another content type, `400` for a malformed body, `413` past a limit.
|
|
47
|
+
*
|
|
48
|
+
* Kept apart from JSON on purpose: an HTML form on another site can post a form
|
|
49
|
+
* without a CORS preflight, a JSON body cannot. A JSON endpoint must not accept forms.
|
|
50
|
+
*
|
|
51
|
+
* `File.type` is the type the client declared, not checked against the content.
|
|
52
|
+
*/
|
|
53
|
+
export declare const readFormBody: (req: Request, limits?: FormLimits) => Promise<FormFields>;
|
|
26
54
|
//# sourceMappingURL=body.d.ts.map
|
package/dist/http/body.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"body.d.ts","sourceRoot":"","sources":["../../src/http/body.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AA8BH;;GAEG;AACH,eAAO,MAAM,SAAS,QAAS,MAAM,KAAG,OAavC,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,YAAY,QAAe,OAAO,KAAG,OAAO,CAAC,OAAO,CAahE,CAAA"}
|
|
1
|
+
{"version":3,"file":"body.d.ts","sourceRoot":"","sources":["../../src/http/body.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AA8BH;;GAEG;AACH,eAAO,MAAM,SAAS,QAAS,MAAM,KAAG,OAavC,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,YAAY,QAAe,OAAO,KAAG,OAAO,CAAC,OAAO,CAahE,CAAA;AAQD,yDAAyD;AACzD,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,IAAI,CAAA;AAErC,8EAA8E;AAC9E,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,SAAS,GAAG,SAAS,EAAE,CAAC,CAAA;AA+BhE;;;GAGG;AACH,MAAM,WAAW,UAAU;IACzB,uGAAuG;IACvG,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,0BAA0B;IAC1B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,uBAAuB;IACvB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;IAC1B,wCAAwC;IACxC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAC5B;AAuDD;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,QAAe,OAAO,WAAU,UAAU,KAAQ,OAAO,CAAC,UAAU,CAqB5F,CAAA"}
|
package/dist/http/body.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* and `constructor` holding an own `prototype`. A plain `constructor` field
|
|
15
15
|
* (`"constructor": "Ford"`) is legitimate and kept.
|
|
16
16
|
*/
|
|
17
|
-
import { BadRequestException, UnsupportedMediaTypeException } from './exceptions.js';
|
|
17
|
+
import { BadRequestException, PayloadTooLargeException, UnsupportedMediaTypeException } from './exceptions.js';
|
|
18
18
|
const SUSPECT_PROTO = /"(?:_|\\u005[Ff])(?:_|\\u005[Ff])(?:p|\\u0070)(?:r|\\u0072)(?:o|\\u006[Ff])(?:t|\\u0074)(?:o|\\u006[Ff])(?:_|\\u005[Ff])(?:_|\\u005[Ff])"\s*:/;
|
|
19
19
|
const SUSPECT_CONSTRUCTOR = /"(?:c|\\u0063)(?:o|\\u006[Ff])(?:n|\\u006[Ee])(?:s|\\u0073)(?:t|\\u0074)(?:r|\\u0072)(?:u|\\u0075)(?:c|\\u0063)(?:t|\\u0074)(?:o|\\u006[Ff])(?:r|\\u0072)"\s*:/;
|
|
20
20
|
const BOM = 0xfeff;
|
|
@@ -63,4 +63,99 @@ export const readJsonBody = async (req) => {
|
|
|
63
63
|
}
|
|
64
64
|
return parseJson(text);
|
|
65
65
|
};
|
|
66
|
+
const FORM_TYPE = /^(?:multipart\/form-data|application\/x-www-form-urlencoded)(?:\s*;|$)/i;
|
|
67
|
+
const ARRAY_SUFFIX = '[]';
|
|
68
|
+
/**
|
|
69
|
+
* Turns form data into an object without prototype.
|
|
70
|
+
*
|
|
71
|
+
* ```ts
|
|
72
|
+
* 'a=1&a=2' => { a: ['1', '2'] } // repeated fields are kept, not overwritten
|
|
73
|
+
* 'tags[]=x' => { tags: ['x'] } // `[]` always gives an array, even with one value
|
|
74
|
+
* 'name=Ann' => { name: 'Ann' }
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
const toFields = (data) => {
|
|
78
|
+
const fields = Object.create(null);
|
|
79
|
+
for (const [rawKey, value] of data) {
|
|
80
|
+
const array = rawKey.endsWith(ARRAY_SUFFIX);
|
|
81
|
+
const key = array ? rawKey.slice(0, -ARRAY_SUFFIX.length) : rawKey;
|
|
82
|
+
const current = fields[key];
|
|
83
|
+
if (current === undefined) {
|
|
84
|
+
fields[key] = array ? [value] : value;
|
|
85
|
+
}
|
|
86
|
+
else if (Array.isArray(current)) {
|
|
87
|
+
current.push(value);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
fields[key] = [current, value];
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return fields;
|
|
94
|
+
};
|
|
95
|
+
/** The body, read up to `max` bytes: `413` as soon as it is larger. */
|
|
96
|
+
const readUpTo = async (req, max) => {
|
|
97
|
+
if (Number(req.headers.get('content-length') ?? 0) > max) {
|
|
98
|
+
throw new PayloadTooLargeException(`Request body is larger than ${max} bytes`);
|
|
99
|
+
}
|
|
100
|
+
if (req.body === null) {
|
|
101
|
+
return new Uint8Array();
|
|
102
|
+
}
|
|
103
|
+
const chunks = [];
|
|
104
|
+
let size = 0;
|
|
105
|
+
for await (const chunk of req.body) {
|
|
106
|
+
size += chunk.byteLength;
|
|
107
|
+
if (size > max) {
|
|
108
|
+
throw new PayloadTooLargeException(`Request body is larger than ${max} bytes`);
|
|
109
|
+
}
|
|
110
|
+
chunks.push(chunk);
|
|
111
|
+
}
|
|
112
|
+
return Buffer.concat(chunks);
|
|
113
|
+
};
|
|
114
|
+
/** `413` for the first limit the form exceeds. */
|
|
115
|
+
const checkLimits = (data, limits) => {
|
|
116
|
+
const { maxFileSize = Infinity, maxFiles = Infinity, maxFields = Infinity } = limits;
|
|
117
|
+
let fields = 0;
|
|
118
|
+
let files = 0;
|
|
119
|
+
for (const [name, value] of data) {
|
|
120
|
+
fields++;
|
|
121
|
+
if (fields > maxFields) {
|
|
122
|
+
throw new PayloadTooLargeException(`Form has more than ${maxFields} fields`);
|
|
123
|
+
}
|
|
124
|
+
if (typeof value !== 'string') {
|
|
125
|
+
files++;
|
|
126
|
+
if (files > maxFiles) {
|
|
127
|
+
throw new PayloadTooLargeException(`Form has more than ${maxFiles} files`);
|
|
128
|
+
}
|
|
129
|
+
if (value.size > maxFileSize) {
|
|
130
|
+
throw new PayloadTooLargeException(`File "${name}" is larger than ${maxFileSize} bytes`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
/**
|
|
136
|
+
* Reads a `multipart/form-data` or `application/x-www-form-urlencoded` body:
|
|
137
|
+
* `415` for another content type, `400` for a malformed body, `413` past a limit.
|
|
138
|
+
*
|
|
139
|
+
* Kept apart from JSON on purpose: an HTML form on another site can post a form
|
|
140
|
+
* without a CORS preflight, a JSON body cannot. A JSON endpoint must not accept forms.
|
|
141
|
+
*
|
|
142
|
+
* `File.type` is the type the client declared, not checked against the content.
|
|
143
|
+
*/
|
|
144
|
+
export const readFormBody = async (req, limits = {}) => {
|
|
145
|
+
const type = req.headers.get('content-type');
|
|
146
|
+
if (type === null || !FORM_TYPE.test(type)) {
|
|
147
|
+
throw new UnsupportedMediaTypeException(`Expected a form body, got ${type ?? 'no content type'}`);
|
|
148
|
+
}
|
|
149
|
+
// Parsed from the bytes read so far, as only a whole body can be parsed.
|
|
150
|
+
const source = limits.maxBodySize === undefined ? req : new Response(await readUpTo(req, limits.maxBodySize), { headers: { 'content-type': type } });
|
|
151
|
+
let data;
|
|
152
|
+
try {
|
|
153
|
+
data = await source.formData();
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
throw new BadRequestException('Request body is not a valid form', { cause: error });
|
|
157
|
+
}
|
|
158
|
+
checkLimits(data, limits);
|
|
159
|
+
return toFields(data);
|
|
160
|
+
};
|
|
66
161
|
//# sourceMappingURL=body.js.map
|
package/dist/http/body.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"body.js","sourceRoot":"","sources":["../../src/http/body.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,mBAAmB,EAAE,6BAA6B,EAAE,MAAM,iBAAiB,CAAA;
|
|
1
|
+
{"version":3,"file":"body.js","sourceRoot":"","sources":["../../src/http/body.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* Typed keys for values attached to a request: what a guard found (the user,
|
|
4
|
+
* the tenant), read later by interceptors and the handler.
|
|
5
|
+
*
|
|
6
|
+
* Like Angular's `HttpContextToken`: the key is an object, so two keys can
|
|
7
|
+
* never collide, and it carries the value type. Unlike it, a default is
|
|
8
|
+
* optional: reading a key nobody set and that has no default is a programming
|
|
9
|
+
* error (a guard missing on the route), reported as such.
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* export const USER = new ContextKey<User>('user')
|
|
13
|
+
*
|
|
14
|
+
* // guard
|
|
15
|
+
* ctx.set(USER, await this.#auth.verify(ctx.header('authorization')))
|
|
16
|
+
*
|
|
17
|
+
* // handler
|
|
18
|
+
* const user = ctx.get(USER)
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Key of a value attached to a request.
|
|
23
|
+
*/
|
|
24
|
+
export declare class ContextKey<T> {
|
|
25
|
+
readonly description: string;
|
|
26
|
+
/** Value of the key when nothing was set. Without it, reading an unset key throws. */
|
|
27
|
+
readonly defaultValue?: (() => T) | undefined;
|
|
28
|
+
constructor(description: string,
|
|
29
|
+
/** Value of the key when nothing was set. Without it, reading an unset key throws. */
|
|
30
|
+
defaultValue?: (() => T) | undefined);
|
|
31
|
+
toString(): string;
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=context-key.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-key.d.ts","sourceRoot":"","sources":["../../src/http/context-key.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH;;GAEG;AACH,qBAAa,UAAU,CAAC,CAAC;IAErB,QAAQ,CAAC,WAAW,EAAE,MAAM;IAC5B,sFAAsF;IACtF,QAAQ,CAAC,YAAY,CAAC,GAAE,MAAM,CAAC;IAHjC,YACW,WAAW,EAAE,MAAM;IAC5B,sFAAsF;IAC7E,YAAY,CAAC,GAAE,MAAM,CAAC,aAAA,EAC7B;IAEJ,QAAQ,IAAI,MAAM,CAEjB;CACF"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* Typed keys for values attached to a request: what a guard found (the user,
|
|
4
|
+
* the tenant), read later by interceptors and the handler.
|
|
5
|
+
*
|
|
6
|
+
* Like Angular's `HttpContextToken`: the key is an object, so two keys can
|
|
7
|
+
* never collide, and it carries the value type. Unlike it, a default is
|
|
8
|
+
* optional: reading a key nobody set and that has no default is a programming
|
|
9
|
+
* error (a guard missing on the route), reported as such.
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* export const USER = new ContextKey<User>('user')
|
|
13
|
+
*
|
|
14
|
+
* // guard
|
|
15
|
+
* ctx.set(USER, await this.#auth.verify(ctx.header('authorization')))
|
|
16
|
+
*
|
|
17
|
+
* // handler
|
|
18
|
+
* const user = ctx.get(USER)
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Key of a value attached to a request.
|
|
23
|
+
*/
|
|
24
|
+
export class ContextKey {
|
|
25
|
+
description;
|
|
26
|
+
defaultValue;
|
|
27
|
+
constructor(description,
|
|
28
|
+
/** Value of the key when nothing was set. Without it, reading an unset key throws. */
|
|
29
|
+
defaultValue) {
|
|
30
|
+
this.description = description;
|
|
31
|
+
this.defaultValue = defaultValue;
|
|
32
|
+
}
|
|
33
|
+
toString() {
|
|
34
|
+
return `ContextKey(${this.description})`;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=context-key.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context-key.js","sourceRoot":"","sources":["../../src/http/context-key.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH;;GAEG;AACH,MAAM,OAAO,UAAU;IAEV,WAAW;IAEX,YAAY;IAHvB,YACW,WAAmB;IAC5B,sFAAsF;IAC7E,YAAsB;2BAFtB,WAAW;4BAEX,YAAY;IACpB,CAAC;IAEJ,QAAQ;QACN,OAAO,cAAc,IAAI,CAAC,WAAW,GAAG,CAAA;IAC1C,CAAC;CACF"}
|
package/dist/http/context.d.ts
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* Request context passed to route handlers.
|
|
4
4
|
*/
|
|
5
5
|
import type { Constructor } from '../di/index.ts';
|
|
6
|
+
import type { FormFields, FormLimits } from './body.ts';
|
|
7
|
+
import type { ContextKey } from './context-key.ts';
|
|
6
8
|
import type { MetadataDecorator } from './enhancers.ts';
|
|
7
9
|
import type { Parsed, ValueParser } from './parsers.ts';
|
|
8
10
|
import type { InferOutput, StandardSchemaV1 } from './schema.ts';
|
|
@@ -41,14 +43,24 @@ export interface RouteInfo {
|
|
|
41
43
|
* }
|
|
42
44
|
* ```
|
|
43
45
|
*
|
|
44
|
-
* The URL is parsed only when `url` or `query()` is used; the body is read at
|
|
46
|
+
* The URL is parsed only when `url` or `query()` is used; the body is read at
|
|
47
|
+
* most once; the storage of `set()` is created on the first `set()`.
|
|
45
48
|
*/
|
|
46
49
|
export declare class Context<P extends string = string> {
|
|
47
50
|
#private;
|
|
48
|
-
readonly req:
|
|
51
|
+
readonly req: Bun.BunRequest;
|
|
49
52
|
readonly params: ContextParams<P>;
|
|
50
53
|
readonly route: RouteInfo;
|
|
51
|
-
constructor(req:
|
|
54
|
+
constructor(req: Bun.BunRequest, params: ContextParams<P>, route: RouteInfo);
|
|
55
|
+
/** Attaches a value to this request, for the guards, interceptors and handler that follow. */
|
|
56
|
+
set<T>(key: ContextKey<T>, value: T): void;
|
|
57
|
+
/**
|
|
58
|
+
* Value attached to this request under `key`, or its default. Throws if
|
|
59
|
+
* nothing was set and the key has no default: a guard is missing on the route.
|
|
60
|
+
*/
|
|
61
|
+
get<T>(key: ContextKey<T>): T;
|
|
62
|
+
/** `true` if a value was set under `key` for this request. */
|
|
63
|
+
has(key: ContextKey<unknown>): boolean;
|
|
52
64
|
/**
|
|
53
65
|
* Custom metadata of the route, the method's value over the class's.
|
|
54
66
|
* Like Nest's `reflector.getAllAndOverride(Roles, [handler, class])`.
|
|
@@ -64,11 +76,33 @@ export declare class Context<P extends string = string> {
|
|
|
64
76
|
/** Request header; converted and checked when a parser is given. */
|
|
65
77
|
header(name: string): string | undefined;
|
|
66
78
|
header<V extends ValueParser<unknown>>(name: string, parser: V): Parsed<V>;
|
|
79
|
+
/**
|
|
80
|
+
* Cookies of the request: Bun's `CookieMap`. What `set()` and `delete()`
|
|
81
|
+
* change is sent with the response as `Set-Cookie`, whatever the response is.
|
|
82
|
+
*
|
|
83
|
+
* ```ts
|
|
84
|
+
* ctx.cookies.set('theme', 'dark', { maxAge: 60 * 60 * 24 * 365 })
|
|
85
|
+
* ctx.cookies.delete('session')
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
get cookies(): Bun.CookieMap;
|
|
89
|
+
/** Cookie, decoded; converted and checked when a parser is given, such as `signed()`. */
|
|
90
|
+
cookie(name: string): string | undefined;
|
|
91
|
+
cookie<V extends ValueParser<unknown>>(name: string, parser: V): Parsed<V>;
|
|
67
92
|
/**
|
|
68
93
|
* JSON body, validated by `schema` when given. `415` for a non-JSON body,
|
|
69
94
|
* `400` for invalid JSON or a failed validation.
|
|
70
95
|
*/
|
|
71
96
|
body(): Promise<unknown>;
|
|
72
97
|
body<S extends StandardSchemaV1>(schema: S): Promise<InferOutput<S>>;
|
|
98
|
+
/**
|
|
99
|
+
* Form body (`multipart/form-data` or `application/x-www-form-urlencoded`),
|
|
100
|
+
* validated by `schema` when given. `415` for another content type.
|
|
101
|
+
* Repeated fields and fields named `name[]` are arrays; files are `File`s
|
|
102
|
+
* whose `type` is what the client declared, not checked against the content.
|
|
103
|
+
* `limits` bound the body, the files and the fields: `413` past them.
|
|
104
|
+
*/
|
|
105
|
+
form(limits?: FormLimits): Promise<FormFields>;
|
|
106
|
+
form<S extends StandardSchemaV1>(schema: S, limits?: FormLimits): Promise<InferOutput<S>>;
|
|
73
107
|
}
|
|
74
108
|
//# sourceMappingURL=context.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/http/context.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAEjD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAA;AAEvD,OAAO,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAEvD,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAChE,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAEvD;;;GAGG;AACH,MAAM,MAAM,aAAa,CAAC,CAAC,SAAS,MAAM,IAAI,MAAM,SAAS,CAAC,GAAG,SAAS,GAAG,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,GAAG,SAAS,CAAA;AAEhH,+CAA+C;AAC/C,MAAM,MAAM,WAAW,CAAC,CAAC,SAAS,MAAM,IAAI,MAAM,SAAS,CAAC,GAAG,MAAM,GAAG,MAAM,UAAU,CAAC,CAAC,CAAC,GAAG,MAAM,CAAA;AAEpG;;;GAGG;AACH,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,UAAU,EAAE,WAAW,CAAC,OAAO,CAAC,CAAA;IACzC,qCAAqC;IACrC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,6DAA6D;IAC7D,QAAQ,CAAC,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAChD;AAED
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/http/context.ts"],"names":[],"mappings":"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"}
|
package/dist/http/context.js
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
* @module
|
|
3
3
|
* Request context passed to route handlers.
|
|
4
4
|
*/
|
|
5
|
-
import { readJsonBody } from './body.js';
|
|
5
|
+
import { readFormBody, readJsonBody } from './body.js';
|
|
6
6
|
import { applyParser } from './parsers.js';
|
|
7
|
-
import { validate } from './schema.js';
|
|
7
|
+
import { isStandardSchema, validate } from './schema.js';
|
|
8
8
|
/**
|
|
9
9
|
* What a route handler receives: the request, with typed readers. `P` is the
|
|
10
10
|
* route path as written in the decorator.
|
|
@@ -19,7 +19,8 @@ import { validate } from './schema.js';
|
|
|
19
19
|
* }
|
|
20
20
|
* ```
|
|
21
21
|
*
|
|
22
|
-
* The URL is parsed only when `url` or `query()` is used; the body is read at
|
|
22
|
+
* The URL is parsed only when `url` or `query()` is used; the body is read at
|
|
23
|
+
* most once; the storage of `set()` is created on the first `set()`.
|
|
23
24
|
*/
|
|
24
25
|
export class Context {
|
|
25
26
|
req;
|
|
@@ -27,11 +28,37 @@ export class Context {
|
|
|
27
28
|
route;
|
|
28
29
|
#url;
|
|
29
30
|
#body;
|
|
31
|
+
#values;
|
|
30
32
|
constructor(req, params, route) {
|
|
31
33
|
this.req = req;
|
|
32
34
|
this.params = params;
|
|
33
35
|
this.route = route;
|
|
34
36
|
}
|
|
37
|
+
/** Attaches a value to this request, for the guards, interceptors and handler that follow. */
|
|
38
|
+
set(key, value) {
|
|
39
|
+
this.#values ??= new Map();
|
|
40
|
+
this.#values.set(key, value);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Value attached to this request under `key`, or its default. Throws if
|
|
44
|
+
* nothing was set and the key has no default: a guard is missing on the route.
|
|
45
|
+
*/
|
|
46
|
+
get(key) {
|
|
47
|
+
if (this.#values?.has(key) === true) {
|
|
48
|
+
return this.#values.get(key);
|
|
49
|
+
}
|
|
50
|
+
if (key.defaultValue === undefined) {
|
|
51
|
+
throw new Error(`${key.toString()} is not set for ${this.route.controller.name}.${this.route.handler}: ` +
|
|
52
|
+
'is the guard or interceptor that sets it applied to this route?');
|
|
53
|
+
}
|
|
54
|
+
const value = key.defaultValue();
|
|
55
|
+
this.set(key, value);
|
|
56
|
+
return value;
|
|
57
|
+
}
|
|
58
|
+
/** `true` if a value was set under `key` for this request. */
|
|
59
|
+
has(key) {
|
|
60
|
+
return this.#values?.has(key) === true;
|
|
61
|
+
}
|
|
35
62
|
/**
|
|
36
63
|
* Custom metadata of the route, the method's value over the class's.
|
|
37
64
|
* Like Nest's `reflector.getAllAndOverride(Roles, [handler, class])`.
|
|
@@ -55,10 +82,41 @@ export class Context {
|
|
|
55
82
|
const value = this.req.headers.get(name) ?? undefined;
|
|
56
83
|
return parser === undefined ? value : applyParser(parser, value, name);
|
|
57
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* Cookies of the request: Bun's `CookieMap`. What `set()` and `delete()`
|
|
87
|
+
* change is sent with the response as `Set-Cookie`, whatever the response is.
|
|
88
|
+
*
|
|
89
|
+
* ```ts
|
|
90
|
+
* ctx.cookies.set('theme', 'dark', { maxAge: 60 * 60 * 24 * 365 })
|
|
91
|
+
* ctx.cookies.delete('session')
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
get cookies() {
|
|
95
|
+
return this.req.cookies;
|
|
96
|
+
}
|
|
97
|
+
cookie(name, parser) {
|
|
98
|
+
const value = this.req.cookies.get(name) ?? undefined;
|
|
99
|
+
return parser === undefined ? value : applyParser(parser, value, name);
|
|
100
|
+
}
|
|
58
101
|
async body(schema) {
|
|
59
|
-
this.#
|
|
60
|
-
const value = await this.#body;
|
|
102
|
+
const value = await this.#read('JSON', readJsonBody);
|
|
61
103
|
return schema === undefined ? value : validate(schema, value, '');
|
|
62
104
|
}
|
|
105
|
+
async form(schemaOrLimits, limits) {
|
|
106
|
+
if (isStandardSchema(schemaOrLimits)) {
|
|
107
|
+
return validate(schemaOrLimits, await this.#read('form', (req) => readFormBody(req, limits)), '');
|
|
108
|
+
}
|
|
109
|
+
return this.#read('form', (req) => readFormBody(req, schemaOrLimits));
|
|
110
|
+
}
|
|
111
|
+
/** Reads the body once; reading it again in another format is a programming error. */
|
|
112
|
+
#read(kind, read) {
|
|
113
|
+
if (this.#body === undefined) {
|
|
114
|
+
this.#body = { kind, value: read(this.req) };
|
|
115
|
+
}
|
|
116
|
+
else if (this.#body.kind !== kind) {
|
|
117
|
+
throw new TypeError(`The body of this request was already read as ${this.#body.kind}, it cannot be read as ${kind}`);
|
|
118
|
+
}
|
|
119
|
+
return this.#body.value;
|
|
120
|
+
}
|
|
63
121
|
}
|
|
64
122
|
//# sourceMappingURL=context.js.map
|
package/dist/http/context.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/http/context.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/http/context.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* Signed cookies. Reading and writing cookies is Bun's: `ctx.cookies` is the
|
|
4
|
+
* request's `CookieMap`, whose changes Bun sends with the response as
|
|
5
|
+
* `Set-Cookie`. This module adds what `@fastify/cookie` and `cookie-signature`
|
|
6
|
+
* add on top: values signed with HMAC-SHA256, so the client can read them but
|
|
7
|
+
* not forge them.
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* const signer = new CookieSigner(process.env.COOKIE_SECRET!)
|
|
11
|
+
*
|
|
12
|
+
* ctx.cookies.set('session', signer.sign(id), { httpOnly: true, secure: true })
|
|
13
|
+
*
|
|
14
|
+
* @Get('me', [cookie('session', signed(signer))])
|
|
15
|
+
* me(id: string) {}
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* A signature proves the value was set by the server, not that it is secret
|
|
19
|
+
* or recent: store secrets server-side, and limit the lifetime with `maxAge`.
|
|
20
|
+
*/
|
|
21
|
+
import type { Parser, ParserOptions } from './parsers.ts';
|
|
22
|
+
/**
|
|
23
|
+
* Signs cookie values as `value.signature` and checks them. Several secrets
|
|
24
|
+
* allow rotation: the first signs, every one verifies.
|
|
25
|
+
*
|
|
26
|
+
* ```ts
|
|
27
|
+
* new CookieSigner([process.env.COOKIE_SECRET!, process.env.OLD_COOKIE_SECRET!])
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export declare class CookieSigner {
|
|
31
|
+
#private;
|
|
32
|
+
/** Secrets should be random and long: `openssl rand -base64 32`. */
|
|
33
|
+
constructor(secrets: string | readonly string[]);
|
|
34
|
+
/** `value` followed by `.` and its signature, in base64url. */
|
|
35
|
+
sign(value: string): string;
|
|
36
|
+
/** The value of a signed string, or `undefined` if it is not signed by one of the secrets. */
|
|
37
|
+
unsign(signed: string): string | undefined;
|
|
38
|
+
}
|
|
39
|
+
type Result<O extends ParserOptions> = O extends {
|
|
40
|
+
readonly optional: true;
|
|
41
|
+
} ? string | undefined : string;
|
|
42
|
+
/**
|
|
43
|
+
* Parser of a signed cookie: its value, `400` when it is missing or its
|
|
44
|
+
* signature is wrong. With `{ optional: true }`, both give `undefined`, as a
|
|
45
|
+
* forged cookie is no better than none.
|
|
46
|
+
*/
|
|
47
|
+
export declare const signed: <const O extends ParserOptions = {}>(signer: CookieSigner, options?: O) => Parser<Result<O>>;
|
|
48
|
+
export {};
|
|
49
|
+
//# sourceMappingURL=cookies.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cookies.d.ts","sourceRoot":"","sources":["../../src/http/cookies.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAKzD;;;;;;;GAOG;AACH,qBAAa,YAAY;;IAGvB,oEAAoE;IACpE,YAAY,OAAO,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,EAQ9C;IAED,+DAA+D;IAC/D,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE1B;IAED,8FAA8F;IAC9F,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAmBzC;CACF;AAED,KAAK,MAAM,CAAC,CAAC,SAAS,aAAa,IAAI,CAAC,SAAS;IAAE,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAA;CAAE,GAAG,MAAM,GAAG,SAAS,GAAG,MAAM,CAAA;AAE1G;;;;GAIG;AACH,eAAO,MAAM,MAAM,GAAI,KAAK,CAAC,CAAC,SAAS,aAAa,GAAG,EAAE,UAAU,YAAY,YAAY,CAAC,KAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAU7G,CAAA"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* Signed cookies. Reading and writing cookies is Bun's: `ctx.cookies` is the
|
|
4
|
+
* request's `CookieMap`, whose changes Bun sends with the response as
|
|
5
|
+
* `Set-Cookie`. This module adds what `@fastify/cookie` and `cookie-signature`
|
|
6
|
+
* add on top: values signed with HMAC-SHA256, so the client can read them but
|
|
7
|
+
* not forge them.
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* const signer = new CookieSigner(process.env.COOKIE_SECRET!)
|
|
11
|
+
*
|
|
12
|
+
* ctx.cookies.set('session', signer.sign(id), { httpOnly: true, secure: true })
|
|
13
|
+
*
|
|
14
|
+
* @Get('me', [cookie('session', signed(signer))])
|
|
15
|
+
* me(id: string) {}
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* A signature proves the value was set by the server, not that it is secret
|
|
19
|
+
* or recent: store secrets server-side, and limit the lifetime with `maxAge`.
|
|
20
|
+
*/
|
|
21
|
+
import { timingSafeEqual } from 'node:crypto';
|
|
22
|
+
import { failure } from './parsers.js';
|
|
23
|
+
const hmac = (secret, value) => new Bun.CryptoHasher('sha256', secret).update(value).digest('base64url');
|
|
24
|
+
/**
|
|
25
|
+
* Signs cookie values as `value.signature` and checks them. Several secrets
|
|
26
|
+
* allow rotation: the first signs, every one verifies.
|
|
27
|
+
*
|
|
28
|
+
* ```ts
|
|
29
|
+
* new CookieSigner([process.env.COOKIE_SECRET!, process.env.OLD_COOKIE_SECRET!])
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export class CookieSigner {
|
|
33
|
+
#secrets;
|
|
34
|
+
/** Secrets should be random and long: `openssl rand -base64 32`. */
|
|
35
|
+
constructor(secrets) {
|
|
36
|
+
const list = typeof secrets === 'string' ? [secrets] : [...secrets];
|
|
37
|
+
if (list.length === 0 || list.includes('')) {
|
|
38
|
+
throw new TypeError('CookieSigner needs at least one secret, and no empty ones');
|
|
39
|
+
}
|
|
40
|
+
this.#secrets = list;
|
|
41
|
+
}
|
|
42
|
+
/** `value` followed by `.` and its signature, in base64url. */
|
|
43
|
+
sign(value) {
|
|
44
|
+
return `${value}.${hmac(this.#secrets[0], value)}`;
|
|
45
|
+
}
|
|
46
|
+
/** The value of a signed string, or `undefined` if it is not signed by one of the secrets. */
|
|
47
|
+
unsign(signed) {
|
|
48
|
+
const dot = signed.lastIndexOf('.');
|
|
49
|
+
if (dot === -1) {
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
const value = signed.slice(0, dot);
|
|
53
|
+
const signature = Buffer.from(signed.slice(dot + 1));
|
|
54
|
+
for (const secret of this.#secrets) {
|
|
55
|
+
const expected = Buffer.from(hmac(secret, value));
|
|
56
|
+
if (expected.length === signature.length && timingSafeEqual(expected, signature)) {
|
|
57
|
+
return value;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Parser of a signed cookie: its value, `400` when it is missing or its
|
|
65
|
+
* signature is wrong. With `{ optional: true }`, both give `undefined`, as a
|
|
66
|
+
* forged cookie is no better than none.
|
|
67
|
+
*/
|
|
68
|
+
export const signed = (signer, options) => ({
|
|
69
|
+
parse: (value, name) => {
|
|
70
|
+
const unsigned = value === undefined ? undefined : signer.unsign(value);
|
|
71
|
+
if (unsigned === undefined && options?.optional !== true) {
|
|
72
|
+
throw failure(name, 'signed cookie');
|
|
73
|
+
}
|
|
74
|
+
return unsigned;
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
//# sourceMappingURL=cookies.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cookies.js","sourceRoot":"","sources":["../../src/http/cookies.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAC7C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAGtC,MAAM,IAAI,GAAG,CAAC,MAAc,EAAE,KAAa,EAAU,EAAE,CACrD,IAAI,GAAG,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,CAAA;AAE1E;;;;;;;GAOG;AACH,MAAM,OAAO,YAAY;IACd,QAAQ,CAAmB;IAEpC,oEAAoE;IACpE,YAAY,OAAmC;QAC7C,MAAM,IAAI,GAAG,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAA;QAEnE,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC;YAC3C,MAAM,IAAI,SAAS,CAAC,2DAA2D,CAAC,CAAA;QAClF,CAAC;QAED,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAA;IACtB,CAAC;IAED,+DAA+D;IAC/D,IAAI,CAAC,KAAa;QAChB,OAAO,GAAG,KAAK,IAAI,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAE,EAAE,KAAK,CAAC,EAAE,CAAA;IACrD,CAAC;IAED,8FAA8F;IAC9F,MAAM,CAAC,MAAc;QACnB,MAAM,GAAG,GAAG,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,CAAA;QAEnC,IAAI,GAAG,KAAK,CAAC,CAAC,EAAE,CAAC;YACf,OAAO,SAAS,CAAA;QAClB,CAAC;QAED,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAA;QAClC,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAA;QAEpD,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YACnC,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAA;YAEjD,IAAI,QAAQ,CAAC,MAAM,KAAK,SAAS,CAAC,MAAM,IAAI,eAAe,CAAC,QAAQ,EAAE,SAAS,CAAC,EAAE,CAAC;gBACjF,OAAO,KAAK,CAAA;YACd,CAAC;QACH,CAAC;QAED,OAAO,SAAS,CAAA;IAClB,CAAC;CACF;AAID;;;;GAIG;AACH,MAAM,CAAC,MAAM,MAAM,GAAG,CAAqC,MAAoB,EAAE,OAAW,EAAqB,EAAE,CAAC,CAAC;IACnH,KAAK,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;QACrB,MAAM,QAAQ,GAAG,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;QAEvE,IAAI,QAAQ,KAAK,SAAS,IAAI,OAAO,EAAE,QAAQ,KAAK,IAAI,EAAE,CAAC;YACzD,MAAM,OAAO,CAAC,IAAI,EAAE,eAAe,CAAC,CAAA;QACtC,CAAC;QAED,OAAO,QAAqB,CAAA;IAC9B,CAAC;CACF,CAAC,CAAA"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* CORS middleware.
|
|
4
|
+
*
|
|
5
|
+
* Follows Hono's `cors()` and the `cors` package Nest uses, with two differences:
|
|
6
|
+
*
|
|
7
|
+
* - only a real preflight (`OPTIONS` with `Access-Control-Request-Method`, as
|
|
8
|
+
* the Fetch spec defines it) is answered here; other `OPTIONS` requests reach
|
|
9
|
+
* the application and its own `OPTIONS` routes;
|
|
10
|
+
* - `credentials: true` with `origin: '*'` is rejected when the middleware is
|
|
11
|
+
* created: browsers refuse that pair, so it could never work.
|
|
12
|
+
*
|
|
13
|
+
* Response headers are set in place: Bun's responses always have mutable headers.
|
|
14
|
+
*
|
|
15
|
+
* ```ts
|
|
16
|
+
* cors({ origin: ['https://app.example.com'], credentials: true, maxAge: 600 })
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
import type { MiddlewareFunction } from './middleware.ts';
|
|
20
|
+
export interface CorsOptions {
|
|
21
|
+
/**
|
|
22
|
+
* Allowed origins: `'*'` (default), one origin, a list, or a predicate.
|
|
23
|
+
* An allowed origin is echoed back; a refused one gets no CORS headers, so the browser blocks the response.
|
|
24
|
+
*/
|
|
25
|
+
readonly origin?: '*' | string | readonly string[] | ((origin: string, req: Request) => boolean);
|
|
26
|
+
/** Methods announced to preflights. */
|
|
27
|
+
readonly methods?: readonly string[];
|
|
28
|
+
/** Headers announced to preflights. Defaults to the ones the preflight asks for. */
|
|
29
|
+
readonly allowedHeaders?: readonly string[];
|
|
30
|
+
/** Response headers the browser exposes to scripts. */
|
|
31
|
+
readonly exposedHeaders?: readonly string[];
|
|
32
|
+
/** Allows cookies and `Authorization`; needs explicit origins. */
|
|
33
|
+
readonly credentials?: boolean;
|
|
34
|
+
/** Seconds a browser may cache a preflight. */
|
|
35
|
+
readonly maxAge?: number;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Creates the CORS middleware. Throws a `TypeError` for options browsers would refuse.
|
|
39
|
+
*/
|
|
40
|
+
export declare const cors: (options?: CorsOptions) => MiddlewareFunction;
|
|
41
|
+
//# sourceMappingURL=cors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cors.d.ts","sourceRoot":"","sources":["../../src/http/cors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AAEzD,MAAM,WAAW,WAAW;IAC1B;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,GAAG,GAAG,MAAM,GAAG,SAAS,MAAM,EAAE,GAAG,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,CAAA;IAChG,uCAAuC;IACvC,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACpC,oFAAoF;IACpF,QAAQ,CAAC,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAC3C,uDAAuD;IACvD,QAAQ,CAAC,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAC3C,kEAAkE;IAClE,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAA;IAC9B,+CAA+C;IAC/C,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CACzB;AA4BD;;GAEG;AACH,eAAO,MAAM,IAAI,aAAa,WAAW,KAAQ,kBA0EhD,CAAA"}
|