@nifrajs/middleware 0.1.0-alpha.1
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/LICENSE +21 -0
- package/README.md +79 -0
- package/dist/_utils.d.ts +27 -0
- package/dist/_utils.d.ts.map +1 -0
- package/dist/_utils.js +141 -0
- package/dist/_utils.js.map +1 -0
- package/dist/basic-auth.d.ts +27 -0
- package/dist/basic-auth.d.ts.map +1 -0
- package/dist/basic-auth.js +64 -0
- package/dist/basic-auth.js.map +1 -0
- package/dist/body-limit.d.ts +19 -0
- package/dist/body-limit.d.ts.map +1 -0
- package/dist/body-limit.js +42 -0
- package/dist/body-limit.js.map +1 -0
- package/dist/cache-control.d.ts +23 -0
- package/dist/cache-control.d.ts.map +1 -0
- package/dist/cache-control.js +35 -0
- package/dist/cache-control.js.map +1 -0
- package/dist/cache.d.ts +60 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +224 -0
- package/dist/cache.js.map +1 -0
- package/dist/combine.d.ts +7 -0
- package/dist/combine.d.ts.map +1 -0
- package/dist/combine.js +21 -0
- package/dist/combine.js.map +1 -0
- package/dist/compression.d.ts +29 -0
- package/dist/compression.d.ts.map +1 -0
- package/dist/compression.js +105 -0
- package/dist/compression.js.map +1 -0
- package/dist/context-storage.d.ts +14 -0
- package/dist/context-storage.d.ts.map +1 -0
- package/dist/context-storage.js +30 -0
- package/dist/context-storage.js.map +1 -0
- package/dist/cors.d.ts +25 -0
- package/dist/cors.d.ts.map +1 -0
- package/dist/cors.js +75 -0
- package/dist/cors.js.map +1 -0
- package/dist/csrf.d.ts +23 -0
- package/dist/csrf.d.ts.map +1 -0
- package/dist/csrf.js +68 -0
- package/dist/csrf.js.map +1 -0
- package/dist/etag.d.ts +14 -0
- package/dist/etag.d.ts.map +1 -0
- package/dist/etag.js +95 -0
- package/dist/etag.js.map +1 -0
- package/dist/healthcheck.d.ts +25 -0
- package/dist/healthcheck.d.ts.map +1 -0
- package/dist/healthcheck.js +41 -0
- package/dist/healthcheck.js.map +1 -0
- package/dist/idempotency.d.ts +83 -0
- package/dist/idempotency.d.ts.map +1 -0
- package/dist/idempotency.js +217 -0
- package/dist/idempotency.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +33 -0
- package/dist/index.js.map +1 -0
- package/dist/ip-restriction.d.ts +21 -0
- package/dist/ip-restriction.d.ts.map +1 -0
- package/dist/ip-restriction.js +161 -0
- package/dist/ip-restriction.js.map +1 -0
- package/dist/jwt.d.ts +82 -0
- package/dist/jwt.d.ts.map +1 -0
- package/dist/jwt.js +327 -0
- package/dist/jwt.js.map +1 -0
- package/dist/language.d.ts +20 -0
- package/dist/language.d.ts.map +1 -0
- package/dist/language.js +94 -0
- package/dist/language.js.map +1 -0
- package/dist/logger.d.ts +19 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +28 -0
- package/dist/logger.js.map +1 -0
- package/dist/method-override.d.ts +28 -0
- package/dist/method-override.d.ts.map +1 -0
- package/dist/method-override.js +67 -0
- package/dist/method-override.js.map +1 -0
- package/dist/openapi.d.ts +83 -0
- package/dist/openapi.d.ts.map +1 -0
- package/dist/openapi.js +100 -0
- package/dist/openapi.js.map +1 -0
- package/dist/powered-by.d.ts +15 -0
- package/dist/powered-by.d.ts.map +1 -0
- package/dist/powered-by.js +23 -0
- package/dist/powered-by.js.map +1 -0
- package/dist/pretty-json.d.ts +18 -0
- package/dist/pretty-json.d.ts.map +1 -0
- package/dist/pretty-json.js +88 -0
- package/dist/pretty-json.js.map +1 -0
- package/dist/rate-limit.d.ts +92 -0
- package/dist/rate-limit.d.ts.map +1 -0
- package/dist/rate-limit.js +172 -0
- package/dist/rate-limit.js.map +1 -0
- package/dist/request-id.d.ts +18 -0
- package/dist/request-id.d.ts.map +1 -0
- package/dist/request-id.js +21 -0
- package/dist/request-id.js.map +1 -0
- package/dist/security-headers.d.ts +23 -0
- package/dist/security-headers.d.ts.map +1 -0
- package/dist/security-headers.js +36 -0
- package/dist/security-headers.js.map +1 -0
- package/dist/timing.d.ts +25 -0
- package/dist/timing.d.ts.map +1 -0
- package/dist/timing.js +101 -0
- package/dist/timing.js.map +1 -0
- package/dist/token-auth.d.ts +65 -0
- package/dist/token-auth.d.ts.map +1 -0
- package/dist/token-auth.js +90 -0
- package/dist/token-auth.js.map +1 -0
- package/dist/trailing-slash.d.ts +23 -0
- package/dist/trailing-slash.d.ts.map +1 -0
- package/dist/trailing-slash.js +70 -0
- package/dist/trailing-slash.js.map +1 -0
- package/package.json +68 -0
- package/src/_utils.ts +163 -0
- package/src/basic-auth.ts +109 -0
- package/src/body-limit.ts +53 -0
- package/src/cache-control.ts +45 -0
- package/src/cache.ts +277 -0
- package/src/combine.ts +24 -0
- package/src/compression.ts +111 -0
- package/src/context-storage.ts +36 -0
- package/src/cors.ts +90 -0
- package/src/csrf.ts +102 -0
- package/src/etag.ts +99 -0
- package/src/healthcheck.ts +59 -0
- package/src/idempotency.ts +313 -0
- package/src/index.ts +110 -0
- package/src/ip-restriction.ts +187 -0
- package/src/jwt.ts +441 -0
- package/src/language.ts +109 -0
- package/src/logger.ts +44 -0
- package/src/method-override.ts +87 -0
- package/src/openapi.ts +174 -0
- package/src/powered-by.ts +31 -0
- package/src/pretty-json.ts +97 -0
- package/src/rate-limit.ts +248 -0
- package/src/request-id.ts +30 -0
- package/src/security-headers.ts +51 -0
- package/src/timing.ts +133 -0
- package/src/token-auth.ts +172 -0
- package/src/trailing-slash.ts +94 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Nifra Authors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# @nifrajs/middleware
|
|
2
|
+
|
|
3
|
+
Composable, dependency-light middleware for [nifra](../../README.md) — CORS, security headers, body
|
|
4
|
+
limits, auth, CSRF, JWT/JWKS, IP restriction, response caching, timing, and ops helpers — applied with
|
|
5
|
+
`app.use()`.
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
bun add @nifrajs/middleware
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
import { server } from "@nifrajs/core"
|
|
13
|
+
import {
|
|
14
|
+
bodyLimit,
|
|
15
|
+
cors,
|
|
16
|
+
MemoryStore,
|
|
17
|
+
rateLimit,
|
|
18
|
+
securityHeaders,
|
|
19
|
+
timing,
|
|
20
|
+
} from "@nifrajs/middleware"
|
|
21
|
+
|
|
22
|
+
const app = server()
|
|
23
|
+
.use(securityHeaders())
|
|
24
|
+
.use(cors({ origin: ["https://app.example.com"], credentials: true }))
|
|
25
|
+
.use(bodyLimit({ maxBytes: 1_000_000 }))
|
|
26
|
+
.use(rateLimit({
|
|
27
|
+
store: new MemoryStore(),
|
|
28
|
+
max: 100,
|
|
29
|
+
windowMs: 60_000,
|
|
30
|
+
key: (req) => req.headers.get("x-user-id") ?? "anonymous",
|
|
31
|
+
}))
|
|
32
|
+
.use(timing())
|
|
33
|
+
.get("/", () => ({ ok: true }))
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
- **`bodyLimit({ maxBytes })`** — fail-closed `Content-Length` gate before routing. Lengthless
|
|
37
|
+
bodies are rejected with `411` by default; use route-level `c.boundedBody()` / schema validation
|
|
38
|
+
for intentionally streamed endpoints.
|
|
39
|
+
- **`basicAuth(options)`** — Basic Auth plugin with constant-time static credential comparison or
|
|
40
|
+
a custom verifier.
|
|
41
|
+
- **`bearer(options)` / `apiKey(options)`** — token auth plugins with typed principals.
|
|
42
|
+
- **`csrf({ secret })`** — signed double-submit CSRF protection plus Origin/Referer checking.
|
|
43
|
+
- **`jwt(options)` + `verifyJwt()` / `tryVerifyJwt()` + `jwk()` / `jwks()`** — JWT auth
|
|
44
|
+
with explicit algorithm allowlists, required expiration by default, issuer/audience checks,
|
|
45
|
+
direct JWK, HTTPS JWKS, and an additive no-throw Result helper for manual verification.
|
|
46
|
+
- **`ipRestriction(options)`** — allow/deny IPv4/IPv6 exact and CIDR matches. Fails closed unless
|
|
47
|
+
you provide `clientIp`, trusted proxy extraction, or a trusted single-IP header.
|
|
48
|
+
- **`cors(options)`** — preflight handling + headers on *every* response (errors and
|
|
49
|
+
404s included). Origin as `"*"` / exact / list / predicate. **Throws** if
|
|
50
|
+
`credentials: true` is paired with `origin: "*"` (the browser rejects it).
|
|
51
|
+
- **`securityHeaders(options)`** — `X-Content-Type-Options`, `X-Frame-Options`,
|
|
52
|
+
`Referrer-Policy` by default; opt-in HSTS and CSP.
|
|
53
|
+
- **`rateLimit(options)`** — `429` + `Retry-After` + `RateLimit-*` headers, with a
|
|
54
|
+
pluggable `RateLimitStore`. Configure a trusted `key`, trusted single-IP `header`, or
|
|
55
|
+
`trustedProxies`; a missing key source fails closed instead of silently sharing one
|
|
56
|
+
bucket. The bundled `MemoryStore` **refuses to run in production** (a per-instance
|
|
57
|
+
limiter is unsafe across instances) — provide a shared store (Redis, etc.) there.
|
|
58
|
+
- **`cache({ store, ttlMs })` / `responseCache(...)`** — full response cache with a pluggable
|
|
59
|
+
store, `Vary`-aware keys, `Age`, byte caps, and `Cache-Control` / `Set-Cookie` safety defaults.
|
|
60
|
+
`MemoryResponseCache` is dev/single-instance only unless explicitly allowed in production.
|
|
61
|
+
- **`timing()`** — `Server-Timing` plus typed `c.timing.metric/mark/measure` controls.
|
|
62
|
+
- **`prettyJson()`** — capped, JSON-only pretty printing for debugging and developer-facing APIs.
|
|
63
|
+
- **`methodOverride()`** — header/query method tunneling for clients that can only send `POST`.
|
|
64
|
+
Header override is on by default; query override is opt-in.
|
|
65
|
+
- **`trimTrailingSlash()` / `appendTrailingSlash()`** — redirect or rewrite URL canonicalization.
|
|
66
|
+
- **`poweredBy()`** — opt-in product/framework header; Nifra emits no powered-by header by default.
|
|
67
|
+
- **`language()` / `pickLanguage()`** — `Accept-Language` negotiation with typed `c.language`.
|
|
68
|
+
- **`combine()` / `namedCombine()`** — reusable runtime bundles for middleware/plugins.
|
|
69
|
+
- **`requestId()` / `logger()` / `etag()` / `compression()` / `cacheControl()` /
|
|
70
|
+
`idempotency()` / `healthcheck()` / `openapi()`** — additional operational middleware for APIs.
|
|
71
|
+
|
|
72
|
+
Request timeouts are configured at the core server boundary so they can abort `c.signal` and race the
|
|
73
|
+
whole lifecycle:
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
const app = server({ requestTimeoutMs: 5_000 })
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
`@nifrajs/core` is a peer dependency. ESM-only. MIT.
|
package/dist/_utils.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export type MaybePromise<T> = T | Promise<T>;
|
|
2
|
+
export declare const SAFE_METHODS: Set<string>;
|
|
3
|
+
export declare function jsonError(status: number, error: string, headers?: Record<string, string>): Response;
|
|
4
|
+
export declare function parseCookies(header: string | null | undefined): Record<string, string>;
|
|
5
|
+
export declare function quotedHeaderValue(value: string): string;
|
|
6
|
+
export declare function utf8Bytes(value: string): Uint8Array<ArrayBuffer>;
|
|
7
|
+
export declare function secretBytes(secret: string | Uint8Array, label: string): Uint8Array<ArrayBuffer>;
|
|
8
|
+
export declare function sha256(input: string | Uint8Array): Promise<Uint8Array<ArrayBuffer>>;
|
|
9
|
+
export declare function timingSafeEqualBytes(a: Uint8Array, b: Uint8Array): boolean;
|
|
10
|
+
export declare function timingSafeEqualString(a: string, b: string): Promise<boolean>;
|
|
11
|
+
export declare function base64UrlEncode(bytes: ArrayBuffer | Uint8Array): string;
|
|
12
|
+
export declare function base64UrlDecode(input: string): Uint8Array<ArrayBuffer> | null;
|
|
13
|
+
export declare function decodeBase64(input: string): Uint8Array<ArrayBuffer> | null;
|
|
14
|
+
export declare function importHmacKey(secret: string | Uint8Array, hash?: string): Promise<CryptoKey>;
|
|
15
|
+
export declare function hmacSha256(input: string, secret: string | Uint8Array): Promise<string>;
|
|
16
|
+
export declare function verifyHmacSha256(input: string, signature: string, secret: string | Uint8Array): Promise<boolean>;
|
|
17
|
+
/**
|
|
18
|
+
* Apply header mutations to a response, in place when possible. Framework-built responses
|
|
19
|
+
* (`new Response`, `Response.json`) have mutable headers across every runtime — so the common path
|
|
20
|
+
* mutates `res` directly and returns it, allocating nothing. Only an *immutable*-headers response
|
|
21
|
+
* (`Response.redirect()`/`Response.error()`, or a proxied `fetch()` response on Node/Deno/workerd —
|
|
22
|
+
* never on Bun) makes `.set`/`.append` throw; that path clones into a fresh `Headers` + `Response`,
|
|
23
|
+
* exactly the old always-clone behavior. `apply` runs once either way (immutability is all-or-nothing
|
|
24
|
+
* — the first mutation throws before any partial change), so a mutation chain is safe to pass.
|
|
25
|
+
*/
|
|
26
|
+
export declare function withHeaders(res: Response, apply: (headers: Headers) => void): Response;
|
|
27
|
+
//# sourceMappingURL=_utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_utils.d.ts","sourceRoot":"","sources":["../src/_utils.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,CAAC,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;AAE5C,eAAO,MAAM,YAAY,aAA+C,CAAA;AAIxE,wBAAgB,SAAS,CACvB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC/B,QAAQ,CAKV;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAqBtF;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CASvD;AAED,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAAC,WAAW,CAAC,CAEhE;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,EAAE,KAAK,EAAE,MAAM,GAAG,UAAU,CAAC,WAAW,CAAC,CAM/F;AAED,wBAAsB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC,CAGzF;AAED,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,UAAU,GAAG,OAAO,CAK1E;AAED,wBAAsB,qBAAqB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAGlF;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,WAAW,GAAG,UAAU,GAAG,MAAM,CAKvE;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAAC,WAAW,CAAC,GAAG,IAAI,CAc7E;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAAC,WAAW,CAAC,GAAG,IAAI,CAS1E;AAED,wBAAsB,aAAa,CACjC,MAAM,EAAE,MAAM,GAAG,UAAU,EAC3B,IAAI,SAAY,GACf,OAAO,CAAC,SAAS,CAAC,CAQpB;AAED,wBAAsB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAI5F;AAED,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GAAG,UAAU,GAC1B,OAAO,CAAC,OAAO,CAAC,CAKlB;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,GAAG,QAAQ,CAStF"}
|
package/dist/_utils.js
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
export const SAFE_METHODS = new Set(["GET", "HEAD", "OPTIONS", "TRACE"]);
|
|
2
|
+
const TEXT = new TextEncoder();
|
|
3
|
+
export function jsonError(status, error, headers) {
|
|
4
|
+
return Response.json({ ok: false, error }, headers === undefined ? { status } : { status, headers });
|
|
5
|
+
}
|
|
6
|
+
export function parseCookies(header) {
|
|
7
|
+
// Null-prototype: an untrusted cookie name (`constructor`/`__proto__`/…) is an inert own key,
|
|
8
|
+
// never a prototype-member shadow. Mirrors core's parseCookies + the query/form parsers.
|
|
9
|
+
const out = Object.create(null);
|
|
10
|
+
if (!header)
|
|
11
|
+
return out;
|
|
12
|
+
for (const part of header.split(";")) {
|
|
13
|
+
const eq = part.indexOf("=");
|
|
14
|
+
if (eq < 1)
|
|
15
|
+
continue;
|
|
16
|
+
const name = part.slice(0, eq).trim();
|
|
17
|
+
if (name === "")
|
|
18
|
+
continue;
|
|
19
|
+
let value = part.slice(eq + 1).trim();
|
|
20
|
+
if (value.length >= 2 && value.startsWith('"') && value.endsWith('"')) {
|
|
21
|
+
value = value.slice(1, -1);
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
out[name] = decodeURIComponent(value);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
out[name] = value;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
export function quotedHeaderValue(value) {
|
|
33
|
+
let out = "";
|
|
34
|
+
for (let i = 0; i < value.length; i++) {
|
|
35
|
+
const c = value.charCodeAt(i);
|
|
36
|
+
if (c < 0x20 || c === 0x7f)
|
|
37
|
+
continue;
|
|
38
|
+
const ch = value[i] ?? "";
|
|
39
|
+
out += ch === "\\" || ch === '"' ? `\\${ch}` : ch;
|
|
40
|
+
}
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
export function utf8Bytes(value) {
|
|
44
|
+
return TEXT.encode(value);
|
|
45
|
+
}
|
|
46
|
+
export function secretBytes(secret, label) {
|
|
47
|
+
const bytes = typeof secret === "string" ? utf8Bytes(secret) : new Uint8Array(secret);
|
|
48
|
+
if (bytes.byteLength < 32) {
|
|
49
|
+
throw new Error(`${label}: secret must be at least 32 bytes`);
|
|
50
|
+
}
|
|
51
|
+
return bytes;
|
|
52
|
+
}
|
|
53
|
+
export async function sha256(input) {
|
|
54
|
+
const bytes = typeof input === "string" ? utf8Bytes(input) : new Uint8Array(input);
|
|
55
|
+
return new Uint8Array(await crypto.subtle.digest("SHA-256", bytes));
|
|
56
|
+
}
|
|
57
|
+
export function timingSafeEqualBytes(a, b) {
|
|
58
|
+
if (a.byteLength !== b.byteLength)
|
|
59
|
+
return false;
|
|
60
|
+
let diff = 0;
|
|
61
|
+
for (let i = 0; i < a.byteLength; i++)
|
|
62
|
+
diff |= (a[i] ?? 0) ^ (b[i] ?? 0);
|
|
63
|
+
return diff === 0;
|
|
64
|
+
}
|
|
65
|
+
export async function timingSafeEqualString(a, b) {
|
|
66
|
+
const [left, right] = await Promise.all([sha256(a), sha256(b)]);
|
|
67
|
+
return timingSafeEqualBytes(left, right);
|
|
68
|
+
}
|
|
69
|
+
export function base64UrlEncode(bytes) {
|
|
70
|
+
const view = bytes instanceof Uint8Array ? bytes : new Uint8Array(bytes);
|
|
71
|
+
let bin = "";
|
|
72
|
+
for (const b of view)
|
|
73
|
+
bin += String.fromCharCode(b);
|
|
74
|
+
return btoa(bin).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
75
|
+
}
|
|
76
|
+
export function base64UrlDecode(input) {
|
|
77
|
+
if (!/^[A-Za-z0-9_-]*$/.test(input) || input.length % 4 === 1)
|
|
78
|
+
return null;
|
|
79
|
+
const padded = input
|
|
80
|
+
.replace(/-/g, "+")
|
|
81
|
+
.replace(/_/g, "/")
|
|
82
|
+
.padEnd(Math.ceil(input.length / 4) * 4, "=");
|
|
83
|
+
try {
|
|
84
|
+
const bin = atob(padded);
|
|
85
|
+
const bytes = new Uint8Array(bin.length);
|
|
86
|
+
for (let i = 0; i < bin.length; i++)
|
|
87
|
+
bytes[i] = bin.charCodeAt(i);
|
|
88
|
+
return bytes;
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
export function decodeBase64(input) {
|
|
95
|
+
try {
|
|
96
|
+
const bin = atob(input);
|
|
97
|
+
const bytes = new Uint8Array(bin.length);
|
|
98
|
+
for (let i = 0; i < bin.length; i++)
|
|
99
|
+
bytes[i] = bin.charCodeAt(i);
|
|
100
|
+
return bytes;
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
export async function importHmacKey(secret, hash = "SHA-256") {
|
|
107
|
+
return crypto.subtle.importKey("raw", secretBytes(secret, "hmac"), { name: "HMAC", hash }, false, ["sign", "verify"]);
|
|
108
|
+
}
|
|
109
|
+
export async function hmacSha256(input, secret) {
|
|
110
|
+
const key = await importHmacKey(secret, "SHA-256");
|
|
111
|
+
const sig = await crypto.subtle.sign("HMAC", key, utf8Bytes(input));
|
|
112
|
+
return base64UrlEncode(sig);
|
|
113
|
+
}
|
|
114
|
+
export async function verifyHmacSha256(input, signature, secret) {
|
|
115
|
+
const sig = base64UrlDecode(signature);
|
|
116
|
+
if (sig === null)
|
|
117
|
+
return false;
|
|
118
|
+
const key = await importHmacKey(secret, "SHA-256");
|
|
119
|
+
return crypto.subtle.verify("HMAC", key, sig, utf8Bytes(input));
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Apply header mutations to a response, in place when possible. Framework-built responses
|
|
123
|
+
* (`new Response`, `Response.json`) have mutable headers across every runtime — so the common path
|
|
124
|
+
* mutates `res` directly and returns it, allocating nothing. Only an *immutable*-headers response
|
|
125
|
+
* (`Response.redirect()`/`Response.error()`, or a proxied `fetch()` response on Node/Deno/workerd —
|
|
126
|
+
* never on Bun) makes `.set`/`.append` throw; that path clones into a fresh `Headers` + `Response`,
|
|
127
|
+
* exactly the old always-clone behavior. `apply` runs once either way (immutability is all-or-nothing
|
|
128
|
+
* — the first mutation throws before any partial change), so a mutation chain is safe to pass.
|
|
129
|
+
*/
|
|
130
|
+
export function withHeaders(res, apply) {
|
|
131
|
+
try {
|
|
132
|
+
apply(res.headers);
|
|
133
|
+
return res;
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
const headers = new Headers(res.headers);
|
|
137
|
+
apply(headers);
|
|
138
|
+
return new Response(res.body, { status: res.status, statusText: res.statusText, headers });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=_utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_utils.js","sourceRoot":"","sources":["../src/_utils.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type NifraPlugin } from "@nifrajs/core";
|
|
2
|
+
import { type MaybePromise } from "./_utils.js";
|
|
3
|
+
export type BasicAuthPlugin<P> = NifraPlugin & {
|
|
4
|
+
principal(request: Request): P | null;
|
|
5
|
+
requirePrincipal(request: Request): P;
|
|
6
|
+
};
|
|
7
|
+
export interface BasicAuthStaticOptions<P = string> {
|
|
8
|
+
readonly username: string;
|
|
9
|
+
readonly password: string;
|
|
10
|
+
readonly principal?: P;
|
|
11
|
+
readonly realm?: string;
|
|
12
|
+
readonly optional?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export interface BasicAuthVerifyOptions<P> {
|
|
15
|
+
readonly verify: (username: string, password: string) => MaybePromise<P | null | undefined>;
|
|
16
|
+
readonly realm?: string;
|
|
17
|
+
readonly optional?: boolean;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* HTTP Basic authentication. Prefer short-lived Basic Auth for internal tools and staging gates, not
|
|
21
|
+
* public user login. Static credentials are compared in constant time after SHA-256 hashing; the
|
|
22
|
+
* callback form is available for external stores.
|
|
23
|
+
*/
|
|
24
|
+
export declare function basicAuth(options: BasicAuthStaticOptions): BasicAuthPlugin<string>;
|
|
25
|
+
export declare function basicAuth<P>(options: BasicAuthStaticOptions<P>): BasicAuthPlugin<P>;
|
|
26
|
+
export declare function basicAuth<P>(options: BasicAuthVerifyOptions<P>): BasicAuthPlugin<P>;
|
|
27
|
+
//# sourceMappingURL=basic-auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"basic-auth.d.ts","sourceRoot":"","sources":["../src/basic-auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,WAAW,EAAE,MAAM,eAAe,CAAA;AAC9D,OAAO,EAGL,KAAK,YAAY,EAIlB,MAAM,aAAa,CAAA;AAEpB,MAAM,MAAM,eAAe,CAAC,CAAC,IAAI,WAAW,GAAG;IAC7C,SAAS,CAAC,OAAO,EAAE,OAAO,GAAG,CAAC,GAAG,IAAI,CAAA;IACrC,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,CAAC,CAAA;CACtC,CAAA;AAED,MAAM,WAAW,sBAAsB,CAAC,CAAC,GAAG,MAAM;IAChD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC,CAAA;IACtB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAC5B;AAED,MAAM,WAAW,sBAAsB,CAAC,CAAC;IACvC,QAAQ,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,YAAY,CAAC,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC,CAAA;IAC3F,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAC5B;AA0CD;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,sBAAsB,GAAG,eAAe,CAAC,MAAM,CAAC,CAAA;AACnF,wBAAgB,SAAS,CAAC,CAAC,EAAE,OAAO,EAAE,sBAAsB,CAAC,CAAC,CAAC,GAAG,eAAe,CAAC,CAAC,CAAC,CAAA;AACpF,wBAAgB,SAAS,CAAC,CAAC,EAAE,OAAO,EAAE,sBAAsB,CAAC,CAAC,CAAC,GAAG,eAAe,CAAC,CAAC,CAAC,CAAA"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { definePlugin } from "@nifrajs/core";
|
|
2
|
+
import { decodeBase64, jsonError, quotedHeaderValue, sha256, timingSafeEqualBytes, } from "./_utils.js";
|
|
3
|
+
const UTF8 = new TextDecoder("utf-8", { fatal: true });
|
|
4
|
+
function challenge(realm) {
|
|
5
|
+
return `Basic realm="${quotedHeaderValue(realm)}", charset="UTF-8"`;
|
|
6
|
+
}
|
|
7
|
+
function reject(realm) {
|
|
8
|
+
return jsonError(401, "unauthorized", { "www-authenticate": challenge(realm) });
|
|
9
|
+
}
|
|
10
|
+
function credentials(request) {
|
|
11
|
+
const header = request.headers.get("authorization");
|
|
12
|
+
if (header?.startsWith("Basic ") !== true)
|
|
13
|
+
return null;
|
|
14
|
+
const raw = decodeBase64(header.slice(6).trim());
|
|
15
|
+
if (raw === null)
|
|
16
|
+
return null;
|
|
17
|
+
let decoded;
|
|
18
|
+
try {
|
|
19
|
+
decoded = UTF8.decode(raw);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
const colon = decoded.indexOf(":");
|
|
25
|
+
if (colon < 0)
|
|
26
|
+
return null;
|
|
27
|
+
return { username: decoded.slice(0, colon), password: decoded.slice(colon + 1) };
|
|
28
|
+
}
|
|
29
|
+
async function staticVerifier(username, password, principal) {
|
|
30
|
+
const [expectedUser, expectedPass] = await Promise.all([sha256(username), sha256(password)]);
|
|
31
|
+
return async (u, p) => {
|
|
32
|
+
const [gotUser, gotPass] = await Promise.all([sha256(u), sha256(p)]);
|
|
33
|
+
const userOk = timingSafeEqualBytes(gotUser, expectedUser);
|
|
34
|
+
const passOk = timingSafeEqualBytes(gotPass, expectedPass);
|
|
35
|
+
return userOk && passOk ? principal : null;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
export function basicAuth(options) {
|
|
39
|
+
const realm = options.realm ?? "api";
|
|
40
|
+
const optional = options.optional === true;
|
|
41
|
+
const store = new WeakMap();
|
|
42
|
+
const verifyPromise = "verify" in options
|
|
43
|
+
? Promise.resolve(options.verify)
|
|
44
|
+
: staticVerifier(options.username, options.password, options.principal ?? options.username);
|
|
45
|
+
const plugin = definePlugin("basicAuth", (app) => app.beforeHandle(async (c) => {
|
|
46
|
+
const parsed = credentials(c.req);
|
|
47
|
+
const principal = parsed === null ? null : await (await verifyPromise)(parsed.username, parsed.password);
|
|
48
|
+
if (principal !== null && principal !== undefined) {
|
|
49
|
+
store.set(c.req, principal);
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
return optional ? undefined : reject(realm);
|
|
53
|
+
}));
|
|
54
|
+
return Object.assign(plugin, {
|
|
55
|
+
principal: (request) => store.get(request) ?? null,
|
|
56
|
+
requirePrincipal: (request) => {
|
|
57
|
+
const principal = store.get(request);
|
|
58
|
+
if (principal === undefined)
|
|
59
|
+
throw reject(realm);
|
|
60
|
+
return principal;
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=basic-auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"basic-auth.js","sourceRoot":"","sources":["../src/basic-auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Middleware } from "@nifrajs/core";
|
|
2
|
+
export interface BodyLimitOptions {
|
|
3
|
+
/** Maximum raw request-body bytes accepted. */
|
|
4
|
+
readonly maxBytes: number;
|
|
5
|
+
/** Methods to inspect. Default: every method except GET/HEAD/OPTIONS/TRACE. */
|
|
6
|
+
readonly methods?: readonly string[];
|
|
7
|
+
/** Response error name for over-limit bodies. Default `"payload_too_large"`. */
|
|
8
|
+
readonly error?: string;
|
|
9
|
+
/** Allow bodies without Content-Length. Default false (fail closed with 411). */
|
|
10
|
+
readonly allowLengthless?: boolean;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Enforce a raw byte cap for request bodies before routing. This middleware is intentionally
|
|
14
|
+
* Content-Length based: reading a cloned Web body is not transparent on every runtime. Lengthless
|
|
15
|
+
* bodies fail closed by default; use route-level `c.boundedBody()` / schema validation for endpoints
|
|
16
|
+
* that intentionally accept streamed bodies.
|
|
17
|
+
*/
|
|
18
|
+
export declare function bodyLimit(options: BodyLimitOptions): Middleware;
|
|
19
|
+
//# sourceMappingURL=body-limit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"body-limit.d.ts","sourceRoot":"","sources":["../src/body-limit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAG/C,MAAM,WAAW,gBAAgB;IAC/B,+CAA+C;IAC/C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,+EAA+E;IAC/E,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACpC,gFAAgF;IAChF,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,iFAAiF;IACjF,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAA;CACnC;AASD;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,UAAU,CAyB/D"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { jsonError, SAFE_METHODS } from "./_utils.js";
|
|
2
|
+
function parseContentLength(value) {
|
|
3
|
+
if (value === null)
|
|
4
|
+
return undefined;
|
|
5
|
+
if (!/^\d+$/.test(value))
|
|
6
|
+
return null;
|
|
7
|
+
const n = Number(value);
|
|
8
|
+
return Number.isSafeInteger(n) ? n : Infinity;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Enforce a raw byte cap for request bodies before routing. This middleware is intentionally
|
|
12
|
+
* Content-Length based: reading a cloned Web body is not transparent on every runtime. Lengthless
|
|
13
|
+
* bodies fail closed by default; use route-level `c.boundedBody()` / schema validation for endpoints
|
|
14
|
+
* that intentionally accept streamed bodies.
|
|
15
|
+
*/
|
|
16
|
+
export function bodyLimit(options) {
|
|
17
|
+
const { maxBytes } = options;
|
|
18
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes < 0) {
|
|
19
|
+
throw new Error("bodyLimit: maxBytes must be a non-negative safe integer");
|
|
20
|
+
}
|
|
21
|
+
const methods = options.methods !== undefined ? new Set(options.methods.map((m) => m.toUpperCase())) : undefined;
|
|
22
|
+
const error = options.error ?? "payload_too_large";
|
|
23
|
+
const allowLengthless = options.allowLengthless === true;
|
|
24
|
+
return {
|
|
25
|
+
name: "body-limit",
|
|
26
|
+
onRequest(req) {
|
|
27
|
+
if (methods !== undefined ? !methods.has(req.method) : SAFE_METHODS.has(req.method)) {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
const contentLength = parseContentLength(req.headers.get("content-length"));
|
|
31
|
+
if (contentLength === null)
|
|
32
|
+
return jsonError(400, "invalid_content_length");
|
|
33
|
+
if (contentLength === undefined && req.body !== null && !allowLengthless) {
|
|
34
|
+
return jsonError(411, "length_required");
|
|
35
|
+
}
|
|
36
|
+
if (contentLength !== undefined && contentLength > maxBytes)
|
|
37
|
+
return jsonError(413, error);
|
|
38
|
+
return undefined;
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=body-limit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"body-limit.js","sourceRoot":"","sources":["../src/body-limit.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAarD,SAAS,kBAAkB,CAAC,KAAoB;IAC9C,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,SAAS,CAAA;IACpC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAA;IACrC,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAA;IACvB,OAAO,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAA;AAC/C,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,SAAS,CAAC,OAAyB;IACjD,MAAM,EAAE,QAAQ,EAAE,GAAG,OAAO,CAAA;IAC5B,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,QAAQ,GAAG,CAAC,EAAE,CAAC;QACpD,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAA;IAC5E,CAAC;IACD,MAAM,OAAO,GACX,OAAO,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;IAClG,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,mBAAmB,CAAA;IAClD,MAAM,eAAe,GAAG,OAAO,CAAC,eAAe,KAAK,IAAI,CAAA;IAExD,OAAO;QACL,IAAI,EAAE,YAAY;QAClB,SAAS,CAAC,GAAG;YACX,IAAI,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;gBACpF,OAAO,SAAS,CAAA;YAClB,CAAC;YACD,MAAM,aAAa,GAAG,kBAAkB,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC,CAAA;YAC3E,IAAI,aAAa,KAAK,IAAI;gBAAE,OAAO,SAAS,CAAC,GAAG,EAAE,wBAAwB,CAAC,CAAA;YAC3E,IAAI,aAAa,KAAK,SAAS,IAAI,GAAG,CAAC,IAAI,KAAK,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;gBACzE,OAAO,SAAS,CAAC,GAAG,EAAE,iBAAiB,CAAC,CAAA;YAC1C,CAAC;YACD,IAAI,aAAa,KAAK,SAAS,IAAI,aAAa,GAAG,QAAQ;gBAAE,OAAO,SAAS,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;YACzF,OAAO,SAAS,CAAA;QAClB,CAAC;KACF,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface CacheControlOptions {
|
|
2
|
+
/** Methods whose responses get the header. Default `["GET", "HEAD"]`. */
|
|
3
|
+
readonly methods?: readonly string[];
|
|
4
|
+
/** Predicate over the response status. Default: 2xx only. */
|
|
5
|
+
readonly status?: (status: number) => boolean;
|
|
6
|
+
/** When `true` (default), don't overwrite a `Cache-Control` the handler already set. */
|
|
7
|
+
readonly respectExisting?: boolean;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Set a `Cache-Control` header on matching responses. `value` is either a fixed directive string or a
|
|
11
|
+
* function of the request (return `undefined` to leave a response untouched — e.g. cache by path).
|
|
12
|
+
* Defaults to `GET`/`HEAD` + 2xx, and never clobbers a `Cache-Control` the handler set itself.
|
|
13
|
+
*
|
|
14
|
+
* ```ts
|
|
15
|
+
* app.use(cacheControl("public, max-age=3600, stale-while-revalidate=60"))
|
|
16
|
+
* // or per-path:
|
|
17
|
+
* app.use(cacheControl((req) =>
|
|
18
|
+
* new URL(req.url).pathname.startsWith("/assets/") ? "public, max-age=31536000, immutable" : undefined,
|
|
19
|
+
* ))
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export declare function cacheControl(value: string | ((request: Request) => string | undefined), options?: CacheControlOptions): import("@nifrajs/core").NifraPlugin<import("@nifrajs/core").AnyServer, import("@nifrajs/core").AnyServer>;
|
|
23
|
+
//# sourceMappingURL=cache-control.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache-control.d.ts","sourceRoot":"","sources":["../src/cache-control.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,mBAAmB;IAClC,yEAAyE;IACzE,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACpC,6DAA6D;IAC7D,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAA;IAC7C,wFAAwF;IACxF,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAA;CACnC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAC1B,KAAK,EAAE,MAAM,GAAG,CAAC,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,GAAG,SAAS,CAAC,EAC1D,OAAO,GAAE,mBAAwB,6GAkBlC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { definePlugin } from "@nifrajs/core";
|
|
2
|
+
/**
|
|
3
|
+
* Set a `Cache-Control` header on matching responses. `value` is either a fixed directive string or a
|
|
4
|
+
* function of the request (return `undefined` to leave a response untouched — e.g. cache by path).
|
|
5
|
+
* Defaults to `GET`/`HEAD` + 2xx, and never clobbers a `Cache-Control` the handler set itself.
|
|
6
|
+
*
|
|
7
|
+
* ```ts
|
|
8
|
+
* app.use(cacheControl("public, max-age=3600, stale-while-revalidate=60"))
|
|
9
|
+
* // or per-path:
|
|
10
|
+
* app.use(cacheControl((req) =>
|
|
11
|
+
* new URL(req.url).pathname.startsWith("/assets/") ? "public, max-age=31536000, immutable" : undefined,
|
|
12
|
+
* ))
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export function cacheControl(value, options = {}) {
|
|
16
|
+
const methods = new Set((options.methods ?? ["GET", "HEAD"]).map((m) => m.toUpperCase()));
|
|
17
|
+
const statusOk = options.status ?? ((status) => status >= 200 && status < 300);
|
|
18
|
+
const respectExisting = options.respectExisting !== false;
|
|
19
|
+
const resolve = typeof value === "function" ? value : () => value;
|
|
20
|
+
return definePlugin("cacheControl", (app) => app.onResponse((res, req) => {
|
|
21
|
+
if (!methods.has(req.method))
|
|
22
|
+
return res;
|
|
23
|
+
if (!statusOk(res.status))
|
|
24
|
+
return res;
|
|
25
|
+
if (respectExisting && res.headers.has("cache-control"))
|
|
26
|
+
return res;
|
|
27
|
+
const directive = resolve(req);
|
|
28
|
+
if (directive === undefined)
|
|
29
|
+
return res;
|
|
30
|
+
const headers = new Headers(res.headers);
|
|
31
|
+
headers.set("cache-control", directive);
|
|
32
|
+
return new Response(res.body, { status: res.status, statusText: res.statusText, headers });
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=cache-control.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache-control.js","sourceRoot":"","sources":["../src/cache-control.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAW5C;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,YAAY,CAC1B,KAA0D,EAC1D,UAA+B,EAAE;IAEjC,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,CAAC,OAAO,CAAC,OAAO,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAA;IACzF,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,IAAI,CAAC,CAAC,MAAc,EAAE,EAAE,CAAC,MAAM,IAAI,GAAG,IAAI,MAAM,GAAG,GAAG,CAAC,CAAA;IACtF,MAAM,eAAe,GAAG,OAAO,CAAC,eAAe,KAAK,KAAK,CAAA;IACzD,MAAM,OAAO,GAAG,OAAO,KAAK,KAAK,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,KAAK,CAAA;IACjE,OAAO,YAAY,CAAC,cAAc,EAAE,CAAC,GAAG,EAAE,EAAE,CAC1C,GAAG,CAAC,UAAU,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QAC1B,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC;YAAE,OAAO,GAAG,CAAA;QACxC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC;YAAE,OAAO,GAAG,CAAA;QACrC,IAAI,eAAe,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC;YAAE,OAAO,GAAG,CAAA;QACnE,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,CAAA;QAC9B,IAAI,SAAS,KAAK,SAAS;YAAE,OAAO,GAAG,CAAA;QACvC,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;QACxC,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,SAAS,CAAC,CAAA;QACvC,OAAO,IAAI,QAAQ,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,EAAE,GAAG,CAAC,UAAU,EAAE,OAAO,EAAE,CAAC,CAAA;IAC5F,CAAC,CAAC,CACH,CAAA;AACH,CAAC"}
|
package/dist/cache.d.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { Middleware } from "@nifrajs/core";
|
|
2
|
+
import type { MaybePromise } from "./_utils.js";
|
|
3
|
+
export interface CachedResponse {
|
|
4
|
+
readonly status: number;
|
|
5
|
+
readonly statusText: string;
|
|
6
|
+
readonly headers: readonly [string, string][];
|
|
7
|
+
readonly body: Uint8Array;
|
|
8
|
+
readonly expiresAt: number;
|
|
9
|
+
readonly storedAt: number;
|
|
10
|
+
}
|
|
11
|
+
export interface ResponseCacheStore {
|
|
12
|
+
get(key: string): MaybePromise<CachedResponse | undefined>;
|
|
13
|
+
set(key: string, response: CachedResponse): MaybePromise<void>;
|
|
14
|
+
delete?(key: string): MaybePromise<void>;
|
|
15
|
+
}
|
|
16
|
+
export interface MemoryResponseCacheOptions {
|
|
17
|
+
/** Maximum entries retained. Default `1000`. Oldest entries are evicted first. */
|
|
18
|
+
readonly maxEntries?: number;
|
|
19
|
+
/** Allow in-process cache in production. Off by default because it is per-instance. */
|
|
20
|
+
readonly allowInProduction?: boolean;
|
|
21
|
+
}
|
|
22
|
+
export declare class MemoryResponseCache implements ResponseCacheStore {
|
|
23
|
+
private readonly entries;
|
|
24
|
+
private readonly maxEntries;
|
|
25
|
+
constructor(options?: MemoryResponseCacheOptions);
|
|
26
|
+
get(key: string): CachedResponse | undefined;
|
|
27
|
+
set(key: string, response: CachedResponse): void;
|
|
28
|
+
delete(key: string): void;
|
|
29
|
+
}
|
|
30
|
+
export interface CacheOptions {
|
|
31
|
+
readonly store: ResponseCacheStore;
|
|
32
|
+
/** Time to live in milliseconds. */
|
|
33
|
+
readonly ttlMs: number;
|
|
34
|
+
/** Methods cached. Default `["GET", "HEAD"]`. */
|
|
35
|
+
readonly methods?: readonly string[];
|
|
36
|
+
/** Status predicate. Default: `200` only. */
|
|
37
|
+
readonly status?: (status: number) => boolean;
|
|
38
|
+
/** Header names included in the cache key and emitted in `Vary`. */
|
|
39
|
+
readonly vary?: readonly string[];
|
|
40
|
+
/** Custom cache key. Receives the normalized `vary` headers through the request itself. */
|
|
41
|
+
readonly key?: (request: Request) => string;
|
|
42
|
+
/** Maximum response bytes to store. Default `1_000_000`. Larger responses pass through. */
|
|
43
|
+
readonly maxBytes?: number;
|
|
44
|
+
/** Respect request `Cache-Control: no-cache/no-store`. Default `true`. */
|
|
45
|
+
readonly respectRequestCacheControl?: boolean;
|
|
46
|
+
/** Respect response `Cache-Control: private/no-store`. Default `true`. */
|
|
47
|
+
readonly respectResponseCacheControl?: boolean;
|
|
48
|
+
/** Cache responses with `Set-Cookie`. Default `false`. */
|
|
49
|
+
readonly cacheSetCookie?: boolean;
|
|
50
|
+
/** Response header for cache status. Default `"x-nifra-cache"`; set `false` to disable. */
|
|
51
|
+
readonly cacheStatusHeader?: string | false;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Full response cache for small, cacheable responses. Use a shared `store` in production. The
|
|
55
|
+
* middleware honors `Cache-Control` by default, avoids `Set-Cookie`, caps stored bytes, emits `Age`,
|
|
56
|
+
* and keeps `Vary` headers aligned with the cache key.
|
|
57
|
+
*/
|
|
58
|
+
export declare function cache(options: CacheOptions): Middleware;
|
|
59
|
+
export declare const responseCache: typeof cache;
|
|
60
|
+
//# sourceMappingURL=cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAC/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE/C,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,OAAO,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAA;IAC7C,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAA;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAC1B;AAED,MAAM,WAAW,kBAAkB;IACjC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,CAAC,cAAc,GAAG,SAAS,CAAC,CAAA;IAC1D,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,GAAG,YAAY,CAAC,IAAI,CAAC,CAAA;IAC9D,MAAM,CAAC,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,CAAC,IAAI,CAAC,CAAA;CACzC;AAED,MAAM,WAAW,0BAA0B;IACzC,kFAAkF;IAClF,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,uFAAuF;IACvF,QAAQ,CAAC,iBAAiB,CAAC,EAAE,OAAO,CAAA;CACrC;AAED,qBAAa,mBAAoB,YAAW,kBAAkB;IAC5D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoC;IAC5D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAQ;gBAEvB,OAAO,GAAE,0BAA+B;IAcpD,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS;IAU5C,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,GAAG,IAAI;IAQhD,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;CAG1B;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,KAAK,EAAE,kBAAkB,CAAA;IAClC,oCAAoC;IACpC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,iDAAiD;IACjD,QAAQ,CAAC,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACpC,6CAA6C;IAC7C,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAA;IAC7C,oEAAoE;IACpE,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACjC,2FAA2F;IAC3F,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,CAAA;IAC3C,2FAA2F;IAC3F,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;IAC1B,0EAA0E;IAC1E,QAAQ,CAAC,0BAA0B,CAAC,EAAE,OAAO,CAAA;IAC7C,0EAA0E;IAC1E,QAAQ,CAAC,2BAA2B,CAAC,EAAE,OAAO,CAAA;IAC9C,0DAA0D;IAC1D,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,CAAA;IACjC,2FAA2F;IAC3F,QAAQ,CAAC,iBAAiB,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;CAC5C;AAwFD;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,OAAO,EAAE,YAAY,GAAG,UAAU,CA6FvD;AAED,eAAO,MAAM,aAAa,cAAQ,CAAA"}
|