@erenthedeveloper0/zen-middleware 0.1.0-alpha.3 → 0.1.0-alpha.4
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 +5 -0
- package/dist/cors.d.ts.map +1 -1
- package/dist/cors.js +23 -6
- package/dist/cors.js.map +1 -1
- package/dist/rate-limit.d.ts.map +1 -1
- package/dist/rate-limit.js +14 -0
- package/dist/rate-limit.js.map +1 -1
- package/dist/request-id.d.ts.map +1 -1
- package/dist/request-id.js +9 -0
- package/dist/request-id.js.map +1 -1
- package/dist/security.d.ts.map +1 -1
- package/dist/security.js +32 -0
- package/dist/security.js.map +1 -1
- package/dist/shared.d.ts +43 -1
- package/dist/shared.d.ts.map +1 -1
- package/dist/shared.js +68 -0
- package/dist/shared.js.map +1 -1
- package/package.json +2 -2
- package/src/cors.ts +27 -6
- package/src/rate-limit.ts +19 -1
- package/src/request-id.ts +12 -1
- package/src/security.ts +41 -1
- package/src/shared.ts +103 -1
package/README.md
CHANGED
|
@@ -68,6 +68,11 @@ works in the browser and one that works only in Postman.
|
|
|
68
68
|
The pack orders itself: register them in any order and they run request id →
|
|
69
69
|
security headers → CORS → rate limit.
|
|
70
70
|
|
|
71
|
+
Each factory has an options schema, checked at boot before any plugin runs, so
|
|
72
|
+
`rateLimit({ limt: 100 })` fails with `"limt" is not an option of rate-limit —
|
|
73
|
+
did you mean "limit"?` rather than running with the default limit. The refusal
|
|
74
|
+
names the key, never the value.
|
|
75
|
+
|
|
71
76
|
## Behind a proxy
|
|
72
77
|
|
|
73
78
|
`ctx.ip` — and so the rate limiter — reads `X-Forwarded-For` only when the app
|
package/dist/cors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cors.d.ts","sourceRoot":"","sources":["../src/cors.ts"],"names":[],"mappings":"AAAA,OAAO,EAEU,KAAK,QAAQ,EAAE,KAAK,UAAU,EAAE,KAAK,MAAM,EAC3D,MAAM,6BAA6B,CAAA;
|
|
1
|
+
{"version":3,"file":"cors.d.ts","sourceRoot":"","sources":["../src/cors.ts"],"names":[],"mappings":"AAAA,OAAO,EAEU,KAAK,QAAQ,EAAE,KAAK,UAAU,EAAE,KAAK,MAAM,EAC3D,MAAM,6BAA6B,CAAA;AAOpC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AAEH,MAAM,MAAM,UAAU,GAClB,GAAG,GACH,MAAM,GACN,SAAS,MAAM,EAAE,GACjB,MAAM,GACN,CAAC,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,CAAA;AAEjC,MAAM,WAAW,WAAW;IAC1B;;;;;;;;;OASG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,UAAU,GAAG,SAAS,CAAA;IACxC;;;;;;;;OAQG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,UAAU,EAAE,GAAG,SAAS,CAAA;IACpD;;;;;;;;OAQG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,GAAG,SAAS,CAAA;IACnE,mHAAmH;IACnH,QAAQ,CAAC,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAA;IACvD,gFAAgF;IAChF,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IAC1C;;;;;;;;;OASG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAA;IACtC,0EAA0E;IAC1E,QAAQ,CAAC,eAAe,CAAC,EAAE,GAAG,GAAG,GAAG,GAAG,SAAS,CAAA;CACjD;AAID,2EAA2E;AAC3E,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,KAAK,GAAG,SAAS,CAAA;IACvD,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAA;CAC9B;AAsCD,wBAAgB,IAAI,CAAC,OAAO,GAAE,WAAgB,GAAG,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAuGhE"}
|
package/dist/cors.js
CHANGED
|
@@ -1,11 +1,23 @@
|
|
|
1
1
|
import { definePlugin, parseDuration, Codes, ZenError, } from '@erenthedeveloper0/zen-core';
|
|
2
2
|
import { assertCorsCorpConsistent } from "./consistency.js";
|
|
3
|
-
import { headerOf, isPreflight } from "./shared.js";
|
|
3
|
+
import { BOOLEAN, DURATION, FUNCTION, REGEXP, STRING, STRINGS, either, headerOf, isPreflight, oneOf, optionsSchema, } from "./shared.js";
|
|
4
4
|
const DEFAULT_METHODS = ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE'];
|
|
5
|
+
/** §10.5 step 2 — checked at boot, so `cors({ credential: true })` is refused with "did you mean "credentials"?". */
|
|
6
|
+
const OPTIONS = optionsSchema('cors', {
|
|
7
|
+
origin: either(STRING, STRINGS, REGEXP, FUNCTION),
|
|
8
|
+
methods: STRINGS,
|
|
9
|
+
allowedHeaders: either(oneOf('reflect'), STRINGS),
|
|
10
|
+
exposedHeaders: STRINGS,
|
|
11
|
+
credentials: BOOLEAN,
|
|
12
|
+
maxAge: DURATION,
|
|
13
|
+
preflightStatus: oneOf(200, 204),
|
|
14
|
+
});
|
|
5
15
|
export function cors(options = {}) {
|
|
6
16
|
return definePlugin({
|
|
7
17
|
name: 'cors',
|
|
8
18
|
version: '0.1.0',
|
|
19
|
+
options: OPTIONS,
|
|
20
|
+
boundOptions: options,
|
|
9
21
|
// The pack orders itself (§10.5 step 4). A 429 or a 404 must carry
|
|
10
22
|
// `Access-Control-Allow-Origin` or the browser reports a rate limit as a
|
|
11
23
|
// CORS failure, so this hook has to be staged before the limiter can
|
|
@@ -176,11 +188,16 @@ function compile(written, config, pluginName) {
|
|
|
176
188
|
? ((only) => (o) => o === only)(list[0])
|
|
177
189
|
: ((set) => (o) => set.has(o))(new Set(list))
|
|
178
190
|
: origin instanceof RegExp
|
|
179
|
-
//
|
|
180
|
-
//
|
|
181
|
-
//
|
|
182
|
-
//
|
|
183
|
-
|
|
191
|
+
// Compiled once, here, without the `g` flag — I1 keeps regex compilation
|
|
192
|
+
// off the request path, and this hook runs on every request with an
|
|
193
|
+
// `Origin`. A global regex carries `lastIndex` between calls and would
|
|
194
|
+
// match every other request; so does a sticky one, whose anchoring is
|
|
195
|
+
// the point of writing `y`, so `lastIndex` is put back to 0 before each
|
|
196
|
+
// test rather than the flag being dropped. The copy is ours, so the
|
|
197
|
+
// application's own RegExp object is never touched. §19.3 forbids
|
|
198
|
+
// unbounded backtracking in framework source; a user's own pattern is
|
|
199
|
+
// their choice, and it is named in `explainRoute` as `cors`.
|
|
200
|
+
? ((re) => (o) => { re.lastIndex = 0; return re.test(o); })(new RegExp(origin.source, origin.flags.replace('g', '')))
|
|
184
201
|
: origin;
|
|
185
202
|
return {
|
|
186
203
|
anyOrigin,
|
package/dist/cors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cors.js","sourceRoot":"","sources":["../src/cors.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EAAE,aAAa,EAAE,KAAK,EAAE,QAAQ,GAE7C,MAAM,6BAA6B,CAAA;AACpC,OAAO,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAA;AAC3D,OAAO,
|
|
1
|
+
{"version":3,"file":"cors.js","sourceRoot":"","sources":["../src/cors.ts"],"names":[],"mappings":"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"}
|
package/dist/rate-limit.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../src/rate-limit.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,QAAQ,EAAE,KAAK,MAAM,EAC3B,MAAM,6BAA6B,CAAA;AACpC,OAAO,EAAe,KAAK,KAAK,EAAc,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../src/rate-limit.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,QAAQ,EAAE,KAAK,MAAM,EAC3B,MAAM,6BAA6B,CAAA;AACpC,OAAO,EAAe,KAAK,KAAK,EAAc,MAAM,YAAY,CAAA;AAMhE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,MAAM,WAAW,gBAAgB;IAC/B,oCAAoC;IACpC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IACnC,0DAA0D;IAC1D,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAA;IACtC;;;;;;OAMG;IACH,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,EAAE,gBAAgB,KAAK,MAAM,GAAG,IAAI,CAAC,GAAG,SAAS,CAAA;IACrE,0EAA0E;IAC1E,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,SAAS,CAAA;IAClC,+GAA+G;IAC/G,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IAC5C,yEAAyE;IACzE,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IAC9C,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CACtC;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,GAAG,EAAE;QAAE,MAAM,CAAC,IAAI,EAAE,KAAK,GAAG,MAAM,GAAG,SAAS,CAAA;KAAE,CAAA;CAC1D;AAiBD,wBAAgB,SAAS,CAAC,OAAO,GAAE,gBAAqB,GAAG,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CA8F1E"}
|
package/dist/rate-limit.js
CHANGED
|
@@ -1,10 +1,24 @@
|
|
|
1
1
|
import { definePlugin, parseDuration, Codes, TooManyRequests, ZenError, } from '@erenthedeveloper0/zen-core';
|
|
2
2
|
import { MemoryStore } from "./store.js";
|
|
3
|
+
import { BOOLEAN, DURATION, FUNCTION, NUMBER, STRING, optionsSchema, } from "./shared.js";
|
|
3
4
|
const DEFAULTS = { limit: 100, window: '1m' };
|
|
5
|
+
/** A store is anything with `hit` — `store.ts`'s contract, checked by shape. */
|
|
6
|
+
const STORE = {
|
|
7
|
+
expected: 'a store with a hit() method',
|
|
8
|
+
accepts: (v) => typeof v === 'object' && v !== null && typeof v.hit === 'function',
|
|
9
|
+
json: { type: 'object' },
|
|
10
|
+
};
|
|
11
|
+
/** §10.5 step 2 — checked at boot, so `rateLimit({ limt: 100 })` is refused with "did you mean "limit"?". */
|
|
12
|
+
const OPTIONS = optionsSchema('rateLimit', {
|
|
13
|
+
limit: NUMBER, window: DURATION, key: FUNCTION, store: STORE,
|
|
14
|
+
legacyHeaders: BOOLEAN, standardHeaders: BOOLEAN, message: STRING,
|
|
15
|
+
});
|
|
4
16
|
export function rateLimit(options = {}) {
|
|
5
17
|
return definePlugin({
|
|
6
18
|
name: 'rate-limit',
|
|
7
19
|
version: '0.1.0',
|
|
20
|
+
options: OPTIONS,
|
|
21
|
+
boundOptions: options,
|
|
8
22
|
config: { namespace: 'rateLimit', defaults: { limit: DEFAULTS.limit, window: DEFAULTS.window } },
|
|
9
23
|
setup(app) {
|
|
10
24
|
// §16.1 layer order: an explicit option beats the plugin's own layer-2
|
package/dist/rate-limit.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rate-limit.js","sourceRoot":"","sources":["../src/rate-limit.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EAAE,aAAa,EAAE,KAAK,EAAE,eAAe,EAAE,QAAQ,GAE9D,MAAM,6BAA6B,CAAA;AACpC,OAAO,EAAE,WAAW,EAA0B,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"rate-limit.js","sourceRoot":"","sources":["../src/rate-limit.ts"],"names":[],"mappings":"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"}
|
package/dist/request-id.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request-id.d.ts","sourceRoot":"","sources":["../src/request-id.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiC,KAAK,MAAM,EAAE,MAAM,6BAA6B,CAAA;
|
|
1
|
+
{"version":3,"file":"request-id.d.ts","sourceRoot":"","sources":["../src/request-id.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiC,KAAK,MAAM,EAAE,MAAM,6BAA6B,CAAA;AA4DxF,MAAM,WAAW,gBAAgB;IAC/B,2DAA2D;IAC3D,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS,CAAA;IAC5C;;;;;;OAMG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAAA;IACnD,kEAAkE;IAClE,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS,CAAA;CACrD;AAoBD,wBAAgB,SAAS,CAAC,OAAO,GAAE,gBAAqB,GAAG,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAgD1E"}
|
package/dist/request-id.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { definePlugin, Codes, ZenError } from '@erenthedeveloper0/zen-core';
|
|
2
|
+
import { BOOLEAN, STRING, either, oneOf, optionsSchema, } from "./shared.js";
|
|
2
3
|
/**
|
|
3
4
|
* 8–128 of `[A-Za-z0-9._-]`.
|
|
4
5
|
*
|
|
@@ -9,6 +10,12 @@ import { definePlugin, Codes, ZenError } from '@erenthedeveloper0/zen-core';
|
|
|
9
10
|
* request, so an unbounded one is an amplification the caller chooses.
|
|
10
11
|
*/
|
|
11
12
|
const ACCEPTABLE = /^[A-Za-z0-9._-]{8,128}$/;
|
|
13
|
+
/** §10.5 step 2 — checked at boot, so `requestId({ trustHeaders: true })` is refused with "did you mean "trustHeader"?". */
|
|
14
|
+
const OPTIONS = optionsSchema('requestId', {
|
|
15
|
+
header: either(STRING, oneOf(false)),
|
|
16
|
+
trustHeader: either(BOOLEAN, STRING),
|
|
17
|
+
rejectedHeader: either(STRING, oneOf(false)),
|
|
18
|
+
});
|
|
12
19
|
export function requestId(options = {}) {
|
|
13
20
|
const echo = options.header === undefined ? 'x-request-id' : options.header;
|
|
14
21
|
const trust = options.trustHeader === undefined || options.trustHeader === false ? null
|
|
@@ -27,6 +34,8 @@ export function requestId(options = {}) {
|
|
|
27
34
|
return definePlugin({
|
|
28
35
|
name: 'request-id',
|
|
29
36
|
version: '0.1.0',
|
|
37
|
+
options: OPTIONS,
|
|
38
|
+
boundOptions: options,
|
|
30
39
|
// First in the pack: a preflight answered by `cors` and a 429 refused by
|
|
31
40
|
// `rate-limit` both short-circuit, and both should still carry the id that
|
|
32
41
|
// the log line for them will be filed under.
|
package/dist/request-id.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"request-id.js","sourceRoot":"","sources":["../src/request-id.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,QAAQ,EAAe,MAAM,6BAA6B,CAAA;
|
|
1
|
+
{"version":3,"file":"request-id.js","sourceRoot":"","sources":["../src/request-id.ts"],"names":[],"mappings":"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"}
|
package/dist/security.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"security.d.ts","sourceRoot":"","sources":["../src/security.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,MAAM,EAAE,MAAM,6BAA6B,CAAA;
|
|
1
|
+
{"version":3,"file":"security.d.ts","sourceRoot":"","sources":["../src/security.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,MAAM,EAAE,MAAM,6BAA6B,CAAA;AAOvE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,MAAM,MAAM,cAAc,GACtB,aAAa,GACb,4BAA4B,GAC5B,QAAQ,GACR,0BAA0B,GAC1B,aAAa,GACb,eAAe,GACf,iCAAiC,GACjC,YAAY,CAAA;AAEhB,MAAM,WAAW,sBAAsB;IACrC,0GAA0G;IAC1G,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;IACtC,mGAAmG;IACnG,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,YAAY,GAAG,KAAK,GAAG,SAAS,CAAA;IACjE,QAAQ,CAAC,cAAc,CAAC,EAAE,cAAc,GAAG,KAAK,GAAG,SAAS,CAAA;IAC5D,oCAAoC;IACpC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,aAAa,GAAG,0BAA0B,GAAG,aAAa,GAAG,KAAK,GAAG,SAAS,CAAA;IAC3G;;;;;;;OAOG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,aAAa,GAAG,WAAW,GAAG,cAAc,GAAG,KAAK,GAAG,SAAS,CAAA;IAC/F;;;;;;;;;;;;OAYG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,OAAO,CAAC;QAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,KAAK,GAAG,SAAS,CAAA;IAClI;;;;;OAKG;IACH,QAAQ,CAAC,qBAAqB,CAAC,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS,CAAA;IAC3D,2CAA2C;IAC3C,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,SAAS,CAAA;CAChE;AAyCD,wBAAgB,eAAe,CAAC,OAAO,GAAE,sBAA2B,GAAG,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC,CAgCtF"}
|
package/dist/security.js
CHANGED
|
@@ -1,9 +1,41 @@
|
|
|
1
1
|
import { definePlugin } from '@erenthedeveloper0/zen-core';
|
|
2
2
|
import { assertCorsCorpConsistent } from "./consistency.js";
|
|
3
|
+
import { BOOLEAN, STRING, STRING_RECORD, either, oneOf, optionsSchema, } from "./shared.js";
|
|
4
|
+
const HSTS = {
|
|
5
|
+
expected: 'false, or { maxAge?: number, includeSubDomains?: boolean, preload?: boolean }',
|
|
6
|
+
accepts: (v) => v === false ||
|
|
7
|
+
(typeof v === 'object' && v !== null && !Array.isArray(v) &&
|
|
8
|
+
Object.entries(v).every(([key, value]) => key === 'maxAge' ? typeof value === 'number'
|
|
9
|
+
: key === 'includeSubDomains' || key === 'preload' ? typeof value === 'boolean'
|
|
10
|
+
: false)),
|
|
11
|
+
json: {
|
|
12
|
+
anyOf: [
|
|
13
|
+
{ const: false },
|
|
14
|
+
{
|
|
15
|
+
type: 'object',
|
|
16
|
+
properties: { maxAge: { type: 'number' }, includeSubDomains: { type: 'boolean' }, preload: { type: 'boolean' } },
|
|
17
|
+
additionalProperties: false,
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
/** §10.5 step 2 — checked at boot, so `securityHeaders({ frameOption: 'DENY' })` is refused by name. */
|
|
23
|
+
const OPTIONS = optionsSchema('securityHeaders', {
|
|
24
|
+
noSniff: BOOLEAN,
|
|
25
|
+
frameOptions: oneOf('DENY', 'SAMEORIGIN', false),
|
|
26
|
+
referrerPolicy: oneOf('no-referrer', 'no-referrer-when-downgrade', 'origin', 'origin-when-cross-origin', 'same-origin', 'strict-origin', 'strict-origin-when-cross-origin', 'unsafe-url', false),
|
|
27
|
+
crossOriginOpener: oneOf('same-origin', 'same-origin-allow-popups', 'unsafe-none', false),
|
|
28
|
+
crossOriginResource: oneOf('same-origin', 'same-site', 'cross-origin', false),
|
|
29
|
+
hsts: HSTS,
|
|
30
|
+
contentSecurityPolicy: either(STRING, oneOf(false)),
|
|
31
|
+
headers: STRING_RECORD,
|
|
32
|
+
});
|
|
3
33
|
export function securityHeaders(options = {}) {
|
|
4
34
|
return definePlugin({
|
|
5
35
|
name: 'security-headers',
|
|
6
36
|
version: '0.1.0',
|
|
37
|
+
options: OPTIONS,
|
|
38
|
+
boundOptions: options,
|
|
7
39
|
// Before everything that can short-circuit. `cors` answers preflights and
|
|
8
40
|
// `rate-limit` throws 429s; a hook that runs after either is absent from
|
|
9
41
|
// exactly the responses §19.2 most wants these headers on. Hints on
|
package/dist/security.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"security.js","sourceRoot":"","sources":["../src/security.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAe,MAAM,6BAA6B,CAAA;AACvE,OAAO,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAA;
|
|
1
|
+
{"version":3,"file":"security.js","sourceRoot":"","sources":["../src/security.ts"],"names":[],"mappings":"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"}
|
package/dist/shared.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { AnySchema, JsonSchemaNode, LowercaseName, Reply, ReplyBuilder } from '@erenthedeveloper0/zen-core';
|
|
2
2
|
/**
|
|
3
3
|
* What this pack touches on a context, declared structurally — §10.4.
|
|
4
4
|
*
|
|
@@ -52,4 +52,46 @@ export declare function headerOf(ctx: RawReading, name: LowercaseName): string |
|
|
|
52
52
|
* that route with something the application did not write.
|
|
53
53
|
*/
|
|
54
54
|
export declare function isPreflight(ctx: RawReading): boolean;
|
|
55
|
+
/** One option a factory accepts, stated twice: as a check, and as JSON Schema. */
|
|
56
|
+
export interface OptionField {
|
|
57
|
+
/** What the option must be, completing "expected …". */
|
|
58
|
+
readonly expected: string;
|
|
59
|
+
readonly accepts: (value: unknown) => boolean;
|
|
60
|
+
readonly json: JsonSchemaNode;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The options schema of a factory in this pack.
|
|
64
|
+
*
|
|
65
|
+
* §8.6's motivating sentence is "`rateLimit({ limt: 100 })` fails at startup
|
|
66
|
+
* with a spelling suggestion, not at 3 a.m. under load", and the pack is where
|
|
67
|
+
* that sentence is about. A factory hands its options to core as
|
|
68
|
+
* `Plugin.boundOptions`, against this schema as `Plugin.options`, and they are
|
|
69
|
+
* checked at boot before any plugin's `setup` runs.
|
|
70
|
+
*
|
|
71
|
+
* Hand-written Standard Schemas, so the pack keeps its zero dependencies. The
|
|
72
|
+
* work is split with core so nothing is reported twice: `validate` checks the
|
|
73
|
+
* type of each option that is present, and core reads the schema's JSON Schema
|
|
74
|
+
* — a closed object — to name every key it does not declare, with the one it
|
|
75
|
+
* was probably meant to be. A schema that also refused unknown keys would put
|
|
76
|
+
* every typo in the diagnostic twice.
|
|
77
|
+
*
|
|
78
|
+
* It returns the options it was given, unchanged: a factory reads them from
|
|
79
|
+
* its closure, so what it was built with is exactly what boot checked. The
|
|
80
|
+
* checks a value needs beyond its type — a `limit` below one, an origin with a
|
|
81
|
+
* trailing slash — stay in the factory, because configuration supplies the
|
|
82
|
+
* same values and must meet the same rule.
|
|
83
|
+
*/
|
|
84
|
+
export declare function optionsSchema(factory: string, fields: Readonly<Record<string, OptionField>>): AnySchema;
|
|
85
|
+
export declare const BOOLEAN: OptionField;
|
|
86
|
+
export declare const STRING: OptionField;
|
|
87
|
+
export declare const NUMBER: OptionField;
|
|
88
|
+
export declare const FUNCTION: OptionField;
|
|
89
|
+
export declare const REGEXP: OptionField;
|
|
90
|
+
export declare const DURATION: OptionField;
|
|
91
|
+
export declare const STRINGS: OptionField;
|
|
92
|
+
export declare const STRING_RECORD: OptionField;
|
|
93
|
+
/** Exactly one of these values. */
|
|
94
|
+
export declare function oneOf(...values: readonly (string | number | boolean)[]): OptionField;
|
|
95
|
+
/** Any one of these fields. */
|
|
96
|
+
export declare function either(...fields: readonly OptionField[]): OptionField;
|
|
55
97
|
//# sourceMappingURL=shared.d.ts.map
|
package/dist/shared.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../src/shared.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"shared.d.ts","sourceRoot":"","sources":["../src/shared.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,SAAS,EAAc,cAAc,EAAE,aAAa,EAAE,KAAK,EAAE,YAAY,EAC1E,MAAM,6BAA6B,CAAA;AAEpC;;;;;;;;;;;;;GAaG;AAEH,mEAAmE;AACnE,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,GAAG,EAAE;QAAE,MAAM,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,GAAG,SAAS,CAAA;KAAE,CAAA;CAClE;AAED,2EAA2E;AAC3E,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,GAAG,EAAE,YAAY,CAAA;CAC3B;AAED,iFAAiF;AACjF,MAAM,WAAW,SAAS;IACxB,KAAK,CAAC,MAAM,CAAC,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,KAAK,CAAC,IAAI,CAAC,CAAA;IAC5C,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,IAAI,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,KAAK,CAAC,CAAC,CAAC,CAAA;CACvD;AAED,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,SAAS,CAAA;AAEhD;;;;;;;;;GASG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE,aAAa,GAAG,MAAM,GAAG,SAAS,CAEjF;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAEpD;AAMD,kFAAkF;AAClF,MAAM,WAAW,WAAW;IAC1B,wDAAwD;IACxD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAA;IAC7C,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAA;CAC9B;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,GAAG,SAAS,CA0BvG;AAKD,eAAO,MAAM,OAAO,aAA6E,CAAA;AACjG,eAAO,MAAM,MAAM,aAAsE,CAAA;AACzF,eAAO,MAAM,MAAM,aAAsE,CAAA;AACzF,eAAO,MAAM,QAAQ,aAAsD,CAAA;AAC3E,eAAO,MAAM,MAAM,aAAgD,CAAA;AACnE,eAAO,MAAM,QAAQ,aAIpB,CAAA;AACD,eAAO,MAAM,OAAO,aAInB,CAAA;AACD,eAAO,MAAM,aAAa,aAIzB,CAAA;AAED,mCAAmC;AACnC,wBAAgB,KAAK,CAAC,GAAG,MAAM,EAAE,SAAS,CAAC,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,EAAE,GAAG,WAAW,CAEpF;AAED,+BAA+B;AAC/B,wBAAgB,MAAM,CAAC,GAAG,MAAM,EAAE,SAAS,WAAW,EAAE,GAAG,WAAW,CAMrE"}
|
package/dist/shared.js
CHANGED
|
@@ -22,4 +22,72 @@ export function headerOf(ctx, name) {
|
|
|
22
22
|
export function isPreflight(ctx) {
|
|
23
23
|
return ctx.method === 'OPTIONS' && ctx.raw.header('access-control-request-method') !== undefined;
|
|
24
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* The options schema of a factory in this pack.
|
|
27
|
+
*
|
|
28
|
+
* §8.6's motivating sentence is "`rateLimit({ limt: 100 })` fails at startup
|
|
29
|
+
* with a spelling suggestion, not at 3 a.m. under load", and the pack is where
|
|
30
|
+
* that sentence is about. A factory hands its options to core as
|
|
31
|
+
* `Plugin.boundOptions`, against this schema as `Plugin.options`, and they are
|
|
32
|
+
* checked at boot before any plugin's `setup` runs.
|
|
33
|
+
*
|
|
34
|
+
* Hand-written Standard Schemas, so the pack keeps its zero dependencies. The
|
|
35
|
+
* work is split with core so nothing is reported twice: `validate` checks the
|
|
36
|
+
* type of each option that is present, and core reads the schema's JSON Schema
|
|
37
|
+
* — a closed object — to name every key it does not declare, with the one it
|
|
38
|
+
* was probably meant to be. A schema that also refused unknown keys would put
|
|
39
|
+
* every typo in the diagnostic twice.
|
|
40
|
+
*
|
|
41
|
+
* It returns the options it was given, unchanged: a factory reads them from
|
|
42
|
+
* its closure, so what it was built with is exactly what boot checked. The
|
|
43
|
+
* checks a value needs beyond its type — a `limit` below one, an origin with a
|
|
44
|
+
* trailing slash — stay in the factory, because configuration supplies the
|
|
45
|
+
* same values and must meet the same rule.
|
|
46
|
+
*/
|
|
47
|
+
export function optionsSchema(factory, fields) {
|
|
48
|
+
const properties = {};
|
|
49
|
+
for (const key of Object.keys(fields))
|
|
50
|
+
properties[key] = fields[key].json;
|
|
51
|
+
const shape = { type: 'object', properties, additionalProperties: false };
|
|
52
|
+
return {
|
|
53
|
+
'~standard': {
|
|
54
|
+
version: 1,
|
|
55
|
+
vendor: 'zen-middleware',
|
|
56
|
+
validate(value) {
|
|
57
|
+
if (value === undefined)
|
|
58
|
+
return { value: {} };
|
|
59
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
60
|
+
return { issues: [{ message: `${factory}() takes an options object` }] };
|
|
61
|
+
}
|
|
62
|
+
const given = value;
|
|
63
|
+
const issues = [];
|
|
64
|
+
for (const key of Object.keys(fields)) {
|
|
65
|
+
const option = given[key];
|
|
66
|
+
const field = fields[key];
|
|
67
|
+
if (option !== undefined && !field.accepts(option))
|
|
68
|
+
issues.push({ message: `expected ${field.expected}`, path: [key] });
|
|
69
|
+
}
|
|
70
|
+
return issues.length === 0 ? { value } : { issues };
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
toJsonSchema: () => shape,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
const field = (expected, accepts, json = {}) => ({ expected, accepts, json });
|
|
77
|
+
export const BOOLEAN = field('true or false', (v) => typeof v === 'boolean', { type: 'boolean' });
|
|
78
|
+
export const STRING = field('a string', (v) => typeof v === 'string', { type: 'string' });
|
|
79
|
+
export const NUMBER = field('a number', (v) => typeof v === 'number', { type: 'number' });
|
|
80
|
+
export const FUNCTION = field('a function', (v) => typeof v === 'function');
|
|
81
|
+
export const REGEXP = field('a RegExp', (v) => v instanceof RegExp);
|
|
82
|
+
export const DURATION = field("a duration — milliseconds, or a string such as '10m'", (v) => typeof v === 'number' || typeof v === 'string', { type: ['number', 'string'] });
|
|
83
|
+
export const STRINGS = field('a list of strings', (v) => Array.isArray(v) && v.every((item) => typeof item === 'string'), { type: 'array', items: { type: 'string' } });
|
|
84
|
+
export const STRING_RECORD = field('an object of string values', (v) => typeof v === 'object' && v !== null && !Array.isArray(v) && Object.values(v).every((item) => typeof item === 'string'), { type: 'object', additionalProperties: { type: 'string' } });
|
|
85
|
+
/** Exactly one of these values. */
|
|
86
|
+
export function oneOf(...values) {
|
|
87
|
+
return field(values.map((value) => JSON.stringify(value)).join(', '), (v) => values.includes(v), { enum: [...values] });
|
|
88
|
+
}
|
|
89
|
+
/** Any one of these fields. */
|
|
90
|
+
export function either(...fields) {
|
|
91
|
+
return field(fields.map((f) => f.expected).join(', or '), (v) => fields.some((f) => f.accepts(v)), { anyOf: fields.map((f) => f.json) });
|
|
92
|
+
}
|
|
25
93
|
//# sourceMappingURL=shared.js.map
|
package/dist/shared.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shared.js","sourceRoot":"","sources":["../src/shared.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"shared.js","sourceRoot":"","sources":["../src/shared.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@erenthedeveloper0/zen-middleware",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.4",
|
|
4
4
|
"description": "Zen first-party middleware: CORS, security headers, request ids and rate limiting, as hooks that also run on unmatched requests.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cors",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"test": "node --test \"test/**/*.test.ts\""
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@erenthedeveloper0/zen-core": "0.1.0-alpha.
|
|
55
|
+
"@erenthedeveloper0/zen-core": "0.1.0-alpha.4"
|
|
56
56
|
},
|
|
57
57
|
"peerDependencies": {
|
|
58
58
|
"typescript": ">=5.0"
|
package/src/cors.ts
CHANGED
|
@@ -3,7 +3,10 @@ import {
|
|
|
3
3
|
type AppGraph, type Duration, type HttpMethod, type Plugin, type Reply,
|
|
4
4
|
} from '@erenthedeveloper0/zen-core'
|
|
5
5
|
import { assertCorsCorpConsistent } from './consistency.ts'
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
BOOLEAN, DURATION, FUNCTION, REGEXP, STRING, STRINGS, either, headerOf, isPreflight, oneOf, optionsSchema,
|
|
8
|
+
type CorsRequest, type OptionField, type Staging,
|
|
9
|
+
} from './shared.ts'
|
|
7
10
|
|
|
8
11
|
/**
|
|
9
12
|
* CORS — rfcs/0001 §19.2, §4.2 stage 5, §9.2.
|
|
@@ -148,10 +151,23 @@ interface Compiled {
|
|
|
148
151
|
allowMethods: string
|
|
149
152
|
}
|
|
150
153
|
|
|
154
|
+
/** §10.5 step 2 — checked at boot, so `cors({ credential: true })` is refused with "did you mean "credentials"?". */
|
|
155
|
+
const OPTIONS = optionsSchema('cors', {
|
|
156
|
+
origin: either(STRING, STRINGS, REGEXP, FUNCTION),
|
|
157
|
+
methods: STRINGS,
|
|
158
|
+
allowedHeaders: either(oneOf('reflect'), STRINGS),
|
|
159
|
+
exposedHeaders: STRINGS,
|
|
160
|
+
credentials: BOOLEAN,
|
|
161
|
+
maxAge: DURATION,
|
|
162
|
+
preflightStatus: oneOf(200, 204),
|
|
163
|
+
} satisfies Record<keyof CorsOptions, OptionField>)
|
|
164
|
+
|
|
151
165
|
export function cors(options: CorsOptions = {}): Plugin<void, {}> {
|
|
152
166
|
return definePlugin<void, {}>({
|
|
153
167
|
name: 'cors',
|
|
154
168
|
version: '0.1.0',
|
|
169
|
+
options: OPTIONS,
|
|
170
|
+
boundOptions: options,
|
|
155
171
|
// The pack orders itself (§10.5 step 4). A 429 or a 404 must carry
|
|
156
172
|
// `Access-Control-Allow-Origin` or the browser reports a rate limit as a
|
|
157
173
|
// CORS failure, so this hook has to be staged before the limiter can
|
|
@@ -351,11 +367,16 @@ function compile(
|
|
|
351
367
|
? ((only) => (o) => o === only)(list[0] as string)
|
|
352
368
|
: ((set) => (o) => set.has(o))(new Set(list))
|
|
353
369
|
: origin instanceof RegExp
|
|
354
|
-
//
|
|
355
|
-
//
|
|
356
|
-
//
|
|
357
|
-
//
|
|
358
|
-
|
|
370
|
+
// Compiled once, here, without the `g` flag — I1 keeps regex compilation
|
|
371
|
+
// off the request path, and this hook runs on every request with an
|
|
372
|
+
// `Origin`. A global regex carries `lastIndex` between calls and would
|
|
373
|
+
// match every other request; so does a sticky one, whose anchoring is
|
|
374
|
+
// the point of writing `y`, so `lastIndex` is put back to 0 before each
|
|
375
|
+
// test rather than the flag being dropped. The copy is ours, so the
|
|
376
|
+
// application's own RegExp object is never touched. §19.3 forbids
|
|
377
|
+
// unbounded backtracking in framework source; a user's own pattern is
|
|
378
|
+
// their choice, and it is named in `explainRoute` as `cors`.
|
|
379
|
+
? ((re) => (o: string) => { re.lastIndex = 0; return re.test(o) })(new RegExp(origin.source, origin.flags.replace('g', '')))
|
|
359
380
|
: (origin as (o: string) => boolean)
|
|
360
381
|
|
|
361
382
|
return {
|
package/src/rate-limit.ts
CHANGED
|
@@ -3,7 +3,10 @@ import {
|
|
|
3
3
|
type Duration, type Plugin, type Reply,
|
|
4
4
|
} from '@erenthedeveloper0/zen-core'
|
|
5
5
|
import { MemoryStore, type Store, type Tally } from './store.ts'
|
|
6
|
-
import
|
|
6
|
+
import {
|
|
7
|
+
BOOLEAN, DURATION, FUNCTION, NUMBER, STRING, optionsSchema,
|
|
8
|
+
type Answering, type OptionField, type RawReading, type Staging,
|
|
9
|
+
} from './shared.ts'
|
|
7
10
|
|
|
8
11
|
/**
|
|
9
12
|
* Rate limiting — rfcs/0001 §9.2, §19.2, §19.4, Annex B `ZEN_RATE_LIMITED`.
|
|
@@ -74,10 +77,25 @@ export interface RateLimitContext {
|
|
|
74
77
|
|
|
75
78
|
const DEFAULTS = { limit: 100, window: '1m' } as const
|
|
76
79
|
|
|
80
|
+
/** A store is anything with `hit` — `store.ts`'s contract, checked by shape. */
|
|
81
|
+
const STORE: OptionField = {
|
|
82
|
+
expected: 'a store with a hit() method',
|
|
83
|
+
accepts: (v) => typeof v === 'object' && v !== null && typeof (v as { hit?: unknown }).hit === 'function',
|
|
84
|
+
json: { type: 'object' },
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** §10.5 step 2 — checked at boot, so `rateLimit({ limt: 100 })` is refused with "did you mean "limit"?". */
|
|
88
|
+
const OPTIONS = optionsSchema('rateLimit', {
|
|
89
|
+
limit: NUMBER, window: DURATION, key: FUNCTION, store: STORE,
|
|
90
|
+
legacyHeaders: BOOLEAN, standardHeaders: BOOLEAN, message: STRING,
|
|
91
|
+
} satisfies Record<keyof RateLimitOptions, OptionField>)
|
|
92
|
+
|
|
77
93
|
export function rateLimit(options: RateLimitOptions = {}): Plugin<void, {}> {
|
|
78
94
|
return definePlugin<void, {}>({
|
|
79
95
|
name: 'rate-limit',
|
|
80
96
|
version: '0.1.0',
|
|
97
|
+
options: OPTIONS,
|
|
98
|
+
boundOptions: options,
|
|
81
99
|
config: { namespace: 'rateLimit', defaults: { limit: DEFAULTS.limit, window: DEFAULTS.window } },
|
|
82
100
|
|
|
83
101
|
setup(app) {
|
package/src/request-id.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { definePlugin, Codes, ZenError, type Plugin } from '@erenthedeveloper0/zen-core'
|
|
2
|
-
import
|
|
2
|
+
import {
|
|
3
|
+
BOOLEAN, STRING, either, oneOf, optionsSchema, type OptionField, type RawReading, type Staging,
|
|
4
|
+
} from './shared.ts'
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* Request id — rfcs/0001 §19.4, §31.1.
|
|
@@ -82,6 +84,13 @@ export interface RequestIdOptions {
|
|
|
82
84
|
*/
|
|
83
85
|
const ACCEPTABLE = /^[A-Za-z0-9._-]{8,128}$/
|
|
84
86
|
|
|
87
|
+
/** §10.5 step 2 — checked at boot, so `requestId({ trustHeaders: true })` is refused with "did you mean "trustHeader"?". */
|
|
88
|
+
const OPTIONS = optionsSchema('requestId', {
|
|
89
|
+
header: either(STRING, oneOf(false)),
|
|
90
|
+
trustHeader: either(BOOLEAN, STRING),
|
|
91
|
+
rejectedHeader: either(STRING, oneOf(false)),
|
|
92
|
+
} satisfies Record<keyof RequestIdOptions, OptionField>)
|
|
93
|
+
|
|
85
94
|
export function requestId(options: RequestIdOptions = {}): Plugin<void, {}> {
|
|
86
95
|
const echo = options.header === undefined ? 'x-request-id' : options.header
|
|
87
96
|
const trust =
|
|
@@ -107,6 +116,8 @@ export function requestId(options: RequestIdOptions = {}): Plugin<void, {}> {
|
|
|
107
116
|
return definePlugin<void, {}>({
|
|
108
117
|
name: 'request-id',
|
|
109
118
|
version: '0.1.0',
|
|
119
|
+
options: OPTIONS,
|
|
120
|
+
boundOptions: options,
|
|
110
121
|
// First in the pack: a preflight answered by `cors` and a 429 refused by
|
|
111
122
|
// `rate-limit` both short-circuit, and both should still carry the id that
|
|
112
123
|
// the log line for them will be filed under.
|
package/src/security.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { definePlugin, type Plugin } from '@erenthedeveloper0/zen-core'
|
|
2
2
|
import { assertCorsCorpConsistent } from './consistency.ts'
|
|
3
3
|
import type { CorsExports } from './cors.ts'
|
|
4
|
-
import
|
|
4
|
+
import {
|
|
5
|
+
BOOLEAN, STRING, STRING_RECORD, either, oneOf, optionsSchema, type OptionField, type Staging,
|
|
6
|
+
} from './shared.ts'
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* Security response headers — rfcs/0001 §19.2.
|
|
@@ -90,10 +92,48 @@ export interface SecurityHeadersOptions {
|
|
|
90
92
|
/** One resolved header, so the set is data and the hook is a loop over it. */
|
|
91
93
|
type Pair = readonly [name: string, value: string]
|
|
92
94
|
|
|
95
|
+
const HSTS: OptionField = {
|
|
96
|
+
expected: 'false, or { maxAge?: number, includeSubDomains?: boolean, preload?: boolean }',
|
|
97
|
+
accepts: (v) =>
|
|
98
|
+
v === false ||
|
|
99
|
+
(typeof v === 'object' && v !== null && !Array.isArray(v) &&
|
|
100
|
+
Object.entries(v).every(([key, value]) =>
|
|
101
|
+
key === 'maxAge' ? typeof value === 'number'
|
|
102
|
+
: key === 'includeSubDomains' || key === 'preload' ? typeof value === 'boolean'
|
|
103
|
+
: false)),
|
|
104
|
+
json: {
|
|
105
|
+
anyOf: [
|
|
106
|
+
{ const: false },
|
|
107
|
+
{
|
|
108
|
+
type: 'object',
|
|
109
|
+
properties: { maxAge: { type: 'number' }, includeSubDomains: { type: 'boolean' }, preload: { type: 'boolean' } },
|
|
110
|
+
additionalProperties: false,
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
},
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** §10.5 step 2 — checked at boot, so `securityHeaders({ frameOption: 'DENY' })` is refused by name. */
|
|
117
|
+
const OPTIONS = optionsSchema('securityHeaders', {
|
|
118
|
+
noSniff: BOOLEAN,
|
|
119
|
+
frameOptions: oneOf('DENY', 'SAMEORIGIN', false),
|
|
120
|
+
referrerPolicy: oneOf(
|
|
121
|
+
'no-referrer', 'no-referrer-when-downgrade', 'origin', 'origin-when-cross-origin', 'same-origin',
|
|
122
|
+
'strict-origin', 'strict-origin-when-cross-origin', 'unsafe-url', false,
|
|
123
|
+
),
|
|
124
|
+
crossOriginOpener: oneOf('same-origin', 'same-origin-allow-popups', 'unsafe-none', false),
|
|
125
|
+
crossOriginResource: oneOf('same-origin', 'same-site', 'cross-origin', false),
|
|
126
|
+
hsts: HSTS,
|
|
127
|
+
contentSecurityPolicy: either(STRING, oneOf(false)),
|
|
128
|
+
headers: STRING_RECORD,
|
|
129
|
+
} satisfies Record<keyof SecurityHeadersOptions, OptionField>)
|
|
130
|
+
|
|
93
131
|
export function securityHeaders(options: SecurityHeadersOptions = {}): Plugin<void, {}> {
|
|
94
132
|
return definePlugin<void, {}>({
|
|
95
133
|
name: 'security-headers',
|
|
96
134
|
version: '0.1.0',
|
|
135
|
+
options: OPTIONS,
|
|
136
|
+
boundOptions: options,
|
|
97
137
|
// Before everything that can short-circuit. `cors` answers preflights and
|
|
98
138
|
// `rate-limit` throws 429s; a hook that runs after either is absent from
|
|
99
139
|
// exactly the responses §19.2 most wants these headers on. Hints on
|
package/src/shared.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
AnySchema, JsonSchema, JsonSchemaNode, LowercaseName, Reply, ReplyBuilder, StandardIssue,
|
|
3
|
+
} from '@erenthedeveloper0/zen-core'
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
6
|
* What this pack touches on a context, declared structurally — §10.4.
|
|
@@ -59,3 +61,103 @@ export function headerOf(ctx: RawReading, name: LowercaseName): string | undefin
|
|
|
59
61
|
export function isPreflight(ctx: RawReading): boolean {
|
|
60
62
|
return ctx.method === 'OPTIONS' && ctx.raw.header('access-control-request-method') !== undefined
|
|
61
63
|
}
|
|
64
|
+
|
|
65
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
66
|
+
// Options schemas — rfcs/0001 §10.5 step 2
|
|
67
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
/** One option a factory accepts, stated twice: as a check, and as JSON Schema. */
|
|
70
|
+
export interface OptionField {
|
|
71
|
+
/** What the option must be, completing "expected …". */
|
|
72
|
+
readonly expected: string
|
|
73
|
+
readonly accepts: (value: unknown) => boolean
|
|
74
|
+
readonly json: JsonSchemaNode
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The options schema of a factory in this pack.
|
|
79
|
+
*
|
|
80
|
+
* §8.6's motivating sentence is "`rateLimit({ limt: 100 })` fails at startup
|
|
81
|
+
* with a spelling suggestion, not at 3 a.m. under load", and the pack is where
|
|
82
|
+
* that sentence is about. A factory hands its options to core as
|
|
83
|
+
* `Plugin.boundOptions`, against this schema as `Plugin.options`, and they are
|
|
84
|
+
* checked at boot before any plugin's `setup` runs.
|
|
85
|
+
*
|
|
86
|
+
* Hand-written Standard Schemas, so the pack keeps its zero dependencies. The
|
|
87
|
+
* work is split with core so nothing is reported twice: `validate` checks the
|
|
88
|
+
* type of each option that is present, and core reads the schema's JSON Schema
|
|
89
|
+
* — a closed object — to name every key it does not declare, with the one it
|
|
90
|
+
* was probably meant to be. A schema that also refused unknown keys would put
|
|
91
|
+
* every typo in the diagnostic twice.
|
|
92
|
+
*
|
|
93
|
+
* It returns the options it was given, unchanged: a factory reads them from
|
|
94
|
+
* its closure, so what it was built with is exactly what boot checked. The
|
|
95
|
+
* checks a value needs beyond its type — a `limit` below one, an origin with a
|
|
96
|
+
* trailing slash — stay in the factory, because configuration supplies the
|
|
97
|
+
* same values and must meet the same rule.
|
|
98
|
+
*/
|
|
99
|
+
export function optionsSchema(factory: string, fields: Readonly<Record<string, OptionField>>): AnySchema {
|
|
100
|
+
const properties: Record<string, JsonSchemaNode> = {}
|
|
101
|
+
for (const key of Object.keys(fields)) properties[key] = (fields[key] as OptionField).json
|
|
102
|
+
const shape: JsonSchema = { type: 'object', properties, additionalProperties: false }
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
'~standard': {
|
|
106
|
+
version: 1,
|
|
107
|
+
vendor: 'zen-middleware',
|
|
108
|
+
validate(value: unknown) {
|
|
109
|
+
if (value === undefined) return { value: {} }
|
|
110
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
111
|
+
return { issues: [{ message: `${factory}() takes an options object` }] }
|
|
112
|
+
}
|
|
113
|
+
const given = value as Readonly<Record<string, unknown>>
|
|
114
|
+
const issues: StandardIssue[] = []
|
|
115
|
+
for (const key of Object.keys(fields)) {
|
|
116
|
+
const option = given[key]
|
|
117
|
+
const field = fields[key] as OptionField
|
|
118
|
+
if (option !== undefined && !field.accepts(option)) issues.push({ message: `expected ${field.expected}`, path: [key] })
|
|
119
|
+
}
|
|
120
|
+
return issues.length === 0 ? { value } : { issues }
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
toJsonSchema: () => shape,
|
|
124
|
+
} as AnySchema
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const field = (expected: string, accepts: (value: unknown) => boolean, json: JsonSchemaNode = {}): OptionField =>
|
|
128
|
+
({ expected, accepts, json })
|
|
129
|
+
|
|
130
|
+
export const BOOLEAN = field('true or false', (v) => typeof v === 'boolean', { type: 'boolean' })
|
|
131
|
+
export const STRING = field('a string', (v) => typeof v === 'string', { type: 'string' })
|
|
132
|
+
export const NUMBER = field('a number', (v) => typeof v === 'number', { type: 'number' })
|
|
133
|
+
export const FUNCTION = field('a function', (v) => typeof v === 'function')
|
|
134
|
+
export const REGEXP = field('a RegExp', (v) => v instanceof RegExp)
|
|
135
|
+
export const DURATION = field(
|
|
136
|
+
"a duration — milliseconds, or a string such as '10m'",
|
|
137
|
+
(v) => typeof v === 'number' || typeof v === 'string',
|
|
138
|
+
{ type: ['number', 'string'] },
|
|
139
|
+
)
|
|
140
|
+
export const STRINGS = field(
|
|
141
|
+
'a list of strings',
|
|
142
|
+
(v) => Array.isArray(v) && v.every((item) => typeof item === 'string'),
|
|
143
|
+
{ type: 'array', items: { type: 'string' } },
|
|
144
|
+
)
|
|
145
|
+
export const STRING_RECORD = field(
|
|
146
|
+
'an object of string values',
|
|
147
|
+
(v) => typeof v === 'object' && v !== null && !Array.isArray(v) && Object.values(v).every((item) => typeof item === 'string'),
|
|
148
|
+
{ type: 'object', additionalProperties: { type: 'string' } },
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
/** Exactly one of these values. */
|
|
152
|
+
export function oneOf(...values: readonly (string | number | boolean)[]): OptionField {
|
|
153
|
+
return field(values.map((value) => JSON.stringify(value)).join(', '), (v) => values.includes(v as never), { enum: [...values] })
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** Any one of these fields. */
|
|
157
|
+
export function either(...fields: readonly OptionField[]): OptionField {
|
|
158
|
+
return field(
|
|
159
|
+
fields.map((f) => f.expected).join(', or '),
|
|
160
|
+
(v) => fields.some((f) => f.accepts(v)),
|
|
161
|
+
{ anyOf: fields.map((f) => f.json) },
|
|
162
|
+
)
|
|
163
|
+
}
|