@nifrajs/middleware 2.8.2 → 2.9.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 +22 -1
- package/dist/_utils.d.ts +6 -1
- package/dist/_utils.d.ts.map +1 -1
- package/dist/_utils.js +39 -28
- package/dist/_utils.js.map +1 -1
- package/dist/cache-control.d.ts.map +1 -1
- package/dist/cache-control.js +31 -15
- package/dist/cache-control.js.map +1 -1
- package/dist/cache.d.ts +1 -1
- package/dist/cache.d.ts.map +1 -1
- package/dist/cache.js +2 -3
- package/dist/cache.js.map +1 -1
- package/dist/compression.d.ts +10 -10
- package/dist/compression.d.ts.map +1 -1
- package/dist/compression.js +191 -70
- package/dist/compression.js.map +1 -1
- package/dist/cors.d.ts.map +1 -1
- package/dist/cors.js +27 -16
- package/dist/cors.js.map +1 -1
- package/dist/etag.d.ts +4 -2
- package/dist/etag.d.ts.map +1 -1
- package/dist/etag.js +135 -63
- package/dist/etag.js.map +1 -1
- package/dist/language.d.ts.map +1 -1
- package/dist/language.js +14 -15
- package/dist/language.js.map +1 -1
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +34 -16
- package/dist/logger.js.map +1 -1
- package/dist/powered-by.d.ts.map +1 -1
- package/dist/powered-by.js +8 -5
- package/dist/powered-by.js.map +1 -1
- package/dist/pretty-json.d.ts +5 -2
- package/dist/pretty-json.d.ts.map +1 -1
- package/dist/pretty-json.js +85 -64
- package/dist/pretty-json.js.map +1 -1
- package/dist/rate-limit.d.ts.map +1 -1
- package/dist/rate-limit.js +76 -19
- package/dist/rate-limit.js.map +1 -1
- package/dist/request-id.js +1 -1
- package/dist/request-id.js.map +1 -1
- package/dist/security-headers.d.ts +8 -4
- package/dist/security-headers.d.ts.map +1 -1
- package/dist/security-headers.js +18 -16
- package/dist/security-headers.js.map +1 -1
- package/package.json +3 -3
- package/src/_utils.ts +39 -24
- package/src/cache-control.ts +27 -14
- package/src/cache.ts +2 -4
- package/src/compression.ts +202 -62
- package/src/cors.ts +23 -14
- package/src/etag.ts +141 -59
- package/src/language.ts +12 -13
- package/src/logger.ts +24 -6
- package/src/powered-by.ts +8 -4
- package/src/pretty-json.ts +95 -70
- package/src/rate-limit.ts +86 -20
- package/src/request-id.ts +1 -1
- package/src/security-headers.ts +17 -15
package/dist/rate-limit.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { NIFRA_ASSURANCE, withRouteAssurance } from "@nifrajs/core/assurance";
|
|
2
|
+
import { withHeaders, withNodeHeaders } from "./_utils.js";
|
|
2
3
|
/** Max entries scanned (oldest-first) per eviction looking for an expired victim before falling back
|
|
3
4
|
* to evicting the oldest-inserted. Bounds eviction to O(1) per insertion instead of a full O(n) sweep
|
|
4
5
|
* under a distinct-key flood - the abuse the key cap defends against. */
|
|
@@ -111,6 +112,24 @@ function defaultKey(req, trustedProxies, header, allowGlobalKey) {
|
|
|
111
112
|
// from it. A shared bucket is only safe when the app deliberately asked for a global throttle.
|
|
112
113
|
return allowGlobalKey ? "global" : null;
|
|
113
114
|
}
|
|
115
|
+
/** {@link defaultKey} against the allocation-light native request view - same logic, same order. */
|
|
116
|
+
function nativeKey(req, trustedProxies, header, allowGlobalKey) {
|
|
117
|
+
if (header !== undefined) {
|
|
118
|
+
const ip = req.header(header);
|
|
119
|
+
if (ip !== null && ip.trim() !== "")
|
|
120
|
+
return ip.trim();
|
|
121
|
+
}
|
|
122
|
+
if (trustedProxies > 0) {
|
|
123
|
+
const xff = req.header("x-forwarded-for");
|
|
124
|
+
if (xff !== null) {
|
|
125
|
+
const parts = xff.split(",");
|
|
126
|
+
const ip = parts[parts.length - trustedProxies]?.trim();
|
|
127
|
+
if (ip !== undefined && ip !== "")
|
|
128
|
+
return ip;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return allowGlobalKey ? "global" : null;
|
|
132
|
+
}
|
|
114
133
|
/**
|
|
115
134
|
* Rate limiting as a {@link Middleware}. Runs in `onRequest` (before routing, so it
|
|
116
135
|
* also covers 404s); over the limit → `429` + `Retry-After`. Every response carries
|
|
@@ -139,35 +158,73 @@ export function rateLimit(options) {
|
|
|
139
158
|
}
|
|
140
159
|
const keyOf = options.key ?? ((req) => defaultKey(req, trustedProxies, header, allowGlobalKey));
|
|
141
160
|
const quota = new WeakMap();
|
|
161
|
+
// State for the Node twins, keyed by the NodeRequestContext identity (the same object is passed
|
|
162
|
+
// to the request and response twins - the core identity contract that replaces `Request` keying).
|
|
163
|
+
const nativeQuota = new WeakMap();
|
|
164
|
+
const hitStore = async (key) => {
|
|
165
|
+
if (typeof key !== "string" || key.trim() === "") {
|
|
166
|
+
return Response.json({ ok: false, error: "rate_limit_key_unavailable" }, { status: 500 });
|
|
167
|
+
}
|
|
168
|
+
const { count, resetAt } = await store.hit(key, windowMs);
|
|
169
|
+
const resetSeconds = Math.max(0, Math.ceil((resetAt - Date.now()) / 1000));
|
|
170
|
+
const info = { remaining: Math.max(0, max - count), resetSeconds };
|
|
171
|
+
if (count > max) {
|
|
172
|
+
return {
|
|
173
|
+
info,
|
|
174
|
+
reject: new Response(JSON.stringify({ ok: false, error: "rate_limited" }), {
|
|
175
|
+
status: 429,
|
|
176
|
+
headers: { "content-type": "application/json", "retry-after": String(resetSeconds) },
|
|
177
|
+
}),
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
return { info, reject: undefined };
|
|
181
|
+
};
|
|
142
182
|
const middleware = {
|
|
143
183
|
name: "rate-limit",
|
|
144
184
|
async onRequest(req) {
|
|
145
|
-
const
|
|
146
|
-
if (
|
|
147
|
-
return
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
const resetSeconds = Math.max(0, Math.ceil((resetAt - Date.now()) / 1000));
|
|
151
|
-
quota.set(req, { remaining: Math.max(0, max - count), resetSeconds });
|
|
152
|
-
if (count > max) {
|
|
153
|
-
return new Response(JSON.stringify({ ok: false, error: "rate_limited" }), {
|
|
154
|
-
status: 429,
|
|
155
|
-
headers: { "content-type": "application/json", "retry-after": String(resetSeconds) },
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
return undefined;
|
|
185
|
+
const outcome = await hitStore(keyOf(req));
|
|
186
|
+
if (outcome instanceof Response)
|
|
187
|
+
return outcome;
|
|
188
|
+
quota.set(req, outcome.info);
|
|
189
|
+
return outcome.reject;
|
|
159
190
|
},
|
|
160
191
|
onResponse(res, req) {
|
|
161
192
|
const info = quota.get(req);
|
|
162
193
|
if (info === undefined)
|
|
163
194
|
return res;
|
|
164
195
|
quota.delete(req);
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
196
|
+
return withHeaders(res, (headers) => {
|
|
197
|
+
headers.set("RateLimit-Limit", String(max));
|
|
198
|
+
headers.set("RateLimit-Remaining", String(info.remaining));
|
|
199
|
+
headers.set("RateLimit-Reset", String(info.resetSeconds));
|
|
200
|
+
});
|
|
170
201
|
},
|
|
202
|
+
// Node twins exist only for the built-in header/XFF key derivation - a custom `key` callback
|
|
203
|
+
// takes a real `Request`, which is exactly the object the native lane avoids building.
|
|
204
|
+
...(options.key === undefined
|
|
205
|
+
? {
|
|
206
|
+
onNodeRequest: async (req) => {
|
|
207
|
+
const outcome = await hitStore(nativeKey(req, trustedProxies, header, allowGlobalKey));
|
|
208
|
+
if (outcome instanceof Response)
|
|
209
|
+
return outcome;
|
|
210
|
+
nativeQuota.set(req, outcome.info);
|
|
211
|
+
return outcome.reject;
|
|
212
|
+
},
|
|
213
|
+
onNodeResponse: (res, req) => {
|
|
214
|
+
const info = nativeQuota.get(req);
|
|
215
|
+
if (info === undefined)
|
|
216
|
+
return;
|
|
217
|
+
nativeQuota.delete(req);
|
|
218
|
+
withNodeHeaders(res, (headers) => {
|
|
219
|
+
// Lowercase on purpose: the Web path's `Headers` lowercases names on the wire, so
|
|
220
|
+
// this is byte-identical output.
|
|
221
|
+
headers["ratelimit-limit"] = String(max);
|
|
222
|
+
headers["ratelimit-remaining"] = String(info.remaining);
|
|
223
|
+
headers["ratelimit-reset"] = String(info.resetSeconds);
|
|
224
|
+
});
|
|
225
|
+
},
|
|
226
|
+
}
|
|
227
|
+
: {}),
|
|
171
228
|
};
|
|
172
229
|
return withRouteAssurance(middleware, {
|
|
173
230
|
id: NIFRA_ASSURANCE.RATE_LIMITED,
|
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,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;
|
|
1
|
+
{"version":3,"file":"rate-limit.js","sourceRoot":"","sources":["../src/rate-limit.ts"],"names":[],"mappings":"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"}
|
package/dist/request-id.js
CHANGED
|
@@ -13,7 +13,7 @@ export function requestId(options = {}) {
|
|
|
13
13
|
const header = options.header ?? "x-request-id";
|
|
14
14
|
const generate = options.generate ?? (() => crypto.randomUUID());
|
|
15
15
|
return definePlugin("requestId", (app) => app.derive((c) => {
|
|
16
|
-
const id = c.
|
|
16
|
+
const id = c.header(header) ?? generate();
|
|
17
17
|
c.set.headers[header] = id; // echo on the response (route handlers)
|
|
18
18
|
return { requestId: id };
|
|
19
19
|
}));
|
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,MAAM,sBAAsB,CAAA;AASnD;;;;;;;;;GASG;AACH,MAAM,UAAU,SAAS,CAAC,UAA4B,EAAE;IACtD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,cAAc,CAAA;IAC/C,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC,CAAA;IAChE,OAAO,YAAY,CAAC,WAAW,EAAE,CAAC,GAAG,EAAE,EAAE,CACvC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE;QACf,MAAM,EAAE,GAAG,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"request-id.js","sourceRoot":"","sources":["../src/request-id.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAA;AASnD;;;;;;;;;GASG;AACH,MAAM,UAAU,SAAS,CAAC,UAA4B,EAAE;IACtD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,cAAc,CAAA;IAC/C,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC,CAAA;IAChE,OAAO,YAAY,CAAC,WAAW,EAAE,CAAC,GAAG,EAAE,EAAE,CACvC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE;QACf,MAAM,EAAE,GAAG,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAA;QACzC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,EAAE,CAAA,CAAC,wCAAwC;QACnE,OAAO,EAAE,SAAS,EAAE,EAAE,EAAE,CAAA;IAC1B,CAAC,CAAC,CACH,CAAA;AACH,CAAC"}
|
|
@@ -14,10 +14,14 @@ export interface SecurityHeadersOptions {
|
|
|
14
14
|
readonly referrerPolicy?: string;
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
17
|
-
* A safe-by-default set of response security headers
|
|
18
|
-
*
|
|
19
|
-
* `
|
|
20
|
-
*
|
|
17
|
+
* A safe-by-default set of response security headers, covering errors and 404s too:
|
|
18
|
+
* `X-Content-Type-Options: nosniff`, `X-Frame-Options`, and `Referrer-Policy` always;
|
|
19
|
+
* `Strict-Transport-Security` and `Content-Security-Policy` only when configured (both are
|
|
20
|
+
* environment-/app-specific).
|
|
21
|
+
*
|
|
22
|
+
* Every value is fixed at construction, so these are declared statically rather than written by a
|
|
23
|
+
* response hook: an app whose response middleware is only this keeps the fused native lanes. A route
|
|
24
|
+
* that sets one of these names itself keeps its own value.
|
|
21
25
|
*/
|
|
22
26
|
export declare function securityHeaders(options?: SecurityHeadersOptions): Middleware;
|
|
23
27
|
//# sourceMappingURL=security-headers.d.ts.map
|
|
@@ -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;
|
|
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;CACjC;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,OAAO,GAAE,sBAA2B,GAAG,UAAU,CAgChF"}
|
package/dist/security-headers.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { NIFRA_ASSURANCE, withRouteAssurance } from "@nifrajs/core/assurance";
|
|
2
|
-
import { withHeaders } from "./_utils.js";
|
|
3
2
|
/**
|
|
4
|
-
* A safe-by-default set of response security headers
|
|
5
|
-
*
|
|
6
|
-
* `
|
|
7
|
-
*
|
|
3
|
+
* A safe-by-default set of response security headers, covering errors and 404s too:
|
|
4
|
+
* `X-Content-Type-Options: nosniff`, `X-Frame-Options`, and `Referrer-Policy` always;
|
|
5
|
+
* `Strict-Transport-Security` and `Content-Security-Policy` only when configured (both are
|
|
6
|
+
* environment-/app-specific).
|
|
7
|
+
*
|
|
8
|
+
* Every value is fixed at construction, so these are declared statically rather than written by a
|
|
9
|
+
* response hook: an app whose response middleware is only this keeps the fused native lanes. A route
|
|
10
|
+
* that sets one of these names itself keeps its own value.
|
|
8
11
|
*/
|
|
9
12
|
export function securityHeaders(options = {}) {
|
|
10
13
|
const frameOptions = options.frameOptions ?? "DENY";
|
|
@@ -19,19 +22,18 @@ export function securityHeaders(options = {}) {
|
|
|
19
22
|
parts.push("preload");
|
|
20
23
|
hstsValue = parts.join("; ");
|
|
21
24
|
}
|
|
25
|
+
const declared = {
|
|
26
|
+
"x-content-type-options": "nosniff",
|
|
27
|
+
"x-frame-options": frameOptions,
|
|
28
|
+
"referrer-policy": referrerPolicy,
|
|
29
|
+
};
|
|
30
|
+
if (hstsValue !== undefined)
|
|
31
|
+
declared["strict-transport-security"] = hstsValue;
|
|
32
|
+
if (csp !== undefined)
|
|
33
|
+
declared["content-security-policy"] = csp;
|
|
22
34
|
return withRouteAssurance({
|
|
23
35
|
name: "security-headers",
|
|
24
|
-
|
|
25
|
-
// (see withHeaders - the old always-clone was ~3% of a realistic request).
|
|
26
|
-
onResponse: (res) => withHeaders(res, (headers) => {
|
|
27
|
-
headers.set("X-Content-Type-Options", "nosniff");
|
|
28
|
-
headers.set("X-Frame-Options", frameOptions);
|
|
29
|
-
headers.set("Referrer-Policy", referrerPolicy);
|
|
30
|
-
if (hstsValue !== undefined)
|
|
31
|
-
headers.set("Strict-Transport-Security", hstsValue);
|
|
32
|
-
if (csp !== undefined)
|
|
33
|
-
headers.set("Content-Security-Policy", csp);
|
|
34
|
-
}),
|
|
36
|
+
responseHeaders: declared,
|
|
35
37
|
}, {
|
|
36
38
|
id: NIFRA_ASSURANCE.SECURITY_HEADERS,
|
|
37
39
|
source: "security-headers",
|
|
@@ -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;AAkB7E;;;;;;;;;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;IAEhE,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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nifrajs/middleware",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.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.9.0"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
|
52
|
-
"@nifrajs/core": "^2.
|
|
52
|
+
"@nifrajs/core": "^2.9.0"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@nifrajs/core": "workspace:*",
|
package/src/_utils.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
export type MaybePromise<T> = T | Promise<T>
|
|
2
2
|
|
|
3
|
+
import type { NodeResponseContext } from "@nifrajs/core/server"
|
|
4
|
+
import { parseCookies as parseCoreCookies } from "@nifrajs/core/server"
|
|
5
|
+
|
|
3
6
|
export const SAFE_METHODS = new Set(["GET", "HEAD", "OPTIONS", "TRACE"])
|
|
4
7
|
|
|
5
8
|
const TEXT = new TextEncoder()
|
|
@@ -15,28 +18,8 @@ export function jsonError(
|
|
|
15
18
|
)
|
|
16
19
|
}
|
|
17
20
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
// never a prototype-member shadow. Mirrors core's parseCookies + the query/form parsers.
|
|
21
|
-
const out: Record<string, string> = Object.create(null)
|
|
22
|
-
if (!header) return out
|
|
23
|
-
for (const part of header.split(";")) {
|
|
24
|
-
const eq = part.indexOf("=")
|
|
25
|
-
if (eq < 1) continue
|
|
26
|
-
const name = part.slice(0, eq).trim()
|
|
27
|
-
if (name === "") continue
|
|
28
|
-
let value = part.slice(eq + 1).trim()
|
|
29
|
-
if (value.length >= 2 && value.startsWith('"') && value.endsWith('"')) {
|
|
30
|
-
value = value.slice(1, -1)
|
|
31
|
-
}
|
|
32
|
-
try {
|
|
33
|
-
out[name] = decodeURIComponent(value)
|
|
34
|
-
} catch {
|
|
35
|
-
out[name] = value
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
return out
|
|
39
|
-
}
|
|
21
|
+
/** Shared with core so auth/CSRF middleware gets the allocation-light cookie scanner too. */
|
|
22
|
+
export const parseCookies = parseCoreCookies
|
|
40
23
|
|
|
41
24
|
export function quotedHeaderValue(value: string): string {
|
|
42
25
|
let out = ""
|
|
@@ -152,12 +135,44 @@ export async function verifyHmacSha256(
|
|
|
152
135
|
* - the first mutation throws before any partial change), so a mutation chain is safe to pass.
|
|
153
136
|
*/
|
|
154
137
|
export function withHeaders(res: Response, apply: (headers: Headers) => void): Response {
|
|
138
|
+
const responseHeaders = res.headers
|
|
139
|
+
const probe = "x-nifra-header-probe"
|
|
140
|
+
let previous: string | null = null
|
|
141
|
+
let mutable = true
|
|
155
142
|
try {
|
|
156
|
-
|
|
157
|
-
|
|
143
|
+
previous = responseHeaders.get(probe)
|
|
144
|
+
responseHeaders.set(probe, "1")
|
|
145
|
+
if (previous === null) responseHeaders.delete(probe)
|
|
146
|
+
else responseHeaders.set(probe, previous)
|
|
158
147
|
} catch {
|
|
148
|
+
mutable = false
|
|
149
|
+
try {
|
|
150
|
+
if (previous === null) responseHeaders.delete(probe)
|
|
151
|
+
else responseHeaders.set(probe, previous)
|
|
152
|
+
} catch {
|
|
153
|
+
// The guarded response rejected cleanup; the clone below is authoritative.
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (!mutable) {
|
|
159
157
|
const headers = new Headers(res.headers)
|
|
160
158
|
apply(headers)
|
|
161
159
|
return new Response(res.body, { status: res.status, statusText: res.statusText, headers })
|
|
162
160
|
}
|
|
161
|
+
apply(responseHeaders)
|
|
162
|
+
return res
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/** Mutate Node-direct response headers without constructing a Web Response. */
|
|
166
|
+
export function withNodeHeaders(
|
|
167
|
+
res: NodeResponseContext,
|
|
168
|
+
apply: (headers: Record<string, string | readonly string[]>) => void,
|
|
169
|
+
): void {
|
|
170
|
+
if (res.headers === undefined) {
|
|
171
|
+
res.headers = Object.create(null) as Record<string, string | readonly string[]>
|
|
172
|
+
} else if (Object.getPrototypeOf(res.headers) !== null) {
|
|
173
|
+
const safe = Object.create(null) as Record<string, string | readonly string[]>
|
|
174
|
+
Object.assign(safe, res.headers)
|
|
175
|
+
res.headers = safe
|
|
176
|
+
}
|
|
177
|
+
apply(res.headers)
|
|
163
178
|
}
|
package/src/cache-control.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { definePlugin } from "@nifrajs/core/server"
|
|
1
|
+
import { definePlugin, type Middleware } from "@nifrajs/core/server"
|
|
2
|
+
import { withHeaders } from "./_utils.ts"
|
|
2
3
|
|
|
3
4
|
export interface CacheControlOptions {
|
|
4
5
|
/** Methods whose responses get the header. Default `["GET", "HEAD"]`. */
|
|
@@ -29,17 +30,29 @@ export function cacheControl(
|
|
|
29
30
|
const methods = new Set((options.methods ?? ["GET", "HEAD"]).map((m) => m.toUpperCase()))
|
|
30
31
|
const statusOk = options.status ?? ((status: number) => status >= 200 && status < 300)
|
|
31
32
|
const respectExisting = options.respectExisting !== false
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
33
|
+
return definePlugin("cacheControl", (app) => {
|
|
34
|
+
// A fixed directive needs nothing from the request beyond the method, so it ships as one
|
|
35
|
+
// portable header hook. A dynamic directive's resolver takes a real `Request`, which only the
|
|
36
|
+
// Web response walk carries - it keeps the full `onResponse` contract (and its cost).
|
|
37
|
+
const middleware: Middleware =
|
|
38
|
+
typeof value === "string"
|
|
39
|
+
? {
|
|
40
|
+
onResponseHeaders(headers, req, status) {
|
|
41
|
+
if (!methods.has(req.method) || !statusOk(status)) return
|
|
42
|
+
if (respectExisting && headers.has("cache-control")) return
|
|
43
|
+
headers.set("cache-control", value)
|
|
44
|
+
},
|
|
45
|
+
}
|
|
46
|
+
: {
|
|
47
|
+
onResponse(res, req) {
|
|
48
|
+
if (!methods.has(req.method)) return res
|
|
49
|
+
if (!statusOk(res.status)) return res
|
|
50
|
+
if (respectExisting && res.headers.has("cache-control")) return res
|
|
51
|
+
const directive = value(req)
|
|
52
|
+
if (directive === undefined) return res
|
|
53
|
+
return withHeaders(res, (headers) => headers.set("cache-control", directive))
|
|
54
|
+
},
|
|
55
|
+
}
|
|
56
|
+
return app.use(middleware)
|
|
57
|
+
})
|
|
45
58
|
}
|
package/src/cache.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Middleware } from "@nifrajs/core/server"
|
|
2
|
-
import type
|
|
2
|
+
import { type MaybePromise, withHeaders } from "./_utils.ts"
|
|
3
3
|
|
|
4
4
|
export interface CachedResponse {
|
|
5
5
|
readonly status: number
|
|
@@ -174,9 +174,7 @@ function withStatusHeader(
|
|
|
174
174
|
status: "HIT" | "MISS" | "BYPASS",
|
|
175
175
|
): Response {
|
|
176
176
|
if (header === false) return res
|
|
177
|
-
|
|
178
|
-
headers.set(header, status)
|
|
179
|
-
return new Response(res.body, { status: res.status, statusText: res.statusText, headers })
|
|
177
|
+
return withHeaders(res, (headers) => headers.set(header, status))
|
|
180
178
|
}
|
|
181
179
|
|
|
182
180
|
/**
|