@daloyjs/core 0.16.0 → 0.28.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.
Files changed (102) hide show
  1. package/README.md +3 -3
  2. package/dist/adapters/bun.d.ts.map +1 -1
  3. package/dist/adapters/bun.js +38 -7
  4. package/dist/adapters/bun.js.map +1 -1
  5. package/dist/adapters/node.d.ts.map +1 -1
  6. package/dist/adapters/node.js +29 -7
  7. package/dist/adapters/node.js.map +1 -1
  8. package/dist/app.d.ts +412 -3
  9. package/dist/app.d.ts.map +1 -1
  10. package/dist/app.js +895 -29
  11. package/dist/app.js.map +1 -1
  12. package/dist/cli.d.ts +4 -0
  13. package/dist/cli.d.ts.map +1 -1
  14. package/dist/cli.js +218 -1
  15. package/dist/cli.js.map +1 -1
  16. package/dist/combine.d.ts +97 -0
  17. package/dist/combine.d.ts.map +1 -0
  18. package/dist/combine.js +247 -0
  19. package/dist/combine.js.map +1 -0
  20. package/dist/compression.d.ts +127 -0
  21. package/dist/compression.d.ts.map +1 -0
  22. package/dist/compression.js +368 -0
  23. package/dist/compression.js.map +1 -0
  24. package/dist/config.d.ts +97 -0
  25. package/dist/config.d.ts.map +1 -0
  26. package/dist/config.js +132 -0
  27. package/dist/config.js.map +1 -0
  28. package/dist/conn-info.d.ts +121 -0
  29. package/dist/conn-info.d.ts.map +1 -0
  30. package/dist/conn-info.js +145 -0
  31. package/dist/conn-info.js.map +1 -0
  32. package/dist/cookie.d.ts +112 -0
  33. package/dist/cookie.d.ts.map +1 -0
  34. package/dist/cookie.js +185 -0
  35. package/dist/cookie.js.map +1 -0
  36. package/dist/dependency.d.ts +47 -0
  37. package/dist/dependency.d.ts.map +1 -0
  38. package/dist/dependency.js +68 -0
  39. package/dist/dependency.js.map +1 -0
  40. package/dist/etag.d.ts +48 -0
  41. package/dist/etag.d.ts.map +1 -0
  42. package/dist/etag.js +117 -0
  43. package/dist/etag.js.map +1 -0
  44. package/dist/index.d.ts +41 -12
  45. package/dist/index.d.ts.map +1 -1
  46. package/dist/index.js +20 -5
  47. package/dist/index.js.map +1 -1
  48. package/dist/ip-restriction.d.ts +75 -0
  49. package/dist/ip-restriction.d.ts.map +1 -0
  50. package/dist/ip-restriction.js +203 -0
  51. package/dist/ip-restriction.js.map +1 -0
  52. package/dist/jwk.d.ts +82 -0
  53. package/dist/jwk.d.ts.map +1 -0
  54. package/dist/jwk.js +269 -0
  55. package/dist/jwk.js.map +1 -0
  56. package/dist/jwt.d.ts +103 -0
  57. package/dist/jwt.d.ts.map +1 -0
  58. package/dist/jwt.js +437 -0
  59. package/dist/jwt.js.map +1 -0
  60. package/dist/load-shedding.d.ts +73 -0
  61. package/dist/load-shedding.d.ts.map +1 -0
  62. package/dist/load-shedding.js +171 -0
  63. package/dist/load-shedding.js.map +1 -0
  64. package/dist/middleware.d.ts +205 -2
  65. package/dist/middleware.d.ts.map +1 -1
  66. package/dist/middleware.js +399 -78
  67. package/dist/middleware.js.map +1 -1
  68. package/dist/multipart.d.ts +17 -0
  69. package/dist/multipart.d.ts.map +1 -1
  70. package/dist/multipart.js +117 -1
  71. package/dist/multipart.js.map +1 -1
  72. package/dist/openapi.d.ts +8 -0
  73. package/dist/openapi.d.ts.map +1 -1
  74. package/dist/openapi.js +18 -1
  75. package/dist/openapi.js.map +1 -1
  76. package/dist/security-schemes.d.ts +21 -5
  77. package/dist/security-schemes.d.ts.map +1 -1
  78. package/dist/security-schemes.js +32 -5
  79. package/dist/security-schemes.js.map +1 -1
  80. package/dist/security.d.ts +32 -0
  81. package/dist/security.d.ts.map +1 -1
  82. package/dist/security.js +79 -0
  83. package/dist/security.js.map +1 -1
  84. package/dist/session.d.ts +43 -1
  85. package/dist/session.d.ts.map +1 -1
  86. package/dist/session.js +130 -87
  87. package/dist/session.js.map +1 -1
  88. package/dist/subdomains.d.ts +97 -0
  89. package/dist/subdomains.d.ts.map +1 -0
  90. package/dist/subdomains.js +157 -0
  91. package/dist/subdomains.js.map +1 -0
  92. package/dist/time-claims.d.ts +72 -0
  93. package/dist/time-claims.d.ts.map +1 -0
  94. package/dist/time-claims.js +88 -0
  95. package/dist/time-claims.js.map +1 -0
  96. package/dist/types.d.ts +50 -0
  97. package/dist/types.d.ts.map +1 -1
  98. package/dist/websocket.d.ts +51 -34
  99. package/dist/websocket.d.ts.map +1 -1
  100. package/dist/websocket.js +162 -2
  101. package/dist/websocket.js.map +1 -1
  102. package/package.json +30 -3
