@daloyjs/core 0.17.0 → 0.29.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 +3 -3
- package/dist/adapters/bun.d.ts.map +1 -1
- package/dist/adapters/bun.js +38 -7
- package/dist/adapters/bun.js.map +1 -1
- package/dist/adapters/node.d.ts.map +1 -1
- package/dist/adapters/node.js +29 -7
- package/dist/adapters/node.js.map +1 -1
- package/dist/app.d.ts +322 -3
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +715 -29
- package/dist/app.js.map +1 -1
- package/dist/cli.d.ts +4 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +218 -1
- package/dist/cli.js.map +1 -1
- package/dist/combine.d.ts +97 -0
- package/dist/combine.d.ts.map +1 -0
- package/dist/combine.js +247 -0
- package/dist/combine.js.map +1 -0
- package/dist/compression.d.ts +127 -0
- package/dist/compression.d.ts.map +1 -0
- package/dist/compression.js +368 -0
- package/dist/compression.js.map +1 -0
- package/dist/config.d.ts +97 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +132 -0
- package/dist/config.js.map +1 -0
- package/dist/conn-info.d.ts +121 -0
- package/dist/conn-info.d.ts.map +1 -0
- package/dist/conn-info.js +145 -0
- package/dist/conn-info.js.map +1 -0
- package/dist/cookie.d.ts +112 -0
- package/dist/cookie.d.ts.map +1 -0
- package/dist/cookie.js +185 -0
- package/dist/cookie.js.map +1 -0
- package/dist/dependency.d.ts +47 -0
- package/dist/dependency.d.ts.map +1 -0
- package/dist/dependency.js +68 -0
- package/dist/dependency.js.map +1 -0
- package/dist/etag.d.ts +48 -0
- package/dist/etag.d.ts.map +1 -0
- package/dist/etag.js +117 -0
- package/dist/etag.js.map +1 -0
- package/dist/index.d.ts +40 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -4
- package/dist/index.js.map +1 -1
- package/dist/ip-restriction.d.ts +75 -0
- package/dist/ip-restriction.d.ts.map +1 -0
- package/dist/ip-restriction.js +203 -0
- package/dist/ip-restriction.js.map +1 -0
- package/dist/jwk.d.ts +82 -0
- package/dist/jwk.d.ts.map +1 -0
- package/dist/jwk.js +269 -0
- package/dist/jwk.js.map +1 -0
- package/dist/jwt.d.ts +103 -0
- package/dist/jwt.d.ts.map +1 -0
- package/dist/jwt.js +437 -0
- package/dist/jwt.js.map +1 -0
- package/dist/load-shedding.d.ts +73 -0
- package/dist/load-shedding.d.ts.map +1 -0
- package/dist/load-shedding.js +171 -0
- package/dist/load-shedding.js.map +1 -0
- package/dist/middleware.d.ts +181 -2
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +367 -77
- package/dist/middleware.js.map +1 -1
- package/dist/multipart.d.ts +17 -0
- package/dist/multipart.d.ts.map +1 -1
- package/dist/multipart.js +117 -1
- package/dist/multipart.js.map +1 -1
- package/dist/openapi.d.ts +8 -0
- package/dist/openapi.d.ts.map +1 -1
- package/dist/openapi.js +18 -1
- package/dist/openapi.js.map +1 -1
- package/dist/security-schemes.d.ts +21 -5
- package/dist/security-schemes.d.ts.map +1 -1
- package/dist/security-schemes.js +32 -5
- package/dist/security-schemes.js.map +1 -1
- package/dist/session.d.ts +23 -1
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +106 -86
- package/dist/session.js.map +1 -1
- package/dist/subdomains.d.ts +97 -0
- package/dist/subdomains.d.ts.map +1 -0
- package/dist/subdomains.js +157 -0
- package/dist/subdomains.js.map +1 -0
- package/dist/time-claims.d.ts +72 -0
- package/dist/time-claims.d.ts.map +1 -0
- package/dist/time-claims.js +88 -0
- package/dist/time-claims.js.map +1 -0
- package/dist/types.d.ts +50 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/websocket.d.ts +51 -34
- package/dist/websocket.d.ts.map +1 -1
- package/dist/websocket.js +162 -2
- package/dist/websocket.js.map +1 -1
- package/package.json +31 -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
|
package/dist/jwk.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jwk.js","sourceRoot":"","sources":["../src/jwk.ts"],"names":[],"mappings":"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"}
|