@nifrajs/middleware 2.10.0 → 2.12.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/dist/_utils.d.ts +14 -2
- package/dist/_utils.d.ts.map +1 -1
- package/dist/_utils.js +25 -10
- package/dist/_utils.js.map +1 -1
- package/dist/basic-auth.d.ts +2 -2
- package/dist/basic-auth.d.ts.map +1 -1
- package/dist/basic-auth.js +2 -2
- package/dist/basic-auth.js.map +1 -1
- package/dist/cache-control.d.ts +1 -1
- package/dist/cache-control.d.ts.map +1 -1
- package/dist/cache-control.js +2 -2
- package/dist/cache-control.js.map +1 -1
- package/dist/cache.d.ts +2 -0
- package/dist/cache.d.ts.map +1 -1
- package/dist/cache.js +13 -1
- package/dist/cache.js.map +1 -1
- package/dist/combine.d.ts +11 -5
- package/dist/combine.d.ts.map +1 -1
- package/dist/combine.js +11 -4
- package/dist/combine.js.map +1 -1
- package/dist/compression.d.ts +8 -1
- package/dist/compression.d.ts.map +1 -1
- package/dist/compression.js +9 -2
- package/dist/compression.js.map +1 -1
- package/dist/csrf.d.ts +11 -4
- package/dist/csrf.d.ts.map +1 -1
- package/dist/csrf.js +19 -5
- package/dist/csrf.js.map +1 -1
- package/dist/durable-command.d.ts +2 -2
- package/dist/durable-command.d.ts.map +1 -1
- package/dist/durable-command.js +2 -2
- package/dist/durable-command.js.map +1 -1
- package/dist/etag.d.ts +2 -2
- package/dist/etag.d.ts.map +1 -1
- package/dist/etag.js +3 -3
- package/dist/etag.js.map +1 -1
- package/dist/healthcheck.d.ts +2 -1
- package/dist/healthcheck.d.ts.map +1 -1
- package/dist/healthcheck.js +5 -2
- package/dist/healthcheck.js.map +1 -1
- package/dist/idempotency.d.ts +51 -5
- package/dist/idempotency.d.ts.map +1 -1
- package/dist/idempotency.js +110 -13
- package/dist/idempotency.js.map +1 -1
- package/dist/jwt.d.ts +2 -2
- package/dist/jwt.d.ts.map +1 -1
- package/dist/jwt.js +14 -3
- package/dist/jwt.js.map +1 -1
- package/dist/language.d.ts +9 -1
- package/dist/language.d.ts.map +1 -1
- package/dist/language.js +8 -2
- package/dist/language.js.map +1 -1
- package/dist/logger.d.ts +9 -4
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +9 -5
- package/dist/logger.js.map +1 -1
- package/dist/method-override.d.ts +1 -1
- package/dist/method-override.d.ts.map +1 -1
- package/dist/method-override.js +2 -2
- package/dist/method-override.js.map +1 -1
- package/dist/openapi.d.ts +2 -1
- package/dist/openapi.d.ts.map +1 -1
- package/dist/openapi.js +4 -2
- package/dist/openapi.js.map +1 -1
- package/dist/pretty-json.d.ts +1 -1
- package/dist/pretty-json.d.ts.map +1 -1
- package/dist/pretty-json.js +2 -2
- package/dist/pretty-json.js.map +1 -1
- package/dist/problem-details.d.ts +1 -1
- package/dist/problem-details.d.ts.map +1 -1
- package/dist/problem-details.js +2 -2
- package/dist/problem-details.js.map +1 -1
- package/dist/rate-limit.d.ts.map +1 -1
- package/dist/rate-limit.js +6 -8
- package/dist/rate-limit.js.map +1 -1
- package/dist/request-id.d.ts +12 -4
- package/dist/request-id.d.ts.map +1 -1
- package/dist/request-id.js +62 -7
- package/dist/request-id.js.map +1 -1
- package/dist/security-headers.d.ts +11 -0
- package/dist/security-headers.d.ts.map +1 -1
- package/dist/security-headers.js +12 -0
- package/dist/security-headers.js.map +1 -1
- package/dist/timing.d.ts +4 -1
- package/dist/timing.d.ts.map +1 -1
- package/dist/timing.js +2 -2
- package/dist/timing.js.map +1 -1
- package/dist/token-auth.d.ts +2 -2
- package/dist/token-auth.d.ts.map +1 -1
- package/dist/token-auth.js +2 -2
- package/dist/token-auth.js.map +1 -1
- package/dist/trailing-slash.d.ts +2 -2
- package/dist/trailing-slash.d.ts.map +1 -1
- package/dist/trailing-slash.js +3 -3
- package/dist/trailing-slash.js.map +1 -1
- package/package.json +3 -3
- package/src/_utils.ts +27 -10
- package/src/basic-auth.ts +3 -3
- package/src/cache-control.ts +2 -2
- package/src/cache.ts +20 -1
- package/src/combine.ts +22 -10
- package/src/compression.ts +9 -2
- package/src/csrf.ts +31 -15
- package/src/durable-command.ts +3 -3
- package/src/etag.ts +3 -3
- package/src/healthcheck.ts +6 -3
- package/src/idempotency.ts +146 -18
- package/src/jwt.ts +22 -4
- package/src/language.ts +26 -16
- package/src/logger.ts +10 -6
- package/src/method-override.ts +2 -2
- package/src/openapi.ts +5 -3
- package/src/pretty-json.ts +2 -2
- package/src/problem-details.ts +2 -2
- package/src/rate-limit.ts +6 -8
- package/src/request-id.ts +63 -11
- package/src/security-headers.ts +23 -0
- package/src/timing.ts +3 -3
- package/src/token-auth.ts +3 -3
- package/src/trailing-slash.ts +3 -3
package/dist/request-id.js
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { defineContextPlugin } from "@nifrajs/core/server";
|
|
2
|
+
import { setNodeHeader, withHeaders } from "./_utils.js";
|
|
2
3
|
/**
|
|
3
|
-
* A {@link
|
|
4
|
+
* A {@link defineContextPlugin} plugin that gives every request a stable id: it reuses an inbound
|
|
4
5
|
* `x-request-id` (or generates one), exposes it on the handler context as **`c.requestId`** (typed,
|
|
5
|
-
* threaded by `derive`), and echoes it on
|
|
6
|
-
*
|
|
6
|
+
* threaded by `derive`), and echoes it on EVERY response leaving the app - handler-returned raw
|
|
7
|
+
* `Response`s and redirects, `onRequest` short-circuits (an auth 401, an admission 429), and
|
|
8
|
+
* framework-generated 404/405/422/timeout responses included. Core deliberately does not merge
|
|
9
|
+
* `c.set.headers` onto a handler-returned `Response`, so the echo lives in an app-global
|
|
10
|
+
* `onResponse` hook (paired with the derive through a `WeakMap` keyed on the request) with a node
|
|
11
|
+
* twin, keeping the Node adapter on its direct writer with no request-walk cost. Idempotent -
|
|
12
|
+
* applying it twice is a no-op.
|
|
7
13
|
*
|
|
8
14
|
* ```ts
|
|
9
15
|
* app.use(requestId()) // c.requestId available downstream; X-Request-Id on responses
|
|
@@ -11,10 +17,59 @@ import { definePlugin } from "@nifrajs/core/server";
|
|
|
11
17
|
*/
|
|
12
18
|
export function requestId(options = {}) {
|
|
13
19
|
const header = options.header ?? "x-request-id";
|
|
20
|
+
// The wire spelling: `c.set.headers` and the Node outcome record both store lowercase names, so
|
|
21
|
+
// the response twin's "already echoed?" probe and the derive's write must agree on it.
|
|
22
|
+
const wireHeader = header.toLowerCase();
|
|
14
23
|
const generate = options.generate ?? (() => crypto.randomUUID());
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
24
|
+
// Web-lane derive-to-response pairing for GENERATED ids (an inbound one is re-readable from the
|
|
25
|
+
// request in onResponse). Entries are deleted in onResponse; a Node-lane derive also writes here
|
|
26
|
+
// but its id travels via the `c.set` write into the outcome record, and the weakly-held entry is
|
|
27
|
+
// simply collected with the request.
|
|
28
|
+
const ids = new WeakMap();
|
|
29
|
+
// defineContextPlugin, not definePlugin: the latter would infer `app` as `AnyServer` here and make
|
|
30
|
+
// `.use(requestId())` return `Server<any, any>`, erasing the route registry for every route the app
|
|
31
|
+
// declares afterwards - and with it the end-to-end types of any typed client built from the app.
|
|
32
|
+
return defineContextPlugin("requestId", (app) => app
|
|
33
|
+
// No `name` on the bundle: the surrounding plugin's "requestId" dedupe already makes the
|
|
34
|
+
// install idempotent, and a bundle named the same would be skipped as already-installed.
|
|
35
|
+
// Response hooks ONLY - no onRequest, so the bundle adds nothing to the request walk and the
|
|
36
|
+
// Node adapter's native request lane stays engaged without needing a no-op twin.
|
|
37
|
+
.use({
|
|
38
|
+
onResponse(res, req) {
|
|
39
|
+
const id = ids.get(req);
|
|
40
|
+
ids.delete(req);
|
|
41
|
+
// Already carried (the derive's `c.set` write merged into a framework-rendered response,
|
|
42
|
+
// or the handler set it explicitly): leave the response untouched.
|
|
43
|
+
if (res.headers.get(header) !== null)
|
|
44
|
+
return res;
|
|
45
|
+
// The id the derive generated; else the inbound header, then a fresh id - so a response
|
|
46
|
+
// no derive saw (a 404, an earlier middleware's short-circuit) is still covered.
|
|
47
|
+
const echo = id ?? req.headers.get(header) ?? generate();
|
|
48
|
+
return withHeaders(res, (headers) => {
|
|
49
|
+
headers.set(header, echo);
|
|
50
|
+
});
|
|
51
|
+
},
|
|
52
|
+
onNodeResponse(res, req) {
|
|
53
|
+
// The common node path (a handler route, whose derive wrote the id through `c.set`) pays
|
|
54
|
+
// exactly this one record-property check. Anything the derive never saw - a 404, a native
|
|
55
|
+
// short-circuit - gets the inbound id or a fresh one here.
|
|
56
|
+
if (res.headers?.[wireHeader] !== undefined)
|
|
57
|
+
return;
|
|
58
|
+
setNodeHeader(res, wireHeader, req.header(header) ?? generate());
|
|
59
|
+
},
|
|
60
|
+
})
|
|
61
|
+
.derive((c) => {
|
|
62
|
+
// Inbound header first (readable on every lane without materializing a Request); generate
|
|
63
|
+
// otherwise, ONCE. A generated id is stored keyed on `c.req` so the Web onResponse hook
|
|
64
|
+
// echoes the exact id a raw-Response handler saw as `c.requestId`; the `c.set` write below
|
|
65
|
+
// carries it into a framework-rendered result on every lane - on Node-direct, into the
|
|
66
|
+
// outcome record, where the response twin sees it as already echoed. Header and
|
|
67
|
+
// `c.requestId` are therefore the same value everywhere with a single generation.
|
|
68
|
+
const inbound = c.header(header);
|
|
69
|
+
const id = inbound ?? generate();
|
|
70
|
+
if (inbound === null)
|
|
71
|
+
ids.set(c.req, id);
|
|
72
|
+
c.set.headers[wireHeader] = id;
|
|
18
73
|
return { requestId: id };
|
|
19
74
|
}));
|
|
20
75
|
}
|
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,
|
|
1
|
+
{"version":3,"file":"request-id.js","sourceRoot":"","sources":["../src/request-id.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,mBAAmB,EAAE,MAAM,sBAAsB,CAAA;AAC9E,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AASxD;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,SAAS,CAAC,UAA4B,EAAE;IACtD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,cAAc,CAAA;IAC/C,gGAAgG;IAChG,uFAAuF;IACvF,MAAM,UAAU,GAAG,MAAM,CAAC,WAAW,EAAE,CAAA;IACvC,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC,CAAA;IAChE,gGAAgG;IAChG,iGAAiG;IACjG,iGAAiG;IACjG,qCAAqC;IACrC,MAAM,GAAG,GAAG,IAAI,OAAO,EAAmB,CAAA;IAC1C,mGAAmG;IACnG,oGAAoG;IACpG,iGAAiG;IACjG,OAAO,mBAAmB,CAAwB,WAAW,EAAE,CAAC,GAAG,EAAE,EAAE,CACrE,GAAG;QACD,yFAAyF;QACzF,yFAAyF;QACzF,6FAA6F;QAC7F,iFAAiF;SAChF,GAAG,CAAC;QACH,UAAU,CAAC,GAAG,EAAE,GAAG;YACjB,MAAM,EAAE,GAAG,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;YACvB,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;YACf,yFAAyF;YACzF,mEAAmE;YACnE,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,IAAI;gBAAE,OAAO,GAAG,CAAA;YAChD,wFAAwF;YACxF,iFAAiF;YACjF,MAAM,IAAI,GAAG,EAAE,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAA;YACxD,OAAO,WAAW,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,EAAE;gBAClC,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;YAC3B,CAAC,CAAC,CAAA;QACJ,CAAC;QACD,cAAc,CAAC,GAAG,EAAE,GAAG;YACrB,yFAAyF;YACzF,0FAA0F;YAC1F,2DAA2D;YAC3D,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC,UAAU,CAAC,KAAK,SAAS;gBAAE,OAAM;YACnD,aAAa,CAAC,GAAG,EAAE,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC,CAAA;QAClE,CAAC;KACF,CAAC;SACD,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE;QACZ,0FAA0F;QAC1F,wFAAwF;QACxF,2FAA2F;QAC3F,uFAAuF;QACvF,gFAAgF;QAChF,kFAAkF;QAClF,MAAM,OAAO,GAAG,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;QAChC,MAAM,EAAE,GAAG,OAAO,IAAI,QAAQ,EAAE,CAAA;QAChC,IAAI,OAAO,KAAK,IAAI;YAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAA;QACxC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,EAAE,CAAA;QAC9B,OAAO,EAAE,SAAS,EAAE,EAAE,EAAE,CAAA;IAC1B,CAAC,CAAC,CACL,CAAA;AACH,CAAC"}
|
|
@@ -12,6 +12,17 @@ export interface SecurityHeadersOptions {
|
|
|
12
12
|
readonly frameOptions?: "DENY" | "SAMEORIGIN";
|
|
13
13
|
/** `Referrer-Policy`. Default `"no-referrer"`. */
|
|
14
14
|
readonly referrerPolicy?: string;
|
|
15
|
+
/** `Cross-Origin-Opener-Policy`. Off by default - isolates the browsing context group; opt in
|
|
16
|
+
* when the app tolerates losing `window.opener` links from cross-origin pages. */
|
|
17
|
+
readonly crossOriginOpenerPolicy?: "same-origin" | "same-origin-allow-popups" | "unsafe-none";
|
|
18
|
+
/** `Cross-Origin-Embedder-Policy`. Off by default - `require-corp` breaks embedding of
|
|
19
|
+
* cross-origin resources that do not opt in, so this is a deliberate decision. */
|
|
20
|
+
readonly crossOriginEmbedderPolicy?: "require-corp" | "credentialless" | "unsafe-none";
|
|
21
|
+
/** `Cross-Origin-Resource-Policy`. Off by default (app-specific: assets meant for other
|
|
22
|
+
* origins need `cross-origin`). */
|
|
23
|
+
readonly crossOriginResourcePolicy?: "same-origin" | "same-site" | "cross-origin";
|
|
24
|
+
/** `Permissions-Policy` value, e.g. `"camera=(), geolocation=()"`. Off by default (app-specific). */
|
|
25
|
+
readonly permissionsPolicy?: string;
|
|
15
26
|
}
|
|
16
27
|
/**
|
|
17
28
|
* A safe-by-default set of response security headers, covering errors and 404s too:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"security-headers.d.ts","sourceRoot":"","sources":["../src/security-headers.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AAEtD,MAAM,WAAW,sBAAsB;IACrC,+FAA+F;IAC/F,QAAQ,CAAC,IAAI,CAAC,EAAE;QACd,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;QACvB,QAAQ,CAAC,iBAAiB,CAAC,EAAE,OAAO,CAAA;QACpC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAC3B,CAAA;IACD,sEAAsE;IACtE,QAAQ,CAAC,qBAAqB,CAAC,EAAE,MAAM,CAAA;IACvC,2CAA2C;IAC3C,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,YAAY,CAAA;IAC7C,kDAAkD;IAClD,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAA;
|
|
1
|
+
{"version":3,"file":"security-headers.d.ts","sourceRoot":"","sources":["../src/security-headers.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAA;AAEtD,MAAM,WAAW,sBAAsB;IACrC,+FAA+F;IAC/F,QAAQ,CAAC,IAAI,CAAC,EAAE;QACd,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;QACvB,QAAQ,CAAC,iBAAiB,CAAC,EAAE,OAAO,CAAA;QACpC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;KAC3B,CAAA;IACD,sEAAsE;IACtE,QAAQ,CAAC,qBAAqB,CAAC,EAAE,MAAM,CAAA;IACvC,2CAA2C;IAC3C,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,YAAY,CAAA;IAC7C,kDAAkD;IAClD,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAA;IAChC;sFACkF;IAClF,QAAQ,CAAC,uBAAuB,CAAC,EAAE,aAAa,GAAG,0BAA0B,GAAG,aAAa,CAAA;IAC7F;sFACkF;IAClF,QAAQ,CAAC,yBAAyB,CAAC,EAAE,cAAc,GAAG,gBAAgB,GAAG,aAAa,CAAA;IACtF;uCACmC;IACnC,QAAQ,CAAC,yBAAyB,CAAC,EAAE,aAAa,GAAG,WAAW,GAAG,cAAc,CAAA;IACjF,qGAAqG;IACrG,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,CAAA;CACpC;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,OAAO,GAAE,sBAA2B,GAAG,UAAU,CA4ChF"}
|
package/dist/security-headers.js
CHANGED
|
@@ -31,6 +31,18 @@ export function securityHeaders(options = {}) {
|
|
|
31
31
|
declared["strict-transport-security"] = hstsValue;
|
|
32
32
|
if (csp !== undefined)
|
|
33
33
|
declared["content-security-policy"] = csp;
|
|
34
|
+
if (options.crossOriginOpenerPolicy !== undefined) {
|
|
35
|
+
declared["cross-origin-opener-policy"] = options.crossOriginOpenerPolicy;
|
|
36
|
+
}
|
|
37
|
+
if (options.crossOriginEmbedderPolicy !== undefined) {
|
|
38
|
+
declared["cross-origin-embedder-policy"] = options.crossOriginEmbedderPolicy;
|
|
39
|
+
}
|
|
40
|
+
if (options.crossOriginResourcePolicy !== undefined) {
|
|
41
|
+
declared["cross-origin-resource-policy"] = options.crossOriginResourcePolicy;
|
|
42
|
+
}
|
|
43
|
+
if (options.permissionsPolicy !== undefined) {
|
|
44
|
+
declared["permissions-policy"] = options.permissionsPolicy;
|
|
45
|
+
}
|
|
34
46
|
return withRouteAssurance({
|
|
35
47
|
name: "security-headers",
|
|
36
48
|
responseHeaders: declared,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"security-headers.js","sourceRoot":"","sources":["../src/security-headers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;
|
|
1
|
+
{"version":3,"file":"security-headers.js","sourceRoot":"","sources":["../src/security-headers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AA6B7E;;;;;;;;;GASG;AACH,MAAM,UAAU,eAAe,CAAC,UAAkC,EAAE;IAClE,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,MAAM,CAAA;IACnD,MAAM,cAAc,GAAG,OAAO,CAAC,cAAc,IAAI,aAAa,CAAA;IAC9D,MAAM,GAAG,GAAG,OAAO,CAAC,qBAAqB,CAAA;IAEzC,IAAI,SAA6B,CAAA;IACjC,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC/B,MAAM,KAAK,GAAG,CAAC,WAAW,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,CAAA;QAChD,IAAI,OAAO,CAAC,IAAI,CAAC,iBAAiB;YAAE,KAAK,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAA;QACnE,IAAI,OAAO,CAAC,IAAI,CAAC,OAAO;YAAE,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;QAC/C,SAAS,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;IAC9B,CAAC;IAED,MAAM,QAAQ,GAA2B;QACvC,wBAAwB,EAAE,SAAS;QACnC,iBAAiB,EAAE,YAAY;QAC/B,iBAAiB,EAAE,cAAc;KAClC,CAAA;IACD,IAAI,SAAS,KAAK,SAAS;QAAE,QAAQ,CAAC,2BAA2B,CAAC,GAAG,SAAS,CAAA;IAC9E,IAAI,GAAG,KAAK,SAAS;QAAE,QAAQ,CAAC,yBAAyB,CAAC,GAAG,GAAG,CAAA;IAChE,IAAI,OAAO,CAAC,uBAAuB,KAAK,SAAS,EAAE,CAAC;QAClD,QAAQ,CAAC,4BAA4B,CAAC,GAAG,OAAO,CAAC,uBAAuB,CAAA;IAC1E,CAAC;IACD,IAAI,OAAO,CAAC,yBAAyB,KAAK,SAAS,EAAE,CAAC;QACpD,QAAQ,CAAC,8BAA8B,CAAC,GAAG,OAAO,CAAC,yBAAyB,CAAA;IAC9E,CAAC;IACD,IAAI,OAAO,CAAC,yBAAyB,KAAK,SAAS,EAAE,CAAC;QACpD,QAAQ,CAAC,8BAA8B,CAAC,GAAG,OAAO,CAAC,yBAAyB,CAAA;IAC9E,CAAC;IACD,IAAI,OAAO,CAAC,iBAAiB,KAAK,SAAS,EAAE,CAAC;QAC5C,QAAQ,CAAC,oBAAoB,CAAC,GAAG,OAAO,CAAC,iBAAiB,CAAA;IAC5D,CAAC;IAED,OAAO,kBAAkB,CACvB;QACE,IAAI,EAAE,kBAAkB;QACxB,eAAe,EAAE,QAAQ;KAC1B,EACD;QACE,EAAE,EAAE,eAAe,CAAC,gBAAgB;QACpC,MAAM,EAAE,kBAAkB;QAC1B,KAAK,EAAE,QAAQ;KAChB,CACF,CAAA;AACH,CAAC"}
|
package/dist/timing.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ContextPlugin } from "@nifrajs/core/server";
|
|
1
2
|
export interface TimingMetric {
|
|
2
3
|
readonly name: string;
|
|
3
4
|
readonly duration: number;
|
|
@@ -21,5 +22,7 @@ export interface TimingOptions {
|
|
|
21
22
|
* Put request-rewriting middleware (for example `methodOverride`) before `timing()` so timing is
|
|
22
23
|
* attached to the final routed request.
|
|
23
24
|
*/
|
|
24
|
-
export declare function timing(options?: TimingOptions):
|
|
25
|
+
export declare function timing(options?: TimingOptions): ContextPlugin<{
|
|
26
|
+
timing: TimingControls;
|
|
27
|
+
}>;
|
|
25
28
|
//# sourceMappingURL=timing.d.ts.map
|
package/dist/timing.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"timing.d.ts","sourceRoot":"","sources":["../src/timing.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"timing.d.ts","sourceRoot":"","sources":["../src/timing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,aAAa,EAAuB,MAAM,sBAAsB,CAAA;AAG9E,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAC9B;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxD,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACnE;AAED,MAAM,WAAW,aAAa;IAC5B,8FAA8F;IAC9F,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;IAC/B,iDAAiD;IACjD,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,8DAA8D;IAC9D,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,CAAC,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,CAAA;CAC7D;AAqCD;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,OAAO,GAAE,aAAkB,GAAG,aAAa,CAAC;IAAE,MAAM,EAAE,cAAc,CAAA;CAAE,CAAC,CAoE7F"}
|
package/dist/timing.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { defineContextPlugin } from "@nifrajs/core/server";
|
|
2
2
|
import { withHeaders } from "./_utils.js";
|
|
3
3
|
const TOKEN = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
|
|
4
4
|
function assertMetricName(name) {
|
|
@@ -73,7 +73,7 @@ export function timing(options = {}) {
|
|
|
73
73
|
}
|
|
74
74
|
},
|
|
75
75
|
});
|
|
76
|
-
return
|
|
76
|
+
return defineContextPlugin("timing", (app) => app
|
|
77
77
|
.onRequest((req) => {
|
|
78
78
|
if (!isEnabled(req))
|
|
79
79
|
return undefined;
|
package/dist/timing.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"timing.js","sourceRoot":"","sources":["../src/timing.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"timing.js","sourceRoot":"","sources":["../src/timing.ts"],"names":[],"mappings":"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"}
|
package/dist/token-auth.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type IdentityPlugin } from "@nifrajs/core/server";
|
|
2
2
|
type MaybePromise<T> = T | Promise<T>;
|
|
3
3
|
/**
|
|
4
4
|
* A token-auth plugin (`bearer` / `apiKey`). Apply it with `app.use(auth)` - it rejects unauthorized
|
|
@@ -16,7 +16,7 @@ type MaybePromise<T> = T | Promise<T>;
|
|
|
16
16
|
* const app = server().use(auth).get("/me", (c) => auth.requirePrincipal(c.req))
|
|
17
17
|
* ```
|
|
18
18
|
*/
|
|
19
|
-
export type AuthPlugin<P> =
|
|
19
|
+
export type AuthPlugin<P> = IdentityPlugin & {
|
|
20
20
|
/** The verified principal for this request, or `null` (no/invalid token in `optional` mode). */
|
|
21
21
|
principal(request: Request): P | null;
|
|
22
22
|
/** The verified principal, or **throws a `401` `Response`** when absent. */
|
package/dist/token-auth.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"token-auth.d.ts","sourceRoot":"","sources":["../src/token-auth.ts"],"names":[],"mappings":"AACA,OAAO,
|
|
1
|
+
{"version":3,"file":"token-auth.d.ts","sourceRoot":"","sources":["../src/token-auth.ts"],"names":[],"mappings":"AACA,OAAO,EAAwB,KAAK,cAAc,EAAE,MAAM,sBAAsB,CAAA;AAEhF,KAAK,YAAY,CAAC,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;AAErC;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,UAAU,CAAC,CAAC,IAAI,cAAc,GAAG;IAC3C,gGAAgG;IAChG,SAAS,CAAC,OAAO,EAAE,OAAO,GAAG,CAAC,GAAG,IAAI,CAAA;IACrC,4EAA4E;IAC5E,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,CAAC,CAAA;CACtC,CAAA;AAqDD,MAAM,WAAW,aAAa,CAAC,CAAC;IAC9B;uGACmG;IACnG,QAAQ,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,YAAY,CAAC,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC,CAAA;IACtE,yGAAyG;IACzG,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAC3B,mFAAmF;IACnF,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CACxB;AAED;;;;GAIG;AACH,wBAAgB,MAAM,CAAC,CAAC,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAYlE;AAED,MAAM,WAAW,mBAAmB,CAAC,CAAC;IACpC,sFAAsF;IACtF,QAAQ,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,YAAY,CAAC,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC,CAAA;IACpE,sDAAsD;IACtD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAC5B;AAED,MAAM,WAAW,mBAAmB;IAClC;0EACsE;IACtE,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;IAChC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAC5B;AAoCD;;;;;;;GAOG;AACH,wBAAgB,MAAM,CAAC,OAAO,EAAE,mBAAmB,GAAG,UAAU,CAAC,MAAM,CAAC,CAAA;AACxE,wBAAgB,MAAM,CAAC,CAAC,EAAE,OAAO,EAAE,mBAAmB,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAAA"}
|
package/dist/token-auth.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { NIFRA_ASSURANCE, withRouteAssurance } from "@nifrajs/core/assurance";
|
|
2
|
-
import {
|
|
2
|
+
import { defineIdentityPlugin } from "@nifrajs/core/server";
|
|
3
3
|
function createTokenAuth(config) {
|
|
4
4
|
// Per-request principal cache: verify runs once (in beforeHandle), reads are free + can't desync.
|
|
5
5
|
const store = new WeakMap();
|
|
@@ -7,7 +7,7 @@ function createTokenAuth(config) {
|
|
|
7
7
|
status: 401,
|
|
8
8
|
headers: config.challenge !== undefined ? { "www-authenticate": config.challenge } : {},
|
|
9
9
|
});
|
|
10
|
-
const plugin =
|
|
10
|
+
const plugin = defineIdentityPlugin(config.name, (app) => app.beforeHandle(async (c) => {
|
|
11
11
|
const token = config.extract(c.req);
|
|
12
12
|
// Empty string is treated as "no credential" - never passed to verify.
|
|
13
13
|
const principal = token !== undefined && token !== "" ? await config.verify(token) : null;
|
package/dist/token-auth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"token-auth.js","sourceRoot":"","sources":["../src/token-auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAC7E,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"token-auth.js","sourceRoot":"","sources":["../src/token-auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAC7E,OAAO,EAAE,oBAAoB,EAAuB,MAAM,sBAAsB,CAAA;AAsChF,SAAS,eAAe,CAAI,MAA0B;IACpD,kGAAkG;IAClG,MAAM,KAAK,GAAG,IAAI,OAAO,EAAc,CAAA;IACvC,MAAM,MAAM,GAAG,GAAa,EAAE,CAC5B,QAAQ,CAAC,IAAI,CACX,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,cAAc,EAAE,EACpC;QACE,MAAM,EAAE,GAAG;QACX,OAAO,EAAE,MAAM,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,kBAAkB,EAAE,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE;KACxF,CACF,CAAA;IACH,MAAM,MAAM,GAAG,oBAAoB,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,EAAE,CACvD,GAAG,CAAC,YAAY,CAAC,KAAK,EAAE,CAA4B,EAAE,EAAE;QACtD,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;QACnC,uEAAuE;QACvE,MAAM,SAAS,GAAG,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,MAAM,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;QACzF,IAAI,SAAS,KAAK,IAAI,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;YAClD,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,SAAS,CAAC,CAAA;YAC3B,OAAO,SAAS,CAAA,CAAC,uCAAuC;QAC1D,CAAC;QACD,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,EAAE,CAAA,CAAC,yCAAyC;IACzF,CAAC,CAAC,CACH,CAAA;IACD,MAAM,YAAY,GAAG,MAAM,CAAC,QAAQ;QAClC,CAAC,CAAC,MAAM;QACR,CAAC,CAAC,kBAAkB,CAAC,MAAM,EAAE;YACzB,EAAE,EAAE,eAAe,CAAC,aAAa;YACjC,MAAM,EAAE,MAAM,CAAC,IAAI;YACnB,KAAK,EAAE,YAAY;SACpB,CAAC,CAAA;IACN,OAAO,MAAM,CAAC,MAAM,CAAC,YAAY,EAAE;QACjC,SAAS,EAAE,CAAC,OAAgB,EAAY,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,IAAI;QACrE,gBAAgB,EAAE,CAAC,OAAgB,EAAK,EAAE;YACxC,MAAM,SAAS,GAAG,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;YACpC,IAAI,SAAS,KAAK,SAAS;gBAAE,MAAM,MAAM,EAAE,CAAA;YAC3C,OAAO,SAAS,CAAA;QAClB,CAAC;KACF,CAAkB,CAAA;AACrB,CAAC;AAYD;;;;GAIG;AACH,MAAM,UAAU,MAAM,CAAI,OAAyB;IACjD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,KAAK,CAAA;IACpC,OAAO,eAAe,CAAC;QACrB,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE,CAAC,OAAO,EAAE,EAAE;YACnB,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAA;YACnD,OAAO,MAAM,EAAE,UAAU,CAAC,SAAS,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAA;QACpF,CAAC;QACD,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,QAAQ,EAAE,OAAO,CAAC,QAAQ,KAAK,IAAI;QACnC,SAAS,EAAE,iBAAiB,KAAK,GAAG;KACrC,CAAC,CAAA;AACJ,CAAC;AAkBD,MAAM,WAAW,GAAG,IAAI,WAAW,EAAE,CAAA;AAErC,KAAK,UAAU,MAAM,CAAC,KAAa;IACjC,OAAO,IAAI,UAAU,CAAC,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;AACzF,CAAC;AAED;oEACoE;AACpE,SAAS,oBAAoB,CAAC,CAAa,EAAE,CAAa;IACxD,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM;QAAE,OAAO,KAAK,CAAA;IACvC,IAAI,IAAI,GAAG,CAAC,CAAA;IACZ,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE;QAAE,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAA;IACpE,OAAO,IAAI,KAAK,CAAC,CAAA;AACnB,CAAC;AAED;;;;;GAKG;AACH,SAAS,eAAe,CAAC,IAAuB;IAC9C,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;IAChE,OAAO,KAAK,EAAE,SAAS,EAAE,EAAE;QACzB,MAAM,eAAe,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,CAAA;QAC/C,IAAI,OAAO,GAAG,KAAK,CAAA;QACnB,KAAK,MAAM,MAAM,IAAI,MAAM,YAAY,EAAE,CAAC;YACxC,kGAAkG;YAClG,IAAI,oBAAoB,CAAC,eAAe,EAAE,MAAM,CAAC;gBAAE,OAAO,GAAG,IAAI,CAAA;QACnE,CAAC;QACD,OAAO,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAA;IACnC,CAAC,CAAA;AACH,CAAC;AAYD,MAAM,UAAU,MAAM,CACpB,OAAqD;IAErD,MAAM,MAAM,GAAG,CAAC,OAAO,CAAC,MAAM,IAAI,WAAW,CAAC,CAAC,WAAW,EAAE,CAAA;IAC5D,MAAM,MAAM,GAAG,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAA;IACjF,OAAO,eAAe,CAAa;QACjC,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,SAAS;QAC9D,MAAM;QACN,QAAQ,EAAE,OAAO,CAAC,QAAQ,KAAK,IAAI;KACpC,CAAC,CAAA;AACJ,CAAC"}
|
package/dist/trailing-slash.d.ts
CHANGED
|
@@ -14,10 +14,10 @@ export interface TrailingSlashOptions {
|
|
|
14
14
|
* Remove trailing slashes from non-root paths. Redirect mode is the production default because it
|
|
15
15
|
* canonicalizes URLs for clients and caches; rewrite mode is available for compatibility migrations.
|
|
16
16
|
*/
|
|
17
|
-
export declare function trimTrailingSlash(options?: TrailingSlashOptions): import("@nifrajs/core
|
|
17
|
+
export declare function trimTrailingSlash(options?: TrailingSlashOptions): import("@nifrajs/core").IdentityPlugin;
|
|
18
18
|
/**
|
|
19
19
|
* Append a trailing slash to non-root paths. By default it skips file-looking paths such as
|
|
20
20
|
* `/app.css`, which keeps static assets and extensionful API routes stable.
|
|
21
21
|
*/
|
|
22
|
-
export declare function appendTrailingSlash(options?: TrailingSlashOptions): import("@nifrajs/core
|
|
22
|
+
export declare function appendTrailingSlash(options?: TrailingSlashOptions): import("@nifrajs/core").IdentityPlugin;
|
|
23
23
|
//# sourceMappingURL=trailing-slash.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"trailing-slash.d.ts","sourceRoot":"","sources":["../src/trailing-slash.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,oBAAoB;IACnC,mEAAmE;IACnE,QAAQ,CAAC,IAAI,CAAC,EAAE,UAAU,GAAG,SAAS,CAAA;IACtC,6EAA6E;IAC7E,QAAQ,CAAC,MAAM,CAAC,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAA;IACvC,uGAAuG;IACvG,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACpC,oGAAoG;IACpG,QAAQ,CAAC,oBAAoB,CAAC,EAAE,OAAO,CAAA;IACvC,mCAAmC;IACnC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,OAAO,CAAA;CAC9D;AAsCD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,GAAE,oBAAyB,
|
|
1
|
+
{"version":3,"file":"trailing-slash.d.ts","sourceRoot":"","sources":["../src/trailing-slash.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,oBAAoB;IACnC,mEAAmE;IACnE,QAAQ,CAAC,IAAI,CAAC,EAAE,UAAU,GAAG,SAAS,CAAA;IACtC,6EAA6E;IAC7E,QAAQ,CAAC,MAAM,CAAC,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAA;IACvC,uGAAuG;IACvG,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACpC,oGAAoG;IACpG,QAAQ,CAAC,oBAAoB,CAAC,EAAE,OAAO,CAAA;IACvC,mCAAmC;IACnC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,KAAK,OAAO,CAAA;CAC9D;AAsCD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,GAAE,oBAAyB,0CAenE;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,oBAAyB,0CAiBrE"}
|
package/dist/trailing-slash.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { defineRouterPlugin } from "@nifrajs/core/server";
|
|
2
2
|
const DEFAULT_METHODS = ["GET", "HEAD"];
|
|
3
3
|
const REDIRECT_STATUSES = new Set([301, 302, 307, 308]);
|
|
4
4
|
function validateStatus(status) {
|
|
@@ -33,7 +33,7 @@ export function trimTrailingSlash(options = {}) {
|
|
|
33
33
|
const status = options.status ?? 308;
|
|
34
34
|
validateStatus(status);
|
|
35
35
|
const methods = methodsOf(options.methods);
|
|
36
|
-
return
|
|
36
|
+
return defineRouterPlugin("trimTrailingSlash", (app) => app.onRequest((req) => {
|
|
37
37
|
if (!methods.has(req.method.toUpperCase()))
|
|
38
38
|
return undefined;
|
|
39
39
|
const path = new URL(req.url).pathname;
|
|
@@ -54,7 +54,7 @@ export function appendTrailingSlash(options = {}) {
|
|
|
54
54
|
const ignoreFileExtensions = options.ignoreFileExtensions !== false;
|
|
55
55
|
validateStatus(status);
|
|
56
56
|
const methods = methodsOf(options.methods);
|
|
57
|
-
return
|
|
57
|
+
return defineRouterPlugin("appendTrailingSlash", (app) => app.onRequest((req) => {
|
|
58
58
|
if (!methods.has(req.method.toUpperCase()))
|
|
59
59
|
return undefined;
|
|
60
60
|
const path = new URL(req.url).pathname;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"trailing-slash.js","sourceRoot":"","sources":["../src/trailing-slash.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"trailing-slash.js","sourceRoot":"","sources":["../src/trailing-slash.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nifrajs/middleware",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.12.0",
|
|
4
4
|
"description": "Composable middleware for nifra - auth, CSRF, JWT/JWKS, IP restriction, CORS, body limits, response cache, timing, and operational helpers.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bun",
|
|
@@ -46,10 +46,10 @@
|
|
|
46
46
|
"access": "public"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"@nifrajs/schema": "2.
|
|
49
|
+
"@nifrajs/schema": "2.12.0"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
|
-
"@nifrajs/core": "^2.
|
|
52
|
+
"@nifrajs/core": "^2.12.0"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@nifrajs/core": "workspace:*",
|
package/src/_utils.ts
CHANGED
|
@@ -162,17 +162,34 @@ export function withHeaders(res: Response, apply: (headers: Headers) => void): R
|
|
|
162
162
|
return res
|
|
163
163
|
}
|
|
164
164
|
|
|
165
|
-
/**
|
|
166
|
-
|
|
165
|
+
/**
|
|
166
|
+
* Set one Node-direct response header without constructing a Web Response. `name` must already be
|
|
167
|
+
* the lowercase wire spelling - the record documents that contract, and it is what `Headers` emits
|
|
168
|
+
* on every other runtime.
|
|
169
|
+
*
|
|
170
|
+
* The outcome's own record is used AS-IS. Re-homing it into a null-prototype object - the obvious
|
|
171
|
+
* way to make `__proto__` inert - demotes it to V8's dictionary mode for the rest of the request,
|
|
172
|
+
* and everything downstream then pays dictionary lookups on the response hot path: the direct
|
|
173
|
+
* writer's key walk, and Node's own `_storeHeader` walk over every name. Values here are strings and
|
|
174
|
+
* string arrays, so assigning one through the inherited `__proto__` setter is a spec-level no-op
|
|
175
|
+
* rather than pollution; the single name that setter would swallow is defined as an own data
|
|
176
|
+
* property instead. Same trade core's portable header view makes over the same record.
|
|
177
|
+
*/
|
|
178
|
+
export function setNodeHeader(
|
|
167
179
|
res: NodeResponseContext,
|
|
168
|
-
|
|
180
|
+
name: string,
|
|
181
|
+
value: string | readonly string[],
|
|
169
182
|
): void {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
183
|
+
res.headers ??= {}
|
|
184
|
+
const headers = res.headers
|
|
185
|
+
if (name === "__proto__") {
|
|
186
|
+
Object.defineProperty(headers, name, {
|
|
187
|
+
value,
|
|
188
|
+
writable: true,
|
|
189
|
+
enumerable: true,
|
|
190
|
+
configurable: true,
|
|
191
|
+
})
|
|
192
|
+
return
|
|
176
193
|
}
|
|
177
|
-
|
|
194
|
+
headers[name] = value
|
|
178
195
|
}
|
package/src/basic-auth.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { NIFRA_ASSURANCE, withRouteAssurance } from "@nifrajs/core/assurance"
|
|
2
|
-
import {
|
|
2
|
+
import { defineIdentityPlugin, type IdentityPlugin } from "@nifrajs/core/server"
|
|
3
3
|
import {
|
|
4
4
|
decodeBase64,
|
|
5
5
|
jsonError,
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
timingSafeEqualBytes,
|
|
10
10
|
} from "./_utils.ts"
|
|
11
11
|
|
|
12
|
-
export type BasicAuthPlugin<P> =
|
|
12
|
+
export type BasicAuthPlugin<P> = IdentityPlugin & {
|
|
13
13
|
principal(request: Request): P | null
|
|
14
14
|
requirePrincipal(request: Request): P
|
|
15
15
|
}
|
|
@@ -87,7 +87,7 @@ export function basicAuth<P>(
|
|
|
87
87
|
? Promise.resolve(options.verify)
|
|
88
88
|
: staticVerifier(options.username, options.password, options.principal ?? options.username)
|
|
89
89
|
|
|
90
|
-
const plugin =
|
|
90
|
+
const plugin = defineIdentityPlugin("basicAuth", (app) =>
|
|
91
91
|
app.beforeHandle(async (c: { readonly req: Request }) => {
|
|
92
92
|
const parsed = credentials(c.req)
|
|
93
93
|
const principal =
|
package/src/cache-control.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { defineRouterPlugin, type Middleware } from "@nifrajs/core/server"
|
|
2
2
|
import { withHeaders } from "./_utils.ts"
|
|
3
3
|
|
|
4
4
|
export interface CacheControlOptions {
|
|
@@ -30,7 +30,7 @@ export function cacheControl(
|
|
|
30
30
|
const methods = new Set((options.methods ?? ["GET", "HEAD"]).map((m) => m.toUpperCase()))
|
|
31
31
|
const statusOk = options.status ?? ((status: number) => status >= 200 && status < 300)
|
|
32
32
|
const respectExisting = options.respectExisting !== false
|
|
33
|
-
return
|
|
33
|
+
return defineRouterPlugin("cacheControl", (app) => {
|
|
34
34
|
// A fixed directive needs nothing from the request beyond the method, so it ships as one
|
|
35
35
|
// portable header hook. A dynamic directive's resolver takes a real `Request`, which only the
|
|
36
36
|
// Web response walk carries - it keeps the full `onResponse` contract (and its cost).
|
package/src/cache.ts
CHANGED
|
@@ -89,6 +89,8 @@ export interface CacheOptions {
|
|
|
89
89
|
* personalized response (RFC 9111 §3.5), or it would be replayed to other users. Leave this off unless
|
|
90
90
|
* the cached route is genuinely the same for every caller. */
|
|
91
91
|
readonly cacheAuthenticated?: boolean
|
|
92
|
+
/** Credential headers that make a request private. Defaults to Authorization, Cookie, and x-api-key. */
|
|
93
|
+
readonly authenticatedHeaders?: readonly string[]
|
|
92
94
|
/** Response header for cache status. Default `"x-nifra-cache"`; set `false` to disable. */
|
|
93
95
|
readonly cacheStatusHeader?: string | false
|
|
94
96
|
}
|
|
@@ -199,12 +201,17 @@ export function cache(options: CacheOptions): Middleware {
|
|
|
199
201
|
const respectResponseCacheControl = options.respectResponseCacheControl !== false
|
|
200
202
|
const cacheSetCookie = options.cacheSetCookie === true
|
|
201
203
|
const cacheAuthenticated = options.cacheAuthenticated === true
|
|
204
|
+
const authenticatedHeaders = (
|
|
205
|
+
options.authenticatedHeaders ?? ["authorization", "cookie", "x-api-key"]
|
|
206
|
+
).map((header) => header.toLowerCase())
|
|
202
207
|
const cacheStatusHeader =
|
|
203
208
|
options.cacheStatusHeader === undefined ? "x-nifra-cache" : options.cacheStatusHeader
|
|
204
209
|
if (cacheStatusHeader !== false && cacheStatusHeader.trim() === "") {
|
|
205
210
|
throw new Error("cache: cacheStatusHeader is empty")
|
|
206
211
|
}
|
|
207
212
|
const hits = new WeakSet<Request>()
|
|
213
|
+
const hasAuthentication = (req: Request): boolean =>
|
|
214
|
+
authenticatedHeaders.some((header) => req.headers.has(header))
|
|
208
215
|
|
|
209
216
|
return {
|
|
210
217
|
name: "cache",
|
|
@@ -216,6 +223,18 @@ export function cache(options: CacheOptions): Middleware {
|
|
|
216
223
|
const key = keyOf(req)
|
|
217
224
|
const entry = await store.get(key)
|
|
218
225
|
if (entry === undefined) return undefined
|
|
226
|
+
// Never replay a personalized entry before route-scoped authentication runs. Public entries
|
|
227
|
+
// remain eligible (and are explicitly marked by their response Cache-Control metadata).
|
|
228
|
+
if (
|
|
229
|
+
!cacheAuthenticated &&
|
|
230
|
+
hasAuthentication(req) &&
|
|
231
|
+
!cacheControlHas(new Headers(entry.headers as Array<[string, string]>), [
|
|
232
|
+
"public",
|
|
233
|
+
"s-maxage",
|
|
234
|
+
])
|
|
235
|
+
) {
|
|
236
|
+
return undefined
|
|
237
|
+
}
|
|
219
238
|
if (Date.now() >= entry.expiresAt) {
|
|
220
239
|
await store.delete?.(key)
|
|
221
240
|
return undefined
|
|
@@ -251,7 +270,7 @@ export function cache(options: CacheOptions): Middleware {
|
|
|
251
270
|
// across users. Opt in with `cacheAuthenticated: true` for a route that's the same for everyone.
|
|
252
271
|
if (
|
|
253
272
|
!cacheAuthenticated &&
|
|
254
|
-
(req
|
|
273
|
+
hasAuthentication(req) &&
|
|
255
274
|
!cacheControlHas(res.headers, ["public", "s-maxage"])
|
|
256
275
|
) {
|
|
257
276
|
return withStatusHeader(res, cacheStatusHeader, "BYPASS")
|
package/src/combine.ts
CHANGED
|
@@ -1,24 +1,36 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type AnyServer,
|
|
3
|
+
defineIdentityPlugin,
|
|
4
|
+
type IdentityPlugin,
|
|
5
|
+
type Middleware,
|
|
6
|
+
} from "@nifrajs/core/server"
|
|
2
7
|
|
|
3
|
-
export type Composable = Middleware |
|
|
8
|
+
export type Composable = Middleware | IdentityPlugin
|
|
4
9
|
|
|
5
|
-
function applyAll(app:
|
|
6
|
-
let current = app
|
|
10
|
+
function applyAll<S extends AnyServer>(app: S, items: readonly Composable[]): S {
|
|
11
|
+
let current: AnyServer = app
|
|
7
12
|
for (const item of items) {
|
|
8
13
|
// The public `use` overload accepts both shapes; this helper intentionally preserves runtime
|
|
9
14
|
// composition while leaving advanced context type threading to direct `.use(...)` chains.
|
|
10
15
|
current = current.use(item as never)
|
|
11
16
|
}
|
|
12
|
-
return current
|
|
17
|
+
return current as S
|
|
13
18
|
}
|
|
14
19
|
|
|
15
|
-
/**
|
|
16
|
-
|
|
17
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Compose middleware/plugins into one reusable bundle. Individual named plugins still dedupe.
|
|
22
|
+
*
|
|
23
|
+
* The bundle is a type **identity**: everything it contains runs, but context a member adds via
|
|
24
|
+
* `derive`/`decorate` is not threaded to the caller's handler types (there is no way to sum unknown
|
|
25
|
+
* members' context at the type level). Apply such a member with its own `.use(...)` instead.
|
|
26
|
+
*/
|
|
27
|
+
export function combine(...items: readonly Composable[]): IdentityPlugin {
|
|
28
|
+
// No `pluginName`: an unnamed bundle is applied every time rather than deduped.
|
|
29
|
+
return <S extends AnyServer>(app: S): S => applyAll(app, items)
|
|
18
30
|
}
|
|
19
31
|
|
|
20
32
|
/** Compose middleware/plugins into one idempotent named bundle. */
|
|
21
|
-
export function namedCombine(name: string, ...items: readonly Composable[]):
|
|
33
|
+
export function namedCombine(name: string, ...items: readonly Composable[]): IdentityPlugin {
|
|
22
34
|
if (name.trim() === "") throw new Error("namedCombine: name is empty")
|
|
23
|
-
return
|
|
35
|
+
return defineIdentityPlugin(name, (app) => applyAll(app, items))
|
|
24
36
|
}
|
package/src/compression.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { defineRouterPlugin, type ResponseHeadersView } from "@nifrajs/core/server"
|
|
2
2
|
|
|
3
3
|
export interface CompressionOptions {
|
|
4
4
|
/** Don't compress bodies smaller than this many bytes. Default `1024`. Enforced by peeking the body
|
|
@@ -217,6 +217,13 @@ function replayRawResponse(
|
|
|
217
217
|
* types, and bodies below `threshold` (gzip's ~20-byte overhead would enlarge them). Adds
|
|
218
218
|
* `Vary: Accept-Encoding`.
|
|
219
219
|
*
|
|
220
|
+
* **Security (BREACH).** Compression makes the response length depend on its content, so a response
|
|
221
|
+
* that mixes a secret with attacker-reflected input leaks the secret through the compressed size
|
|
222
|
+
* (the BREACH class of attacks). This applies to any HTTP compression, not this middleware
|
|
223
|
+
* specifically. Nifra's own CSRF tokens are HMAC-signed per session and safe to compress, but if a
|
|
224
|
+
* response body reflects request input **and** carries a per-request secret, exclude that route via
|
|
225
|
+
* `compressible`/route scoping, or split the secret and the reflection into separate responses.
|
|
226
|
+
*
|
|
220
227
|
* ```ts
|
|
221
228
|
* app.use(compression())
|
|
222
229
|
* ```
|
|
@@ -227,7 +234,7 @@ export function compression(options: CompressionOptions = {}) {
|
|
|
227
234
|
throw new Error("compression: threshold must be a non-negative safe integer")
|
|
228
235
|
}
|
|
229
236
|
const isCompressible = options.compressible ?? defaultCompressible
|
|
230
|
-
return
|
|
237
|
+
return defineRouterPlugin("compression", (app) =>
|
|
231
238
|
app.use({
|
|
232
239
|
onResponseBody(body, headers, req, status) {
|
|
233
240
|
if (!acceptsGzip(req.header("accept-encoding"))) return undefined
|