@@ -0,0 +1,203 @@
1
+ /**
2
+ * Network-layer access control for {@link Hooks}. The {@link ipRestriction}
3
+ * middleware enforces IPv4 / IPv6 / CIDR allow- and deny-lists. Because the
4
+ * Web-standard {@link Request} does not expose a peer address, callers must
5
+ * either provide a trusted resolver or explicitly opt in to trusted proxy
6
+ * headers.
7
+ *
8
+ * @since 0.19.0
9
+ */
10
+ import { ForbiddenError } from "./errors.js";
11
+ /**
12
+ * Block or allow requests by source IP / CIDR range. In direct Web-standard
13
+ * runtimes, pass `resolveIp` from the adapter-specific connection metadata.
14
+ * Behind a trusted proxy chain, set `trustProxyHeaders: true` to read
15
+ * `X-Forwarded-For` / `X-Real-IP`.
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * app.use(ipRestriction({
20
+ * allow: ["10.0.0.0/8", "::1"],
21
+ * deny: ["10.6.6.0/24"],
22
+ * trustProxyHeaders: true,
23
+ * }));
24
+ * ```
25
+ *
26
+ * On reject the middleware throws a {@link ForbiddenError}, which Daloy
27
+ * renders as RFC 9457 `application/problem+json`.
28
+ *
29
+ * @since 0.19.0
30
+ */
31
+ export function ipRestriction(opts) {
32
+ if (!opts.allow?.length && !opts.deny?.length) {
33
+ throw new Error('ipRestriction(): at least one of "allow" or "deny" must be provided.');
34
+ }
35
+ const allow = (opts.allow ?? []).map(compileCidrMatcher);
36
+ const deny = (opts.deny ?? []).map(compileCidrMatcher);
37
+ const resolveIp = opts.resolveIp ??
38
+ (opts.trustProxyHeaders ? forwardedIpResolver : noIpResolver);
39
+ const message = opts.message ?? "IP address not permitted";
40
+ return {
41
+ beforeHandle(ctx) {
42
+ const raw = resolveIp(ctx);
43
+ if (!raw)
44
+ throw new ForbiddenError(message);
45
+ const parsed = parseIp(raw);
46
+ if (!parsed)
47
+ throw new ForbiddenError(message);
48
+ if (deny.some((m) => matchesMatcher(parsed, m))) {
49
+ throw new ForbiddenError(message);
50
+ }
51
+ if (allow.length > 0 && !allow.some((m) => matchesMatcher(parsed, m))) {
52
+ throw new ForbiddenError(message);
53
+ }
54
+ },
55
+ };
56
+ }
57
+ function noIpResolver(_ctx) {
58
+ return undefined;
59
+ }
60
+ function forwardedIpResolver(ctx) {
61
+ const headers = ctx.request.headers;
62
+ const forwarded = headers.get("x-forwarded-for");
63
+ if (forwarded)
64
+ return forwarded.split(",")[0]?.trim();
65
+ return headers.get("x-real-ip") ?? undefined;
66
+ }
67
+ function matchesMatcher(ip, m) {
68
+ const candidate = normalizeFamily(ip, m.family);
69
+ if (!candidate)
70
+ return false;
71
+ const expected = m.bytes;
72
+ const totalBits = candidate.length * 8;
73
+ const prefix = Math.min(m.prefix, totalBits);
74
+ const fullBytes = prefix >> 3;
75
+ for (let i = 0; i < fullBytes; i++) {
76
+ if (candidate[i] !== expected[i])
77
+ return false;
78
+ }
79
+ const remaining = prefix - fullBytes * 8;
80
+ if (remaining === 0)
81
+ return true;
82
+ const mask = 0xff << (8 - remaining);
83
+ return ((candidate[fullBytes] ^ expected[fullBytes]) & mask) === 0;
84
+ }
85
+ function compileCidrMatcher(input) {
86
+ let addr = input;
87
+ let prefixStr;
88
+ if (input.includes("/")) {
89
+ const slash = input.indexOf("/");
90
+ addr = input.slice(0, slash);
91
+ prefixStr = input.slice(slash + 1);
92
+ }
93
+ const parsed = parseIp(addr);
94
+ if (!parsed) {
95
+ throw new Error(`ipRestriction(): invalid IP address ${JSON.stringify(input)}.`);
96
+ }
97
+ const totalBits = parsed.family === 4 ? 32 : 128;
98
+ let prefix = totalBits;
99
+ if (prefixStr !== undefined) {
100
+ if (!/^\d+$/.test(prefixStr)) {
101
+ throw new Error(`ipRestriction(): invalid CIDR prefix in ${JSON.stringify(input)}.`);
102
+ }
103
+ prefix = Number.parseInt(prefixStr, 10);
104
+ if (!Number.isInteger(prefix) || prefix < 0 || prefix > totalBits) {
105
+ throw new Error(`ipRestriction(): invalid CIDR prefix in ${JSON.stringify(input)}.`);
106
+ }
107
+ }
108
+ return { family: parsed.family, prefix, bytes: applyPrefixMask(parsed.bytes, prefix) };
109
+ }
110
+ function normalizeFamily(ip, family) {
111
+ if (ip.family === family)
112
+ return ip.bytes;
113
+ if (ip.family === 6 && family === 4) {
114
+ // IPv4-mapped IPv6 (::ffff:a.b.c.d) — accept as IPv4.
115
+ const b = ip.bytes;
116
+ const isMapped = b.slice(0, 10).every((x) => x === 0) && b[10] === 0xff && b[11] === 0xff;
117
+ if (isMapped)
118
+ return b.slice(12);
119
+ return undefined;
120
+ }
121
+ return undefined;
122
+ }
123
+ function applyPrefixMask(bytes, prefix) {
124
+ const out = new Uint8Array(bytes);
125
+ const fullBytes = prefix >> 3;
126
+ const remaining = prefix - fullBytes * 8;
127
+ if (remaining > 0 && fullBytes < out.length) {
128
+ const mask = 0xff << (8 - remaining);
129
+ out[fullBytes] = out[fullBytes] & mask;
130
+ }
131
+ for (let i = fullBytes + (remaining > 0 ? 1 : 0); i < out.length; i++) {
132
+ out[i] = 0;
133
+ }
134
+ return out;
135
+ }
136
+ function parseIp(input) {
137
+ const trimmed = input.trim();
138
+ if (trimmed.includes(":"))
139
+ return parseIPv6(trimmed);
140
+ return parseIPv4(trimmed);
141
+ }
142
+ function parseIPv4(input) {
143
+ const parts = input.split(".");
144
+ if (parts.length !== 4)
145
+ return undefined;
146
+ const bytes = new Uint8Array(4);
147
+ for (let i = 0; i < 4; i++) {
148
+ const part = parts[i];
149
+ if (!/^\d{1,3}$/.test(part))
150
+ return undefined;
151
+ const n = Number.parseInt(part, 10);
152
+ if (n < 0 || n > 255)
153
+ return undefined;
154
+ bytes[i] = n;
155
+ }
156
+ return { bytes, family: 4 };
157
+ }
158
+ function parseIPv6(input) {
159
+ // Support IPv4-mapped tail (::ffff:1.2.3.4).
160
+ let working = input;
161
+ const lastColon = working.lastIndexOf(":");
162
+ if (lastColon !== -1 && working.slice(lastColon + 1).includes(".")) {
163
+ const v4 = parseIPv4(working.slice(lastColon + 1));
164
+ if (!v4)
165
+ return undefined;
166
+ const hi = (v4.bytes[0] << 8) | v4.bytes[1];
167
+ const lo = (v4.bytes[2] << 8) | v4.bytes[3];
168
+ working =
169
+ working.slice(0, lastColon + 1) +
170
+ hi.toString(16) +
171
+ ":" +
172
+ lo.toString(16);
173
+ }
174
+ const parts = working.split("::");
175
+ if (parts.length > 2)
176
+ return undefined;
177
+ const headGroups = parts[0] === "" ? [] : parts[0].split(":");
178
+ const tailGroups = parts.length === 2 && parts[1] !== "" ? parts[1].split(":") : [];
179
+ const explicit = headGroups.length + tailGroups.length;
180
+ if (explicit > 8)
181
+ return undefined;
182
+ if (parts.length === 1 && explicit !== 8)
183
+ return undefined;
184
+ const missing = parts.length === 2 ? 8 - explicit : 0;
185
+ const groups = [
186
+ ...headGroups,
187
+ ...Array.from({ length: missing }, () => "0"),
188
+ ...tailGroups,
189
+ ];
190
+ if (groups.length !== 8)
191
+ return undefined;
192
+ const bytes = new Uint8Array(16);
193
+ for (let index = 0; index < 8; index++) {
194
+ const group = groups[index];
195
+ if (!/^[0-9a-fA-F]{1,4}$/.test(group))
196
+ return undefined;
197
+ const n = Number.parseInt(group, 16);
198
+ bytes[index * 2] = (n >> 8) & 0xff;
199
+ bytes[index * 2 + 1] = n & 0xff;
200
+ }
201
+ return { bytes, family: 6 };
202
+ }
203
+ //# sourceMappingURL=ip-restriction.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ip-restriction.js","sourceRoot":"","sources":["../src/ip-restriction.ts"],"names":[],"mappings":"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"}
package/dist/jwk.d.ts ADDED
@@ -0,0 +1,82 @@
1
+ import { type JwtAlgorithm } from "./jwt.js";
2
+ import type { BaseContext, Hooks } from "./types.js";
3
+ /** Asymmetric algorithms accepted by {@link jwk}. */
4
+ export type JwkAlgorithm = Exclude<JwtAlgorithm, "HS256" | "HS384" | "HS512">;
5
+ /** Minimal JWKS document shape (RFC 7517 §5). */
6
+ export interface JwkSet {
7
+ keys: JsonWebKey[];
8
+ }
9
+ /**
10
+ * Source for the verifier's public keys. Either an in-memory JWKS, a
11
+ * `https://` URL (fetched with TTL caching), or a custom async resolver.
12
+ */
13
+ export type JwkSource = JwkSet | string | (() => JwkSet | Promise<JwkSet>);
14
+ /** Per-request payload-revalidation hook (Wave 5 leftover). */
15
+ export type JwkVerifyHook = (payload: Record<string, unknown>, ctx: BaseContext<any, any>) => boolean | void | Promise<boolean | void>;
16
+ /** Options for {@link jwk}. */
17
+ export interface JwkOptions {
18
+ /** JWKS source (object, URL, or resolver). */
19
+ jwks: JwkSource;
20
+ /**
21
+ * Explicit asymmetric algorithm allowlist. Required and non-empty.
22
+ * Symmetric `HS*` algorithms are refused at construction time.
23
+ */
24
+ algorithms: JwkAlgorithm[];
25
+ /** Optional expected issuer (string or allowlist). */
26
+ issuer?: string | string[];
27
+ /** Optional expected audience (string or allowlist). */
28
+ audience?: string | string[];
29
+ /** Clock skew tolerance applied to `exp` / `nbf` / `iat`. Default `0`. */
30
+ clockSkewSeconds?: number;
31
+ /** `WWW-Authenticate` realm. Default: `"api"`. */
32
+ realm?: string;
33
+ /**
34
+ * TTL for fetched JWKS responses (seconds). Default `300` (5m). Ignored
35
+ * when `jwks` is not a URL.
36
+ */
37
+ fetchTtlSeconds?: number;
38
+ /**
39
+ * Optional `fetch` implementation override (mainly for tests). Defaults
40
+ * to global `fetch`.
41
+ */
42
+ fetch?: typeof fetch;
43
+ /**
44
+ * Optional per-request revalidation hook. Returning `false` rejects the
45
+ * request with `403`; returning `true` or `undefined` accepts. Use for
46
+ * revocation lists / token-version counters / "user changed password since
47
+ * this JWT was issued".
48
+ *
49
+ * @since 0.22.0
50
+ */
51
+ verify?: JwkVerifyHook;
52
+ }
53
+ /**
54
+ * Bearer-token JWT middleware backed by a JWKS source. Refuses symmetric
55
+ * algorithms at construction time, requires every token to carry a `kid`
56
+ * header, matches that `kid` against the JWKS, and cross-checks the JWT
57
+ * header `alg` against the JWK's own `alg` (when present).
58
+ *
59
+ * @example
60
+ * ```ts
61
+ * import { App, jwk, requireScopes } from "@daloyjs/core";
62
+ *
63
+ * app.use(jwk({
64
+ * jwks: "https://login.example.com/.well-known/jwks.json",
65
+ * algorithms: ["RS256", "ES256"],
66
+ * issuer: "https://login.example.com/",
67
+ * audience: "books-api",
68
+ * }));
69
+ *
70
+ * app.route({
71
+ * method: "POST",
72
+ * path: "/items",
73
+ * hooks: requireScopes(["items:write"]),
74
+ * responses: { 200: { description: "ok" } },
75
+ * handler: () => ({ status: 200 as const, body: { ok: true } }),
76
+ * });
77
+ * ```
78
+ *
79
+ * @since 0.22.0
80
+ */
81
+ export declare function jwk(opts: JwkOptions): Hooks;
82
+ //# sourceMappingURL=jwk.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jwk.d.ts","sourceRoot":"","sources":["../src/jwk.ts"],"names":[],"mappings":"AA8BA,OAAO,EAGL,KAAK,YAAY,EAGlB,MAAM,UAAU,CAAC;AAClB,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAErD,qDAAqD;AACrD,MAAM,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC,CAAC;AAS9E,iDAAiD;AACjD,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,UAAU,EAAE,CAAC;CACpB;AAED;;;GAGG;AACH,MAAM,MAAM,SAAS,GACjB,MAAM,GACN,MAAM,GACN,CAAC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;AAErC,+DAA+D;AAC/D,MAAM,MAAM,aAAa,GAAG,CAC1B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,GAAG,EAAE,WAAW,CAAC,GAAG,EAAE,GAAG,CAAC,KACvB,OAAO,GAAG,IAAI,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;AAE9C,+BAA+B;AAC/B,MAAM,WAAW,UAAU;IACzB,8CAA8C;IAC9C,IAAI,EAAE,SAAS,CAAC;IAChB;;;OAGG;IACH,UAAU,EAAE,YAAY,EAAE,CAAC;IAC3B,sDAAsD;IACtD,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC3B,wDAAwD;IACxD,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC7B,0EAA0E;IAC1E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB;AAyGD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,GAAG,CAAC,IAAI,EAAE,UAAU,GAAG,KAAK,CAuG3C"}
package/dist/jwk.js ADDED
@@ -0,0 +1,269 @@
1
+ /**
2
+ * Wave 5 leftover: first-party `jwk()` Bearer-token middleware that refuses
3
+ * symmetric algorithms outright and verifies tokens against a JWKS source
4
+ * (static object or `https://` URL).
5
+ *
6
+ * Security defaults that cannot be silenced:
7
+ *
8
+ * - Asymmetric-only allowlist (`RS*` / `PS*` / `ES*` / `EdDSA`). Symmetric
9
+ * `HS*` algorithms are refused at construction — JWK / JWKS keys are
10
+ * public, and accepting `HS256` signed with the RSA public key is the
11
+ * classic JWKS confused-deputy attack.
12
+ * - `kid` header REQUIRED on every token. Matching JWK is selected from the
13
+ * JWKS by `kid`. A token without `kid`, or with a `kid` not present in
14
+ * the JWKS, is rejected with `401`.
15
+ * - JWT-header `alg` MUST be in the allowlist. When the JWK declares its own
16
+ * `alg`, the two MUST agree (RFC 7517 §4.4 cross-check).
17
+ * - `exp` / `nbf` / `iat` validated on every verify (Wave 5 leftover plan).
18
+ * - JWKS URLs MUST be `https://` (refused at construction otherwise).
19
+ * - Optional `verify(payload, ctx)` revalidation hook (Wave 5 leftover) for
20
+ * revocation lists, token-version counters, etc.
21
+ *
22
+ * The middleware extracts the Bearer token, runs verification, stamps the
23
+ * decoded payload on `ctx.state.user` (with the standard `sub` / `scope`
24
+ * surface that `requireScopes()` already understands), and rejects with
25
+ * RFC-6750-compliant `WWW-Authenticate: Bearer error="invalid_token"` on
26
+ * failure.
27
+ *
28
+ * @since 0.22.0
29
+ */
30
+ import { ForbiddenError } from "./errors.js";
31
+ import { createJwtVerifier, JwtError, } from "./jwt.js";
32
+ const ALLOWED_JWK_ALGS = new Set([
33
+ "RS256", "RS384", "RS512",
34
+ "PS256", "PS384", "PS512",
35
+ "ES256", "ES384", "ES512",
36
+ "EdDSA",
37
+ ]);
38
+ function unauthorized(realm, errorCode, description) {
39
+ const parts = [`Bearer realm="${realm}"`];
40
+ if (errorCode)
41
+ parts.push(`error="${errorCode}"`);
42
+ if (description)
43
+ parts.push(`error_description="${sanitizeAuthParam(description)}"`);
44
+ return new Response(JSON.stringify({
45
+ type: "https://daloyjs.dev/errors/unauthorized",
46
+ title: "Unauthorized",
47
+ status: 401,
48
+ detail: description,
49
+ }), {
50
+ status: 401,
51
+ headers: {
52
+ "content-type": "application/problem+json",
53
+ "www-authenticate": parts.join(", "),
54
+ "cache-control": "no-store",
55
+ },
56
+ });
57
+ }
58
+ function sanitizeAuthParam(value) {
59
+ return value.replace(/[\u0000-\u001f\u007f"\\]/g, "");
60
+ }
61
+ function isJwkSet(value) {
62
+ return (typeof value === "object" &&
63
+ value !== null &&
64
+ Array.isArray(value.keys));
65
+ }
66
+ function findJwkByKid(jwks, kid) {
67
+ for (const k of jwks.keys) {
68
+ if (typeof k === "object" && k !== null && k.kid === kid) {
69
+ return k;
70
+ }
71
+ }
72
+ return undefined;
73
+ }
74
+ function makeJwksLoader(source, fetchImpl, ttlSeconds) {
75
+ if (typeof source === "string") {
76
+ if (!source.startsWith("https://")) {
77
+ throw new Error("jwk(): jwks URL must be https:// — refusing plaintext JWKS source.");
78
+ }
79
+ let cache;
80
+ let inflight;
81
+ return () => {
82
+ const now = Date.now();
83
+ if (cache && now - cache.fetchedAt < ttlSeconds * 1000) {
84
+ return Promise.resolve(cache.jwks);
85
+ }
86
+ if (inflight)
87
+ return inflight;
88
+ inflight = (async () => {
89
+ try {
90
+ const res = await fetchImpl(source, {
91
+ headers: { accept: "application/json" },
92
+ });
93
+ if (!res.ok) {
94
+ throw new Error(`jwk(): JWKS fetch failed with status ${res.status}.`);
95
+ }
96
+ const body = (await res.json());
97
+ if (!isJwkSet(body)) {
98
+ throw new Error("jwk(): JWKS response is not a valid JWK Set (missing keys[]).");
99
+ }
100
+ cache = { jwks: body, fetchedAt: Date.now() };
101
+ return body;
102
+ }
103
+ finally {
104
+ inflight = undefined;
105
+ }
106
+ })();
107
+ return inflight;
108
+ };
109
+ }
110
+ if (typeof source === "function") {
111
+ return async () => {
112
+ const result = await source();
113
+ if (!isJwkSet(result)) {
114
+ throw new Error("jwk(): resolver returned a value that is not a valid JWK Set.");
115
+ }
116
+ return result;
117
+ };
118
+ }
119
+ if (!isJwkSet(source)) {
120
+ throw new Error("jwk(): jwks option must be a JWK Set, an https:// URL, or a resolver function.");
121
+ }
122
+ return () => Promise.resolve(source);
123
+ }
124
+ /**
125
+ * Bearer-token JWT middleware backed by a JWKS source. Refuses symmetric
126
+ * algorithms at construction time, requires every token to carry a `kid`
127
+ * header, matches that `kid` against the JWKS, and cross-checks the JWT
128
+ * header `alg` against the JWK's own `alg` (when present).
129
+ *
130
+ * @example
131
+ * ```ts
132
+ * import { App, jwk, requireScopes } from "@daloyjs/core";
133
+ *
134
+ * app.use(jwk({
135
+ * jwks: "https://login.example.com/.well-known/jwks.json",
136
+ * algorithms: ["RS256", "ES256"],
137
+ * issuer: "https://login.example.com/",
138
+ * audience: "books-api",
139
+ * }));
140
+ *
141
+ * app.route({
142
+ * method: "POST",
143
+ * path: "/items",
144
+ * hooks: requireScopes(["items:write"]),
145
+ * responses: { 200: { description: "ok" } },
146
+ * handler: () => ({ status: 200 as const, body: { ok: true } }),
147
+ * });
148
+ * ```
149
+ *
150
+ * @since 0.22.0
151
+ */
152
+ export function jwk(opts) {
153
+ if (!opts || typeof opts !== "object") {
154
+ throw new Error("jwk(): options object is required.");
155
+ }
156
+ if (!Array.isArray(opts.algorithms) || opts.algorithms.length === 0) {
157
+ throw new Error("jwk(): algorithms is a required, non-empty asymmetric-only allowlist.");
158
+ }
159
+ for (const alg of opts.algorithms) {
160
+ if (!ALLOWED_JWK_ALGS.has(alg)) {
161
+ throw new Error(`jwk(): algorithm "${String(alg)}" is not asymmetric — symmetric (HS*) algorithms are refused by jwk() to close the JWKS confused-deputy attack.`);
162
+ }
163
+ }
164
+ if (opts.fetchTtlSeconds !== undefined) {
165
+ if (typeof opts.fetchTtlSeconds !== "number" ||
166
+ !Number.isFinite(opts.fetchTtlSeconds) ||
167
+ opts.fetchTtlSeconds < 0) {
168
+ throw new Error("jwk(): fetchTtlSeconds must be a non-negative finite number.");
169
+ }
170
+ }
171
+ const realm = opts.realm ?? "api";
172
+ if (/["\r\n\0]/.test(realm)) {
173
+ throw new Error("jwk(): realm must not contain quotes, CR, LF, or NUL bytes.");
174
+ }
175
+ const ttl = opts.fetchTtlSeconds ?? 300;
176
+ const loader = makeJwksLoader(opts.jwks, opts.fetch ?? globalThis.fetch, ttl);
177
+ const algorithms = [...opts.algorithms];
178
+ let cachedVerifier;
179
+ let cachedJwksRef;
180
+ async function getVerifier() {
181
+ const jwks = await loader();
182
+ if (cachedVerifier && cachedJwksRef === jwks)
183
+ return cachedVerifier;
184
+ cachedJwksRef = jwks;
185
+ cachedVerifier = createJwtVerifier({
186
+ algorithms,
187
+ issuer: opts.issuer,
188
+ audience: opts.audience,
189
+ clockSkewSeconds: opts.clockSkewSeconds,
190
+ // Resolver picks the JWK by `kid` and enforces the alg cross-check.
191
+ key: async (header) => {
192
+ const kid = header.kid;
193
+ if (typeof kid !== "string" || kid.length === 0) {
194
+ throw new JwtError("missing_kid", "jwk(): token header is missing kid.");
195
+ }
196
+ const jwkMatch = findJwkByKid(jwks, kid);
197
+ if (!jwkMatch) {
198
+ throw new JwtError("kid_not_found", `jwk(): kid "${kid}" is not present in the JWKS.`);
199
+ }
200
+ const headerAlg = header.alg;
201
+ const jwkAlg = jwkMatch.alg;
202
+ if (typeof jwkAlg === "string" && jwkAlg !== headerAlg) {
203
+ throw new JwtError("alg_mismatch", `jwk(): token alg "${String(headerAlg)}" does not match JWK alg "${jwkAlg}".`);
204
+ }
205
+ return jwkMatch;
206
+ },
207
+ });
208
+ return cachedVerifier;
209
+ }
210
+ return {
211
+ async beforeHandle(ctx) {
212
+ const header = ctx.request.headers.get("authorization") ?? "";
213
+ const match = /^Bearer\s+(.+)$/i.exec(header);
214
+ if (!match) {
215
+ return unauthorized(realm);
216
+ }
217
+ let verified;
218
+ try {
219
+ const verifier = await getVerifier();
220
+ verified = await verifier.verify(match[1]);
221
+ }
222
+ catch (err) {
223
+ const message = err instanceof JwtError ? err.message : "JWT verification failed";
224
+ return unauthorized(realm, "invalid_token", message);
225
+ }
226
+ // Stamp a user-shaped view on ctx.state so requireScopes() and handlers
227
+ // can read it without re-parsing the JWT.
228
+ const payload = verified.payload;
229
+ const scopes = extractScopes(payload);
230
+ ctx.state.user = {
231
+ sub: payload.sub,
232
+ scopes,
233
+ claims: payload,
234
+ };
235
+ if (opts.verify) {
236
+ const ok = await opts.verify(payload, ctx);
237
+ if (ok === false)
238
+ throw new ForbiddenError("Token revoked");
239
+ }
240
+ return undefined;
241
+ },
242
+ };
243
+ }
244
+ function extractScopes(payload) {
245
+ // RFC 8693 / OAuth2: `scope` is a space-delimited string; some IdPs emit
246
+ // `scp` (Azure AD) or `scopes` (custom). Accept all three for ergonomics
247
+ // while staying strict on shape.
248
+ const scp = Array.isArray(payload.scp)
249
+ ? payload.scp.filter((scope) => typeof scope === "string").join(" ")
250
+ : "";
251
+ const scopes = Array.isArray(payload.scopes)
252
+ ? payload.scopes.filter((scope) => typeof scope === "string").join(" ")
253
+ : "";
254
+ const raw = (typeof payload.scope === "string" && payload.scope) || scp || scopes || "";
255
+ if (!raw)
256
+ return [];
257
+ const out = [];
258
+ const seen = new Set();
259
+ for (const piece of raw.split(/\s+/)) {
260
+ if (piece.length === 0)
261
+ continue;
262
+ if (seen.has(piece))
263
+ continue;
264
+ seen.add(piece);
265
+ out.push(piece);
266
+ }
267
+ return out;
268
+ }
269
+ //# sourceMappingURL=jwk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jwk.js","sourceRoot":"","sources":["../src/jwk.ts"],"names":[],"mappings":"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"}