dbsc-toolkit 2.11.0 → 2.13.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 +32 -9
- package/dist/core/dpop/bind.d.ts +13 -0
- package/dist/core/dpop/bind.d.ts.map +1 -0
- package/dist/core/dpop/bind.js +12 -0
- package/dist/core/dpop/bind.js.map +1 -0
- package/dist/core/dpop/guard.d.ts +55 -0
- package/dist/core/dpop/guard.d.ts.map +1 -0
- package/dist/core/dpop/guard.js +40 -0
- package/dist/core/dpop/guard.js.map +1 -0
- package/dist/core/dpop/htu.d.ts +4 -0
- package/dist/core/dpop/htu.d.ts.map +1 -0
- package/dist/core/dpop/htu.js +42 -0
- package/dist/core/dpop/htu.js.map +1 -0
- package/dist/core/dpop/index.d.ts +9 -0
- package/dist/core/dpop/index.d.ts.map +1 -0
- package/dist/core/dpop/index.js +6 -0
- package/dist/core/dpop/index.js.map +1 -0
- package/dist/core/dpop/thumbprint.d.ts +13 -0
- package/dist/core/dpop/thumbprint.d.ts.map +1 -0
- package/dist/core/dpop/thumbprint.js +30 -0
- package/dist/core/dpop/thumbprint.js.map +1 -0
- package/dist/core/dpop/verify.d.ts +44 -0
- package/dist/core/dpop/verify.d.ts.map +1 -0
- package/dist/core/dpop/verify.js +124 -0
- package/dist/core/dpop/verify.js.map +1 -0
- package/dist/core/errors.d.ts +13 -0
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +16 -0
- package/dist/core/errors.js.map +1 -1
- package/dist/dpop/index.d.ts +12 -0
- package/dist/dpop/index.d.ts.map +1 -0
- package/dist/dpop/index.js +11 -0
- package/dist/dpop/index.js.map +1 -0
- package/dist/electron/create-dbsc.d.ts +25 -0
- package/dist/electron/create-dbsc.d.ts.map +1 -0
- package/dist/electron/create-dbsc.js +22 -0
- package/dist/electron/create-dbsc.js.map +1 -0
- package/dist/electron/index.d.ts +30 -0
- package/dist/electron/index.d.ts.map +1 -0
- package/dist/electron/index.js +95 -0
- package/dist/electron/index.js.map +1 -0
- package/dist/electron/require-dpop.d.ts +2 -0
- package/dist/electron/require-dpop.d.ts.map +1 -0
- package/dist/electron/require-dpop.js +3 -0
- package/dist/electron/require-dpop.js.map +1 -0
- package/dist/electron/require-proof.d.ts +2 -0
- package/dist/electron/require-proof.d.ts.map +1 -0
- package/dist/electron/require-proof.js +4 -0
- package/dist/electron/require-proof.js.map +1 -0
- package/dist/express/create-dbsc.d.ts +4 -1
- package/dist/express/create-dbsc.d.ts.map +1 -1
- package/dist/express/create-dbsc.js +2 -0
- package/dist/express/create-dbsc.js.map +1 -1
- package/dist/express/index.d.ts +1 -0
- package/dist/express/index.d.ts.map +1 -1
- package/dist/express/index.js +1 -0
- package/dist/express/index.js.map +1 -1
- package/dist/express/require-dpop.d.ts +17 -0
- package/dist/express/require-dpop.d.ts.map +1 -0
- package/dist/express/require-dpop.js +51 -0
- package/dist/express/require-dpop.js.map +1 -0
- package/dist/fastify/create-dbsc.d.ts +4 -0
- package/dist/fastify/create-dbsc.d.ts.map +1 -1
- package/dist/fastify/create-dbsc.js +2 -0
- package/dist/fastify/create-dbsc.js.map +1 -1
- package/dist/fastify/index.d.ts +1 -0
- package/dist/fastify/index.d.ts.map +1 -1
- package/dist/fastify/index.js +1 -0
- package/dist/fastify/index.js.map +1 -1
- package/dist/fastify/require-dpop.d.ts +13 -0
- package/dist/fastify/require-dpop.d.ts.map +1 -0
- package/dist/fastify/require-dpop.js +42 -0
- package/dist/fastify/require-dpop.js.map +1 -0
- package/dist/hono/create-dbsc.d.ts +3 -0
- package/dist/hono/create-dbsc.d.ts.map +1 -1
- package/dist/hono/create-dbsc.js +2 -0
- package/dist/hono/create-dbsc.js.map +1 -1
- package/dist/hono/index.d.ts +1 -0
- package/dist/hono/index.d.ts.map +1 -1
- package/dist/hono/index.js +1 -0
- package/dist/hono/index.js.map +1 -1
- package/dist/hono/require-dpop.d.ts +14 -0
- package/dist/hono/require-dpop.d.ts.map +1 -0
- package/dist/hono/require-dpop.js +38 -0
- package/dist/hono/require-dpop.js.map +1 -0
- package/dist/koa/create-dbsc.d.ts +3 -0
- package/dist/koa/create-dbsc.d.ts.map +1 -1
- package/dist/koa/create-dbsc.js +2 -0
- package/dist/koa/create-dbsc.js.map +1 -1
- package/dist/koa/index.d.ts +1 -0
- package/dist/koa/index.d.ts.map +1 -1
- package/dist/koa/index.js +1 -0
- package/dist/koa/index.js.map +1 -1
- package/dist/koa/require-dpop.d.ts +14 -0
- package/dist/koa/require-dpop.d.ts.map +1 -0
- package/dist/koa/require-dpop.js +38 -0
- package/dist/koa/require-dpop.js.map +1 -0
- package/dist/nestjs/index.d.ts +13 -1
- package/dist/nestjs/index.d.ts.map +1 -1
- package/dist/nestjs/index.js +47 -1
- package/dist/nestjs/index.js.map +1 -1
- package/dist/nextjs/index.d.ts +2 -0
- package/dist/nextjs/index.d.ts.map +1 -1
- package/dist/nextjs/index.js +1 -0
- package/dist/nextjs/index.js.map +1 -1
- package/dist/nextjs/require-dpop.d.ts +21 -0
- package/dist/nextjs/require-dpop.d.ts.map +1 -0
- package/dist/nextjs/require-dpop.js +35 -0
- package/dist/nextjs/require-dpop.js.map +1 -0
- package/dist/node/create-dbsc.d.ts +4 -0
- package/dist/node/create-dbsc.d.ts.map +1 -1
- package/dist/node/create-dbsc.js +2 -0
- package/dist/node/create-dbsc.js.map +1 -1
- package/dist/node/index.d.ts +2 -0
- package/dist/node/index.d.ts.map +1 -1
- package/dist/node/index.js +1 -0
- package/dist/node/index.js.map +1 -1
- package/dist/node/require-dpop.d.ts +17 -0
- package/dist/node/require-dpop.d.ts.map +1 -0
- package/dist/node/require-dpop.js +49 -0
- package/dist/node/require-dpop.js.map +1 -0
- package/dist/sveltekit/index.d.ts +1 -0
- package/dist/sveltekit/index.d.ts.map +1 -1
- package/dist/sveltekit/index.js +1 -0
- package/dist/sveltekit/index.js.map +1 -1
- package/dist/sveltekit/require-dpop.d.ts +17 -0
- package/dist/sveltekit/require-dpop.d.ts.map +1 -0
- package/dist/sveltekit/require-dpop.js +48 -0
- package/dist/sveltekit/require-dpop.js.map +1 -0
- package/package.json +15 -1
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
8
|
<strong>Stop stolen session cookies from being replayed on another device.</strong><br>
|
|
9
|
-
Device Bound Session Credentials for Node.js — one framework-agnostic core with adapters for Express, Fastify, Hono, Next.js, NestJS, Koa, SvelteKit and raw <code>node:http</code>, plus a Web Crypto polyfill so every browser is covered, not just Chrome. A language-neutral protocol spec with a Node reference implementation.
|
|
9
|
+
Device Bound Session Credentials for Node.js — one framework-agnostic core with adapters for Express, Fastify, Hono, Next.js, NestJS, Koa, SvelteKit, Electron and raw <code>node:http</code>, plus a Web Crypto polyfill so every browser is covered, not just Chrome. A language-neutral protocol spec with a Node reference implementation.
|
|
10
10
|
</p>
|
|
11
11
|
|
|
12
12
|
<p align="center">
|
|
@@ -120,7 +120,7 @@ Load the polyfill in your HTML so non-Chromium browsers reach `tier: "bound"`:
|
|
|
120
120
|
</script>
|
|
121
121
|
```
|
|
122
122
|
|
|
123
|
-
Full walk-through, failure modes, and migration timeline: **[docs/
|
|
123
|
+
Full walk-through, failure modes, and migration timeline: **[docs/quickstart.md](./docs/quickstart.md)** and **[docs/guide.md](./docs/guide.md)**.
|
|
124
124
|
|
|
125
125
|
### Any framework
|
|
126
126
|
|
|
@@ -135,6 +135,7 @@ The protocol lives in a framework-agnostic core — plain functions plus a `Stor
|
|
|
135
135
|
| NestJS | `dbsc-toolkit/nestjs` |
|
|
136
136
|
| Koa | `dbsc-toolkit/koa` |
|
|
137
137
|
| SvelteKit | `dbsc-toolkit/sveltekit` |
|
|
138
|
+
| Electron (main process) | `dbsc-toolkit/electron` |
|
|
138
139
|
| Any other server (raw `node:http`) | `dbsc-toolkit/node` |
|
|
139
140
|
| [Better Auth](https://better-auth.com) | `@dbsc-toolkit/better-auth` |
|
|
140
141
|
|
|
@@ -161,6 +162,7 @@ The generic `dbsc-toolkit/node` adapter is exactly this over raw `node:http`. Co
|
|
|
161
162
|
| Works on Firefox / Safari / mobile / no-TPM | ✅ | ✅ | ❌ | ✅ (polyfill) |
|
|
162
163
|
| Per-request body-hash proof vs MITM | ❌ | ❌ | ❌ | ✅ |
|
|
163
164
|
| Captured-proof replay defense | n/a | ❌ | n/a | ✅ (replay cache) |
|
|
165
|
+
| Bearer/access-token replay (DPoP, RFC 9449) | n/a | ❌ | n/a | ✅ (`dbsc-toolkit/dpop`) |
|
|
164
166
|
| Multi-subdomain binding | loose | loose | ❌ | ✅ (`cookieScope: "site"`) |
|
|
165
167
|
| Better Auth integration | n/a | n/a | n/a | ✅ (plugin) |
|
|
166
168
|
|
|
@@ -196,11 +198,30 @@ Every session carries a `tier`. You don't gate on it directly — `requireProof(
|
|
|
196
198
|
|
|
197
199
|
The polyfill is on by default. To run native DBSC only (Chromium 145+, no `bound` tier), pass `bound: false` — the `/dbsc-bound/*` routes don't mount and `requireProof()` relaxes to the native binding.
|
|
198
200
|
|
|
201
|
+
## Optional: DPoP for bearer tokens (RFC 9449)
|
|
202
|
+
|
|
203
|
+
DBSC binds a session **cookie**. If you hand out **bearer / access tokens** for an API, those have the same replay problem a cookie does — copy the `Authorization` header, use it anywhere. DPoP closes that the same way: bind the token to a device key and require a per-request `DPoP` proof. It ships as a separate import, off the default path, so projects that don't use it pay nothing.
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
import { dpopConfirmation } from "dbsc-toolkit/dpop";
|
|
207
|
+
import { requireDpop } from "dbsc-toolkit/express";
|
|
208
|
+
|
|
209
|
+
// at issue time — bind the token to the device key:
|
|
210
|
+
const { jkt } = await dpopConfirmation(deviceJwk);
|
|
211
|
+
const token = signAccessToken({ sub, cnf: { jkt } }); // your JWT lib
|
|
212
|
+
|
|
213
|
+
// on the resource route — verify the proof and the binding:
|
|
214
|
+
app.get("/api/resource", requireDpop({ getBoundJkt }), handler);
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
`requireDpop` is exported from every adapter (Express, Fastify, Hono, Next.js, NestJS, Koa, SvelteKit, Electron, `node:http`). A failed check answers `401` with `WWW-Authenticate: DPoP`. Always pass `getBoundJkt` so the presented token is bound to its key — see [docs/dpop.md](./docs/dpop.md) and [spec/10-dpop.md](./spec/10-dpop.md).
|
|
218
|
+
|
|
199
219
|
## Ecosystem
|
|
200
220
|
|
|
201
221
|
| Package | What it is |
|
|
202
222
|
|---|---|
|
|
203
|
-
| `dbsc-toolkit` | Core + adapters for Express, Fastify, Hono, Next.js, NestJS, Koa, SvelteKit & raw `node:http`; memory/Redis/Postgres storage |
|
|
223
|
+
| `dbsc-toolkit` | Core + adapters for Express, Fastify, Hono, Next.js, NestJS, Koa, SvelteKit, Electron & raw `node:http`; memory/Redis/Postgres storage |
|
|
224
|
+
| `dbsc-toolkit/dpop` | Optional DPoP (RFC 9449) — bind bearer/access tokens to a device key |
|
|
204
225
|
| `dbsc-toolkit/client` | Browser SDK + Web Crypto polyfill |
|
|
205
226
|
| [`@dbsc-toolkit/better-auth`](./packages/better-auth/) | First-class [Better Auth](https://better-auth.com) plugin — binds every sign-in method automatically |
|
|
206
227
|
|
|
@@ -225,7 +246,8 @@ The wire protocol is documented as a language-neutral spec in [`spec/`](./spec/)
|
|
|
225
246
|
- [x] Per-request proof + body signing + replay cache
|
|
226
247
|
- [x] Multi-subdomain binding (`cookieScope: "site"`)
|
|
227
248
|
- [x] Better Auth plugin
|
|
228
|
-
- [x] NestJS, Koa, SvelteKit & generic `node:http` adapters
|
|
249
|
+
- [x] NestJS, Koa, SvelteKit, Electron & generic `node:http` adapters
|
|
250
|
+
- [x] DPoP (RFC 9449) for bearer-token binding
|
|
229
251
|
- [ ] Bun / Deno native paths
|
|
230
252
|
- [ ] Third-party security audit
|
|
231
253
|
|
|
@@ -234,7 +256,8 @@ The wire protocol is documented as a language-neutral spec in [`spec/`](./spec/)
|
|
|
234
256
|
| Import | What it is |
|
|
235
257
|
|---|---|
|
|
236
258
|
| `dbsc-toolkit` | Core: types, crypto, protocol handlers, framework-agnostic |
|
|
237
|
-
| `dbsc-toolkit/express` · `/fastify` · `/hono` · `/nextjs` · `/nestjs` · `/koa` · `/sveltekit` · `/node` | Framework adapters |
|
|
259
|
+
| `dbsc-toolkit/express` · `/fastify` · `/hono` · `/nextjs` · `/nestjs` · `/koa` · `/sveltekit` · `/electron` · `/node` | Framework adapters |
|
|
260
|
+
| `dbsc-toolkit/dpop` | Optional DPoP (RFC 9449) verifier + `requireDpop` per adapter |
|
|
238
261
|
| `dbsc-toolkit/client` | Browser SDK + polyfill |
|
|
239
262
|
| `dbsc-toolkit/storage/{memory,redis,postgres}` | Storage adapters |
|
|
240
263
|
|
|
@@ -242,10 +265,10 @@ The wire protocol is documented as a language-neutral spec in [`spec/`](./spec/)
|
|
|
242
265
|
|
|
243
266
|
- **Protocol spec (language-neutral):** [spec/](./spec/) · [conformance](./spec/09-conformance.md) · [test vectors](./spec/vectors)
|
|
244
267
|
- **Concepts & protocol:** [HOW-IT-WORKS.md](./HOW-IT-WORKS.md)
|
|
245
|
-
- **Getting started:** [docs/
|
|
246
|
-
- **Add to an existing app:** [docs/
|
|
247
|
-
- **Per-request signing & replay cache:** [docs/
|
|
248
|
-
- **Bound polyfill wire protocol:** [docs/
|
|
268
|
+
- **Getting started:** [docs/quickstart.md](./docs/quickstart.md)
|
|
269
|
+
- **Add to an existing app:** [docs/guide.md](./docs/guide.md)
|
|
270
|
+
- **Per-request signing & replay cache:** [docs/request-signing.md](./docs/request-signing.md)
|
|
271
|
+
- **Bound polyfill wire protocol:** [docs/polyfill.md](./docs/polyfill.md)
|
|
249
272
|
- **API reference:** [docs/api-reference.md](./docs/api-reference.md)
|
|
250
273
|
- **Adapters (+ build your own):** [docs/adapters.md](./docs/adapters.md)
|
|
251
274
|
- **Storage:** [docs/storage.md](./docs/storage.md) · **Deployment:** [docs/deployment.md](./docs/deployment.md)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface DpopConfirmation {
|
|
2
|
+
/** RFC 7638 thumbprint of the bound key, for the token's cnf.jkt. */
|
|
3
|
+
jkt: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Build the `cnf` confirmation to embed in an access token at issue time so
|
|
7
|
+
* the token is bound to a DPoP key (RFC 9449 §6). Embed the return value as
|
|
8
|
+
* `{ cnf: { jkt } }` in the token claims, then guard the resource route with
|
|
9
|
+
* `requireDpop({ getBoundJkt })` returning the same jkt. Framework-neutral:
|
|
10
|
+
* the caller signs the token however it already does.
|
|
11
|
+
*/
|
|
12
|
+
export declare function dpopConfirmation(jwk: JsonWebKey): Promise<DpopConfirmation>;
|
|
13
|
+
//# sourceMappingURL=bind.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bind.d.ts","sourceRoot":"","sources":["../../../src/core/dpop/bind.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,gBAAgB;IAC/B,qEAAqE;IACrE,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAEjF"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { jwkThumbprint } from "./thumbprint.js";
|
|
2
|
+
/**
|
|
3
|
+
* Build the `cnf` confirmation to embed in an access token at issue time so
|
|
4
|
+
* the token is bound to a DPoP key (RFC 9449 §6). Embed the return value as
|
|
5
|
+
* `{ cnf: { jkt } }` in the token claims, then guard the resource route with
|
|
6
|
+
* `requireDpop({ getBoundJkt })` returning the same jkt. Framework-neutral:
|
|
7
|
+
* the caller signs the token however it already does.
|
|
8
|
+
*/
|
|
9
|
+
export async function dpopConfirmation(jwk) {
|
|
10
|
+
return { jkt: await jwkThumbprint(jwk) };
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=bind.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bind.js","sourceRoot":"","sources":["../../../src/core/dpop/bind.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAOhD;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,GAAe;IACpD,OAAO,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC;AAC3C,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { ProofReplayCache } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Adapter-neutral options for a `requireDpop()` guard. `getBoundJkt` is generic
|
|
4
|
+
* over the framework request type so each adapter passes its own.
|
|
5
|
+
*/
|
|
6
|
+
export interface RequireDpopOptions<Req = unknown> {
|
|
7
|
+
/**
|
|
8
|
+
* Resolve the `cnf.jkt` the presented bearer token was issued against, from
|
|
9
|
+
* the request (e.g. decode the token, read `cnf.jkt`). Required for token
|
|
10
|
+
* binding — without it a presented token is rejected as
|
|
11
|
+
* DPOP_TOKEN_BINDING_REQUIRED unless `requireTokenBinding` is false.
|
|
12
|
+
*/
|
|
13
|
+
getBoundJkt?: (req: Req) => string | undefined | Promise<string | undefined>;
|
|
14
|
+
/**
|
|
15
|
+
* Default true. Set false to verify a presented token's proof without binding
|
|
16
|
+
* it to a jkt — strictly weaker proof-of-possession only. A conscious choice.
|
|
17
|
+
*/
|
|
18
|
+
requireTokenBinding?: boolean;
|
|
19
|
+
/** Acceptable iat window in ms. Default 300000. */
|
|
20
|
+
iatWindowMs?: number;
|
|
21
|
+
/** jti replay store. Falls back to the DBSC middleware's replayCache. */
|
|
22
|
+
replayCache?: ProofReplayCache;
|
|
23
|
+
}
|
|
24
|
+
export interface DpopGuardInput {
|
|
25
|
+
proof: string | undefined;
|
|
26
|
+
authorization: string | undefined;
|
|
27
|
+
method: string;
|
|
28
|
+
url: string;
|
|
29
|
+
boundJkt: string | undefined;
|
|
30
|
+
replayCache: ProofReplayCache | undefined;
|
|
31
|
+
opts: {
|
|
32
|
+
requireTokenBinding?: boolean;
|
|
33
|
+
iatWindowMs?: number;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export interface DpopGuardOutcome {
|
|
37
|
+
ok: boolean;
|
|
38
|
+
/** Set when ok=false: the HTTP error to emit. */
|
|
39
|
+
error?: {
|
|
40
|
+
code: string;
|
|
41
|
+
message: string;
|
|
42
|
+
};
|
|
43
|
+
/** Set when ok=true. */
|
|
44
|
+
jkt?: string;
|
|
45
|
+
}
|
|
46
|
+
/** Parse a `DPoP <token>` Authorization header. Returns the token or undefined. */
|
|
47
|
+
export declare function parseDpopAuthorization(authorization: string | undefined): string | undefined;
|
|
48
|
+
/**
|
|
49
|
+
* Adapter-neutral core of every `requireDpop()`. Each adapter extracts the
|
|
50
|
+
* fields, calls this, then writes its own 401 + WWW-Authenticate on failure.
|
|
51
|
+
*/
|
|
52
|
+
export declare function runDpopGuard(input: DpopGuardInput): Promise<DpopGuardOutcome>;
|
|
53
|
+
/** The WWW-Authenticate header value for a failed DPoP check (RFC 9449 §7.1). */
|
|
54
|
+
export declare const DPOP_WWW_AUTHENTICATE = "DPoP error=\"invalid_dpop_proof\"";
|
|
55
|
+
//# sourceMappingURL=guard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guard.d.ts","sourceRoot":"","sources":["../../../src/core/dpop/guard.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAIpD;;;GAGG;AACH,MAAM,WAAW,kBAAkB,CAAC,GAAG,GAAG,OAAO;IAC/C;;;;;OAKG;IACH,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAC7E;;;OAGG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,mDAAmD;IACnD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yEAAyE;IACzE,WAAW,CAAC,EAAE,gBAAgB,CAAC;CAChC;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,WAAW,EAAE,gBAAgB,GAAG,SAAS,CAAC;IAC1C,IAAI,EAAE;QAAE,mBAAmB,CAAC,EAAE,OAAO,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC/D;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,OAAO,CAAC;IACZ,iDAAiD;IACjD,KAAK,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1C,wBAAwB;IACxB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,mFAAmF;AACnF,wBAAgB,sBAAsB,CAAC,aAAa,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAI5F;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAsBnF;AAED,iFAAiF;AACjF,eAAO,MAAM,qBAAqB,sCAAoC,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { DbscVerificationError } from "../errors.js";
|
|
2
|
+
import { verifyDpopProof } from "./verify.js";
|
|
3
|
+
/** Parse a `DPoP <token>` Authorization header. Returns the token or undefined. */
|
|
4
|
+
export function parseDpopAuthorization(authorization) {
|
|
5
|
+
if (!authorization)
|
|
6
|
+
return undefined;
|
|
7
|
+
const m = /^DPoP\s+(.+)$/i.exec(authorization.trim());
|
|
8
|
+
return m ? m[1] : undefined;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Adapter-neutral core of every `requireDpop()`. Each adapter extracts the
|
|
12
|
+
* fields, calls this, then writes its own 401 + WWW-Authenticate on failure.
|
|
13
|
+
*/
|
|
14
|
+
export async function runDpopGuard(input) {
|
|
15
|
+
const accessToken = parseDpopAuthorization(input.authorization);
|
|
16
|
+
try {
|
|
17
|
+
const { jkt } = await verifyDpopProof({
|
|
18
|
+
proof: input.proof,
|
|
19
|
+
method: input.method,
|
|
20
|
+
url: input.url,
|
|
21
|
+
...(accessToken !== undefined && { accessToken }),
|
|
22
|
+
...(input.boundJkt !== undefined && { boundJkt: input.boundJkt }),
|
|
23
|
+
...(input.opts.requireTokenBinding !== undefined && {
|
|
24
|
+
requireTokenBinding: input.opts.requireTokenBinding,
|
|
25
|
+
}),
|
|
26
|
+
...(input.opts.iatWindowMs !== undefined && { iatWindowMs: input.opts.iatWindowMs }),
|
|
27
|
+
...(input.replayCache !== undefined && { replayCache: input.replayCache }),
|
|
28
|
+
});
|
|
29
|
+
return { ok: true, jkt };
|
|
30
|
+
}
|
|
31
|
+
catch (e) {
|
|
32
|
+
if (e instanceof DbscVerificationError) {
|
|
33
|
+
return { ok: false, error: { code: e.code, message: e.message } };
|
|
34
|
+
}
|
|
35
|
+
throw e;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/** The WWW-Authenticate header value for a failed DPoP check (RFC 9449 §7.1). */
|
|
39
|
+
export const DPOP_WWW_AUTHENTICATE = 'DPoP error="invalid_dpop_proof"';
|
|
40
|
+
//# sourceMappingURL=guard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guard.js","sourceRoot":"","sources":["../../../src/core/dpop/guard.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AA2C9C,mFAAmF;AACnF,MAAM,UAAU,sBAAsB,CAAC,aAAiC;IACtE,IAAI,CAAC,aAAa;QAAE,OAAO,SAAS,CAAC;IACrC,MAAM,CAAC,GAAG,gBAAgB,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC,CAAC;IACtD,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AAC9B,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,KAAqB;IACtD,MAAM,WAAW,GAAG,sBAAsB,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;IAChE,IAAI,CAAC;QACH,MAAM,EAAE,GAAG,EAAE,GAAG,MAAM,eAAe,CAAC;YACpC,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,GAAG,EAAE,KAAK,CAAC,GAAG;YACd,GAAG,CAAC,WAAW,KAAK,SAAS,IAAI,EAAE,WAAW,EAAE,CAAC;YACjD,GAAG,CAAC,KAAK,CAAC,QAAQ,KAAK,SAAS,IAAI,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,CAAC;YACjE,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,mBAAmB,KAAK,SAAS,IAAI;gBAClD,mBAAmB,EAAE,KAAK,CAAC,IAAI,CAAC,mBAAmB;aACpD,CAAC;YACF,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,WAAW,KAAK,SAAS,IAAI,EAAE,WAAW,EAAE,KAAK,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC;YACpF,GAAG,CAAC,KAAK,CAAC,WAAW,KAAK,SAAS,IAAI,EAAE,WAAW,EAAE,KAAK,CAAC,WAAW,EAAE,CAAC;SAC3E,CAAC,CAAC;QACH,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC;IAC3B,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,IAAI,CAAC,YAAY,qBAAqB,EAAE,CAAC;YACvC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC;QACpE,CAAC;QACD,MAAM,CAAC,CAAC;IACV,CAAC;AACH,CAAC;AAED,iFAAiF;AACjF,MAAM,CAAC,MAAM,qBAAqB,GAAG,iCAAiC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"htu.d.ts","sourceRoot":"","sources":["../../../src/core/dpop/htu.ts"],"names":[],"mappings":"AAsBA,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAoBhD;AAED,4EAA4E;AAC5E,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAEvE"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RFC 9449 htu normalization. This is the dangerous check: a weak normalizer
|
|
3
|
+
* lets a proof minted for one URL be accepted on another. §4.3 requires the
|
|
4
|
+
* htu claim to match the request URI "ignoring any query and fragment parts"
|
|
5
|
+
* and SHOULD apply RFC 3986 §6.2.2 (syntax-based) + §6.2.3 (scheme-based)
|
|
6
|
+
* normalization before comparing.
|
|
7
|
+
*
|
|
8
|
+
* Rules applied here, on both the claimed htu and the live request URL, then
|
|
9
|
+
* compared as strings:
|
|
10
|
+
* - lowercase scheme and host (WHATWG URL does this for us)
|
|
11
|
+
* - drop ONLY the scheme-default port (443 for https, 80 for http); keep
|
|
12
|
+
* every non-default port exactly (:8443 stays :8443)
|
|
13
|
+
* - strip query and fragment
|
|
14
|
+
* - empty path becomes "/"
|
|
15
|
+
* - trailing slash is SIGNIFICANT: "/token/" does not equal "/token"
|
|
16
|
+
*
|
|
17
|
+
* Percent-encoding and path "."/".." resolution are handled by `new URL()`.
|
|
18
|
+
*/
|
|
19
|
+
import { DbscVerificationError, ErrorCodes } from "../errors.js";
|
|
20
|
+
const DEFAULT_PORT = { "https:": "443", "http:": "80" };
|
|
21
|
+
export function normalizeHtu(uri) {
|
|
22
|
+
let u;
|
|
23
|
+
try {
|
|
24
|
+
u = new URL(uri);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
throw new DbscVerificationError(ErrorCodes.DPOP_HTU_MISMATCH, `htu is not an absolute URI: ${uri}`);
|
|
28
|
+
}
|
|
29
|
+
const scheme = u.protocol.toLowerCase();
|
|
30
|
+
const host = u.hostname.toLowerCase();
|
|
31
|
+
// u.port is "" when the URL used the scheme-default port implicitly, and the
|
|
32
|
+
// numeric string otherwise. Drop it only when it equals the scheme default.
|
|
33
|
+
const port = u.port && u.port !== DEFAULT_PORT[scheme] ? `:${u.port}` : "";
|
|
34
|
+
const path = u.pathname === "" ? "/" : u.pathname;
|
|
35
|
+
// scheme://host[:port]path — query and fragment intentionally dropped.
|
|
36
|
+
return `${scheme}//${host}${port}${path}`;
|
|
37
|
+
}
|
|
38
|
+
/** True when the claimed htu and the actual request URL normalize equal. */
|
|
39
|
+
export function htuMatches(claimed, requestUrl) {
|
|
40
|
+
return normalizeHtu(claimed) === normalizeHtu(requestUrl);
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=htu.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"htu.js","sourceRoot":"","sources":["../../../src/core/dpop/htu.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAE,qBAAqB,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAEjE,MAAM,YAAY,GAA2B,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAEhF,MAAM,UAAU,YAAY,CAAC,GAAW;IACtC,IAAI,CAAM,CAAC;IACX,IAAI,CAAC;QACH,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IACnB,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,qBAAqB,CAC7B,UAAU,CAAC,iBAAiB,EAC5B,+BAA+B,GAAG,EAAE,CACrC,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,CAAC,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC;IACxC,MAAM,IAAI,GAAG,CAAC,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC;IACtC,6EAA6E;IAC7E,4EAA4E;IAC5E,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,KAAK,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAC3E,MAAM,IAAI,GAAG,CAAC,CAAC,QAAQ,KAAK,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC;IAElD,uEAAuE;IACvE,OAAO,GAAG,MAAM,KAAK,IAAI,GAAG,IAAI,GAAG,IAAI,EAAE,CAAC;AAC5C,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,UAAU,CAAC,OAAe,EAAE,UAAkB;IAC5D,OAAO,YAAY,CAAC,OAAO,CAAC,KAAK,YAAY,CAAC,UAAU,CAAC,CAAC;AAC5D,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { verifyDpopProof } from "./verify.js";
|
|
2
|
+
export type { VerifyDpopProofRequest, DpopVerifyResult } from "./verify.js";
|
|
3
|
+
export { normalizeHtu, htuMatches } from "./htu.js";
|
|
4
|
+
export { jwkThumbprint, accessTokenHash } from "./thumbprint.js";
|
|
5
|
+
export { dpopConfirmation } from "./bind.js";
|
|
6
|
+
export type { DpopConfirmation } from "./bind.js";
|
|
7
|
+
export { runDpopGuard, parseDpopAuthorization, DPOP_WWW_AUTHENTICATE, } from "./guard.js";
|
|
8
|
+
export type { RequireDpopOptions, DpopGuardInput, DpopGuardOutcome, } from "./guard.js";
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/dpop/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,YAAY,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC5E,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,YAAY,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAClD,OAAO,EACL,YAAY,EACZ,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,kBAAkB,EAClB,cAAc,EACd,gBAAgB,GACjB,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { verifyDpopProof } from "./verify.js";
|
|
2
|
+
export { normalizeHtu, htuMatches } from "./htu.js";
|
|
3
|
+
export { jwkThumbprint, accessTokenHash } from "./thumbprint.js";
|
|
4
|
+
export { dpopConfirmation } from "./bind.js";
|
|
5
|
+
export { runDpopGuard, parseDpopAuthorization, DPOP_WWW_AUTHENTICATE, } from "./guard.js";
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/core/dpop/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAE9C,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAE7C,OAAO,EACL,YAAY,EACZ,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RFC 7638 JWK SHA-256 Thumbprint, base64url without padding. This is the
|
|
3
|
+
* `jkt` value: the access token is bound to the proof key by embedding this
|
|
4
|
+
* thumbprint in the token's `cnf.jkt`, and the resource server confirms the
|
|
5
|
+
* DPoP proof's `jwk` produces the same thumbprint (RFC 9449 §6).
|
|
6
|
+
*/
|
|
7
|
+
export declare function jwkThumbprint(jwk: JsonWebKey): Promise<string>;
|
|
8
|
+
/**
|
|
9
|
+
* The `ath` claim: base64url(SHA-256(ASCII(access token))) (RFC 9449 §4.2).
|
|
10
|
+
* Lets the server confirm the proof was minted for the exact token presented.
|
|
11
|
+
*/
|
|
12
|
+
export declare function accessTokenHash(accessToken: string): Promise<string>;
|
|
13
|
+
//# sourceMappingURL=thumbprint.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"thumbprint.d.ts","sourceRoot":"","sources":["../../../src/core/dpop/thumbprint.ts"],"names":[],"mappings":"AAEA;;;;;GAKG;AACH,wBAAsB,aAAa,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAEpE;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAI1E"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { calculateJwkThumbprint } from "jose";
|
|
2
|
+
/**
|
|
3
|
+
* RFC 7638 JWK SHA-256 Thumbprint, base64url without padding. This is the
|
|
4
|
+
* `jkt` value: the access token is bound to the proof key by embedding this
|
|
5
|
+
* thumbprint in the token's `cnf.jkt`, and the resource server confirms the
|
|
6
|
+
* DPoP proof's `jwk` produces the same thumbprint (RFC 9449 §6).
|
|
7
|
+
*/
|
|
8
|
+
export async function jwkThumbprint(jwk) {
|
|
9
|
+
return calculateJwkThumbprint(jwk, "sha256");
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The `ath` claim: base64url(SHA-256(ASCII(access token))) (RFC 9449 §4.2).
|
|
13
|
+
* Lets the server confirm the proof was minted for the exact token presented.
|
|
14
|
+
*/
|
|
15
|
+
export async function accessTokenHash(accessToken) {
|
|
16
|
+
const bytes = new TextEncoder().encode(accessToken);
|
|
17
|
+
const digest = await crypto.subtle.digest("SHA-256", bytes);
|
|
18
|
+
return base64UrlBytes(new Uint8Array(digest));
|
|
19
|
+
}
|
|
20
|
+
function base64UrlBytes(b) {
|
|
21
|
+
let s = "";
|
|
22
|
+
for (let i = 0; i < b.length; i++)
|
|
23
|
+
s += String.fromCharCode(b[i]);
|
|
24
|
+
return Buffer.from(s, "binary")
|
|
25
|
+
.toString("base64")
|
|
26
|
+
.replace(/=/g, "")
|
|
27
|
+
.replace(/\+/g, "-")
|
|
28
|
+
.replace(/\//g, "_");
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=thumbprint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"thumbprint.js","sourceRoot":"","sources":["../../../src/core/dpop/thumbprint.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAY,MAAM,MAAM,CAAC;AAExD;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,GAAe;IACjD,OAAO,sBAAsB,CAAC,GAAU,EAAE,QAAQ,CAAC,CAAC;AACtD,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,WAAmB;IACvD,MAAM,KAAK,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;IACpD,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;IAC5D,OAAO,cAAc,CAAC,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;AAChD,CAAC;AAED,SAAS,cAAc,CAAC,CAAa;IACnC,IAAI,CAAC,GAAG,EAAE,CAAC;IACX,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE;QAAE,CAAC,IAAI,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,CAAW,CAAC,CAAC;IAC5E,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,QAAQ,CAAC;SAC5B,QAAQ,CAAC,QAAQ,CAAC;SAClB,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;SACjB,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;SACnB,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;AACzB,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type JWTPayload } from "jose";
|
|
2
|
+
import type { ProofReplayCache } from "../types.js";
|
|
3
|
+
export interface VerifyDpopProofRequest {
|
|
4
|
+
/** The `DPoP` header value (the proof JWT). */
|
|
5
|
+
proof: string | undefined;
|
|
6
|
+
/** The actual HTTP method of the request being guarded. */
|
|
7
|
+
method: string;
|
|
8
|
+
/** The actual absolute request URL (scheme://host[:port]/path...). */
|
|
9
|
+
url: string;
|
|
10
|
+
/**
|
|
11
|
+
* The bearer access token presented with the request, when binding a token.
|
|
12
|
+
* Omit for pure proof-of-possession (no token).
|
|
13
|
+
*/
|
|
14
|
+
accessToken?: string | undefined;
|
|
15
|
+
/**
|
|
16
|
+
* The `cnf.jkt` the presented access token was issued against. Required
|
|
17
|
+
* whenever `accessToken` is set unless `requireTokenBinding` is explicitly
|
|
18
|
+
* false. When present, the proof key's thumbprint MUST equal it.
|
|
19
|
+
*/
|
|
20
|
+
boundJkt?: string | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* Default true. With a token presented but no `boundJkt`, throw
|
|
23
|
+
* DPOP_TOKEN_BINDING_REQUIRED rather than silently accepting an unbound
|
|
24
|
+
* proof. Pass false to opt into proof-of-possession-only on a presented
|
|
25
|
+
* token — strictly weaker, a conscious choice.
|
|
26
|
+
*/
|
|
27
|
+
requireTokenBinding?: boolean | undefined;
|
|
28
|
+
/** Acceptable `iat` window in ms (past and future). Default 300000. */
|
|
29
|
+
iatWindowMs?: number | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* jti replay store. Reuses the per-request proof cache: keyed on the proof
|
|
32
|
+
* `jti`. Default no-op (no replay defense). Pass Memory/Redis to enable.
|
|
33
|
+
*/
|
|
34
|
+
replayCache?: ProofReplayCache | undefined;
|
|
35
|
+
}
|
|
36
|
+
export interface DpopVerifyResult {
|
|
37
|
+
/** RFC 7638 thumbprint of the proof key — the value to bind a token to. */
|
|
38
|
+
jkt: string;
|
|
39
|
+
/** The proof's unique id, recorded for replay defense. */
|
|
40
|
+
jti: string;
|
|
41
|
+
payload: JWTPayload;
|
|
42
|
+
}
|
|
43
|
+
export declare function verifyDpopProof(req: VerifyDpopProofRequest): Promise<DpopVerifyResult>;
|
|
44
|
+
//# sourceMappingURL=verify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify.d.ts","sourceRoot":"","sources":["../../../src/core/dpop/verify.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,UAAU,EAChB,MAAM,MAAM,CAAC;AAGd,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAWpD,MAAM,WAAW,sBAAsB;IACrC,+CAA+C;IAC/C,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,2DAA2D;IAC3D,MAAM,EAAE,MAAM,CAAC;IACf,sEAAsE;IACtE,GAAG,EAAE,MAAM,CAAC;IACZ;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAC1C,uEAAuE;IACvE,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC;;;OAGG;IACH,WAAW,CAAC,EAAE,gBAAgB,GAAG,SAAS,CAAC;CAC5C;AAED,MAAM,WAAW,gBAAgB;IAC/B,2EAA2E;IAC3E,GAAG,EAAE,MAAM,CAAC;IACZ,0DAA0D;IAC1D,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,UAAU,CAAC;CACrB;AAED,wBAAsB,eAAe,CACnC,GAAG,EAAE,sBAAsB,GAC1B,OAAO,CAAC,gBAAgB,CAAC,CA6I3B"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { decodeProtectedHeader, importJWK, jwtVerify, } from "jose";
|
|
2
|
+
import { DbscVerificationError, ErrorCodes } from "../errors.js";
|
|
3
|
+
import { validateJwk } from "../crypto/jwk.js";
|
|
4
|
+
import { htuMatches } from "./htu.js";
|
|
5
|
+
import { accessTokenHash, jwkThumbprint } from "./thumbprint.js";
|
|
6
|
+
const DPOP_TYP = "dpop+jwt";
|
|
7
|
+
const SUPPORTED_ALGS = ["ES256", "RS256"];
|
|
8
|
+
const DEFAULT_IAT_WINDOW_MS = 5 * 60 * 1000;
|
|
9
|
+
// Private-key members that must never appear in a DPoP proof's public jwk
|
|
10
|
+
// (RFC 9449 §4.3: "the jwk JOSE Header Parameter does not contain a private key").
|
|
11
|
+
const PRIVATE_JWK_MEMBERS = ["d", "p", "q", "dp", "dq", "qi", "k", "oth"];
|
|
12
|
+
export async function verifyDpopProof(req) {
|
|
13
|
+
if (!req.proof) {
|
|
14
|
+
throw new DbscVerificationError(ErrorCodes.DPOP_PROOF_MISSING, "DPoP header missing");
|
|
15
|
+
}
|
|
16
|
+
// A DPoP header carrying more than one token is malformed (§4.3).
|
|
17
|
+
if (req.proof.includes(",") || /\s/.test(req.proof.trim())) {
|
|
18
|
+
throw new DbscVerificationError(ErrorCodes.DPOP_PROOF_MALFORMED, "DPoP header must be a single JWT");
|
|
19
|
+
}
|
|
20
|
+
let header;
|
|
21
|
+
try {
|
|
22
|
+
header = decodeProtectedHeader(req.proof);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
throw new DbscVerificationError(ErrorCodes.DPOP_PROOF_MALFORMED, "failed to decode DPoP header");
|
|
26
|
+
}
|
|
27
|
+
if (header["typ"] !== DPOP_TYP) {
|
|
28
|
+
throw new DbscVerificationError(ErrorCodes.DPOP_INVALID_TYP, `expected typ=${DPOP_TYP}, got ${String(header["typ"])}`);
|
|
29
|
+
}
|
|
30
|
+
const alg = header["alg"];
|
|
31
|
+
if (!SUPPORTED_ALGS.includes(alg)) {
|
|
32
|
+
throw new DbscVerificationError(ErrorCodes.DPOP_INVALID_ALG, `unsupported or disallowed alg: ${String(alg)}`);
|
|
33
|
+
}
|
|
34
|
+
const jwk = header["jwk"];
|
|
35
|
+
if (!jwk || typeof jwk !== "object") {
|
|
36
|
+
throw new DbscVerificationError(ErrorCodes.DPOP_PROOF_MALFORMED, "DPoP proof missing jwk header");
|
|
37
|
+
}
|
|
38
|
+
for (const m of PRIVATE_JWK_MEMBERS) {
|
|
39
|
+
if (m in jwk) {
|
|
40
|
+
throw new DbscVerificationError(ErrorCodes.DPOP_JWK_PRIVATE, "DPoP proof jwk contains private key material");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// Reuse the existing JWK validation (P-256 / RSA-2048 floor). Map its
|
|
44
|
+
// INVALID_JWK to the DPoP code so callers get a DPoP-shaped error.
|
|
45
|
+
try {
|
|
46
|
+
validateJwk(jwk);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
throw new DbscVerificationError(ErrorCodes.DPOP_PROOF_MALFORMED, "DPoP proof jwk failed validation");
|
|
50
|
+
}
|
|
51
|
+
let key;
|
|
52
|
+
try {
|
|
53
|
+
key = await importJWK(jwk, alg);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
throw new DbscVerificationError(ErrorCodes.DPOP_PROOF_MALFORMED, "failed to import DPoP proof jwk");
|
|
57
|
+
}
|
|
58
|
+
let payload;
|
|
59
|
+
try {
|
|
60
|
+
const result = await jwtVerify(req.proof, key, { algorithms: [...SUPPORTED_ALGS] });
|
|
61
|
+
payload = result.payload;
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
throw new DbscVerificationError(ErrorCodes.DPOP_SIGNATURE_INVALID, "DPoP proof signature did not verify");
|
|
65
|
+
}
|
|
66
|
+
const { jti, htm, htu, iat } = payload;
|
|
67
|
+
if (typeof jti !== "string" || !jti) {
|
|
68
|
+
throw new DbscVerificationError(ErrorCodes.DPOP_PROOF_MALFORMED, "DPoP proof missing jti");
|
|
69
|
+
}
|
|
70
|
+
if (typeof htm !== "string") {
|
|
71
|
+
throw new DbscVerificationError(ErrorCodes.DPOP_PROOF_MALFORMED, "DPoP proof missing htm");
|
|
72
|
+
}
|
|
73
|
+
if (typeof htu !== "string") {
|
|
74
|
+
throw new DbscVerificationError(ErrorCodes.DPOP_PROOF_MALFORMED, "DPoP proof missing htu");
|
|
75
|
+
}
|
|
76
|
+
if (typeof iat !== "number") {
|
|
77
|
+
throw new DbscVerificationError(ErrorCodes.DPOP_PROOF_MALFORMED, "DPoP proof missing iat");
|
|
78
|
+
}
|
|
79
|
+
if (htm.toUpperCase() !== req.method.toUpperCase()) {
|
|
80
|
+
throw new DbscVerificationError(ErrorCodes.DPOP_HTM_MISMATCH, "DPoP htm does not match request method");
|
|
81
|
+
}
|
|
82
|
+
if (!htuMatches(htu, req.url)) {
|
|
83
|
+
throw new DbscVerificationError(ErrorCodes.DPOP_HTU_MISMATCH, "DPoP htu does not match request URI");
|
|
84
|
+
}
|
|
85
|
+
// iat is a NumericDate (seconds). Window applies in both directions to allow
|
|
86
|
+
// for clock skew (§11.1).
|
|
87
|
+
const windowMs = req.iatWindowMs ?? DEFAULT_IAT_WINDOW_MS;
|
|
88
|
+
if (Math.abs(Date.now() - iat * 1000) > windowMs) {
|
|
89
|
+
throw new DbscVerificationError(ErrorCodes.DPOP_IAT_OUT_OF_WINDOW, "DPoP iat outside acceptable window");
|
|
90
|
+
}
|
|
91
|
+
const jkt = await jwkThumbprint(jwk);
|
|
92
|
+
// Token binding. Default-secure: a presented token without a boundJkt is
|
|
93
|
+
// rejected, never silently downgraded.
|
|
94
|
+
if (req.accessToken !== undefined) {
|
|
95
|
+
const requireBinding = req.requireTokenBinding ?? true;
|
|
96
|
+
if (req.boundJkt === undefined) {
|
|
97
|
+
if (requireBinding) {
|
|
98
|
+
throw new DbscVerificationError(ErrorCodes.DPOP_TOKEN_BINDING_REQUIRED, "access token presented without a bound jkt; pass getBoundJkt or set requireTokenBinding:false");
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
else if (jkt !== req.boundJkt) {
|
|
102
|
+
throw new DbscVerificationError(ErrorCodes.DPOP_JKT_MISMATCH, "DPoP proof key does not match token cnf.jkt");
|
|
103
|
+
}
|
|
104
|
+
const ath = payload.ath;
|
|
105
|
+
if (typeof ath !== "string") {
|
|
106
|
+
throw new DbscVerificationError(ErrorCodes.DPOP_ATH_MISMATCH, "DPoP proof missing ath for presented token");
|
|
107
|
+
}
|
|
108
|
+
const expected = await accessTokenHash(req.accessToken);
|
|
109
|
+
if (ath !== expected) {
|
|
110
|
+
throw new DbscVerificationError(ErrorCodes.DPOP_ATH_MISMATCH, "DPoP ath does not match presented token");
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// Replay defense, after the cryptographic gate. Recording an unverified jti
|
|
114
|
+
// would let an attacker poison the cache. TTL is 2*window so a proof at the
|
|
115
|
+
// edge of the future window survives until the past window closes.
|
|
116
|
+
if (req.replayCache) {
|
|
117
|
+
const fresh = await req.replayCache.checkAndRecord(`dpop:${jti}`, 2 * windowMs);
|
|
118
|
+
if (!fresh) {
|
|
119
|
+
throw new DbscVerificationError(ErrorCodes.DPOP_JTI_REPLAY, "DPoP proof jti already used (replay)");
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return { jkt, jti, payload };
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=verify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verify.js","sourceRoot":"","sources":["../../../src/core/dpop/verify.ts"],"names":[],"mappings":"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"}
|
package/dist/core/errors.d.ts
CHANGED
|
@@ -29,5 +29,18 @@ export declare const ErrorCodes: {
|
|
|
29
29
|
readonly MISSING_PROOF: "MISSING_PROOF";
|
|
30
30
|
readonly MALFORMED_PROOF: "MALFORMED_PROOF";
|
|
31
31
|
readonly PROOF_REPLAY: "PROOF_REPLAY";
|
|
32
|
+
readonly DPOP_PROOF_MISSING: "DPOP_PROOF_MISSING";
|
|
33
|
+
readonly DPOP_PROOF_MALFORMED: "DPOP_PROOF_MALFORMED";
|
|
34
|
+
readonly DPOP_INVALID_TYP: "DPOP_INVALID_TYP";
|
|
35
|
+
readonly DPOP_INVALID_ALG: "DPOP_INVALID_ALG";
|
|
36
|
+
readonly DPOP_JWK_PRIVATE: "DPOP_JWK_PRIVATE";
|
|
37
|
+
readonly DPOP_SIGNATURE_INVALID: "DPOP_SIGNATURE_INVALID";
|
|
38
|
+
readonly DPOP_HTM_MISMATCH: "DPOP_HTM_MISMATCH";
|
|
39
|
+
readonly DPOP_HTU_MISMATCH: "DPOP_HTU_MISMATCH";
|
|
40
|
+
readonly DPOP_IAT_OUT_OF_WINDOW: "DPOP_IAT_OUT_OF_WINDOW";
|
|
41
|
+
readonly DPOP_JTI_REPLAY: "DPOP_JTI_REPLAY";
|
|
42
|
+
readonly DPOP_ATH_MISMATCH: "DPOP_ATH_MISMATCH";
|
|
43
|
+
readonly DPOP_JKT_MISMATCH: "DPOP_JKT_MISMATCH";
|
|
44
|
+
readonly DPOP_TOKEN_BINDING_REQUIRED: "DPOP_TOKEN_BINDING_REQUIRED";
|
|
32
45
|
};
|
|
33
46
|
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"AAAA,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBAEV,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAK1C;AAED,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBAEV,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAK1C;AAED,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBAEV,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAK1C;AAED,eAAO,MAAM,UAAU
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"AAAA,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBAEV,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAK1C;AAED,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBAEV,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAK1C;AAED,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBAEV,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAK1C;AAED,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuCb,CAAC"}
|
package/dist/core/errors.js
CHANGED
|
@@ -45,5 +45,21 @@ export const ErrorCodes = {
|
|
|
45
45
|
MISSING_PROOF: "MISSING_PROOF",
|
|
46
46
|
MALFORMED_PROOF: "MALFORMED_PROOF",
|
|
47
47
|
PROOF_REPLAY: "PROOF_REPLAY",
|
|
48
|
+
// DPoP (RFC 9449). Optional layer, reached only via the dbsc-toolkit/dpop
|
|
49
|
+
// subpath. A failed DPoP check maps to HTTP 401 + WWW-Authenticate: DPoP
|
|
50
|
+
// (resource-server semantics) — not the 403 the DBSC per-request proof uses.
|
|
51
|
+
DPOP_PROOF_MISSING: "DPOP_PROOF_MISSING",
|
|
52
|
+
DPOP_PROOF_MALFORMED: "DPOP_PROOF_MALFORMED",
|
|
53
|
+
DPOP_INVALID_TYP: "DPOP_INVALID_TYP",
|
|
54
|
+
DPOP_INVALID_ALG: "DPOP_INVALID_ALG",
|
|
55
|
+
DPOP_JWK_PRIVATE: "DPOP_JWK_PRIVATE",
|
|
56
|
+
DPOP_SIGNATURE_INVALID: "DPOP_SIGNATURE_INVALID",
|
|
57
|
+
DPOP_HTM_MISMATCH: "DPOP_HTM_MISMATCH",
|
|
58
|
+
DPOP_HTU_MISMATCH: "DPOP_HTU_MISMATCH",
|
|
59
|
+
DPOP_IAT_OUT_OF_WINDOW: "DPOP_IAT_OUT_OF_WINDOW",
|
|
60
|
+
DPOP_JTI_REPLAY: "DPOP_JTI_REPLAY",
|
|
61
|
+
DPOP_ATH_MISMATCH: "DPOP_ATH_MISMATCH",
|
|
62
|
+
DPOP_JKT_MISMATCH: "DPOP_JKT_MISMATCH",
|
|
63
|
+
DPOP_TOKEN_BINDING_REQUIRED: "DPOP_TOKEN_BINDING_REQUIRED",
|
|
48
64
|
};
|
|
49
65
|
//# sourceMappingURL=errors.js.map
|
package/dist/core/errors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IACjC,IAAI,CAAS;IAEtB,YAAY,IAAY,EAAE,OAAe;QACvC,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;QAChC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF;AAED,MAAM,OAAO,qBAAsB,SAAQ,KAAK;IACrC,IAAI,CAAS;IAEtB,YAAY,IAAY,EAAE,OAAe;QACvC,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC;QACpC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF;AAED,MAAM,OAAO,gBAAiB,SAAQ,KAAK;IAChC,IAAI,CAAS;IAEtB,YAAY,IAAY,EAAE,OAAe;QACvC,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;QAC/B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF;AAED,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,uBAAuB,EAAE,yBAAyB;IAClD,aAAa,EAAE,eAAe;IAC9B,WAAW,EAAE,aAAa;IAC1B,iBAAiB,EAAE,mBAAmB;IACtC,mBAAmB,EAAE,qBAAqB;IAC1C,iBAAiB,EAAE,mBAAmB;IACtC,kBAAkB,EAAE,oBAAoB;IACxC,YAAY,EAAE,cAAc;IAC5B,iBAAiB,EAAE,mBAAmB;IACtC,aAAa,EAAE,eAAe;IAC9B,wEAAwE;IACxE,0EAA0E;IAC1E,wEAAwE;IACxE,gBAAgB;IAChB,oBAAoB,EAAE,sBAAsB;IAC5C,mBAAmB,EAAE,qBAAqB;IAC1C,iBAAiB,EAAE,mBAAmB;IACtC,0BAA0B,EAAE,4BAA4B;IACxD,YAAY,EAAE,cAAc;IAC5B,aAAa,EAAE,eAAe;IAC9B,eAAe,EAAE,iBAAiB;IAClC,YAAY,EAAE,cAAc;
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/core/errors.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IACjC,IAAI,CAAS;IAEtB,YAAY,IAAY,EAAE,OAAe;QACvC,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAC;QAChC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF;AAED,MAAM,OAAO,qBAAsB,SAAQ,KAAK;IACrC,IAAI,CAAS;IAEtB,YAAY,IAAY,EAAE,OAAe;QACvC,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC;QACpC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF;AAED,MAAM,OAAO,gBAAiB,SAAQ,KAAK;IAChC,IAAI,CAAS;IAEtB,YAAY,IAAY,EAAE,OAAe;QACvC,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;QAC/B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF;AAED,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,uBAAuB,EAAE,yBAAyB;IAClD,aAAa,EAAE,eAAe;IAC9B,WAAW,EAAE,aAAa;IAC1B,iBAAiB,EAAE,mBAAmB;IACtC,mBAAmB,EAAE,qBAAqB;IAC1C,iBAAiB,EAAE,mBAAmB;IACtC,kBAAkB,EAAE,oBAAoB;IACxC,YAAY,EAAE,cAAc;IAC5B,iBAAiB,EAAE,mBAAmB;IACtC,aAAa,EAAE,eAAe;IAC9B,wEAAwE;IACxE,0EAA0E;IAC1E,wEAAwE;IACxE,gBAAgB;IAChB,oBAAoB,EAAE,sBAAsB;IAC5C,mBAAmB,EAAE,qBAAqB;IAC1C,iBAAiB,EAAE,mBAAmB;IACtC,0BAA0B,EAAE,4BAA4B;IACxD,YAAY,EAAE,cAAc;IAC5B,aAAa,EAAE,eAAe;IAC9B,eAAe,EAAE,iBAAiB;IAClC,YAAY,EAAE,cAAc;IAC5B,0EAA0E;IAC1E,yEAAyE;IACzE,6EAA6E;IAC7E,kBAAkB,EAAE,oBAAoB;IACxC,oBAAoB,EAAE,sBAAsB;IAC5C,gBAAgB,EAAE,kBAAkB;IACpC,gBAAgB,EAAE,kBAAkB;IACpC,gBAAgB,EAAE,kBAAkB;IACpC,sBAAsB,EAAE,wBAAwB;IAChD,iBAAiB,EAAE,mBAAmB;IACtC,iBAAiB,EAAE,mBAAmB;IACtC,sBAAsB,EAAE,wBAAwB;IAChD,eAAe,EAAE,iBAAiB;IAClC,iBAAiB,EAAE,mBAAmB;IACtC,iBAAiB,EAAE,mBAAmB;IACtC,2BAA2B,EAAE,6BAA6B;CAClD,CAAC"}
|