@adcp/sdk 7.6.0 → 7.7.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/bin/adcp.js +3 -1
- package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
- package/dist/lib/server/test-controller.d.ts +46 -1
- package/dist/lib/server/test-controller.d.ts.map +1 -1
- package/dist/lib/server/test-controller.js +49 -2
- package/dist/lib/server/test-controller.js.map +1 -1
- package/dist/lib/signing/canonicalize.d.ts +53 -0
- package/dist/lib/signing/canonicalize.d.ts.map +1 -1
- package/dist/lib/signing/canonicalize.js +33 -1
- package/dist/lib/signing/canonicalize.js.map +1 -1
- package/dist/lib/signing/client.d.ts +6 -5
- package/dist/lib/signing/client.d.ts.map +1 -1
- package/dist/lib/signing/client.js +16 -1
- package/dist/lib/signing/client.js.map +1 -1
- package/dist/lib/signing/errors.d.ts +11 -0
- package/dist/lib/signing/errors.d.ts.map +1 -1
- package/dist/lib/signing/errors.js +11 -1
- package/dist/lib/signing/errors.js.map +1 -1
- package/dist/lib/signing/jwks-helpers.d.ts +4 -1
- package/dist/lib/signing/jwks-helpers.d.ts.map +1 -1
- package/dist/lib/signing/jwks-helpers.js.map +1 -1
- package/dist/lib/signing/provider.d.ts +17 -0
- package/dist/lib/signing/provider.d.ts.map +1 -1
- package/dist/lib/signing/replay.d.ts +16 -0
- package/dist/lib/signing/replay.d.ts.map +1 -1
- package/dist/lib/signing/replay.js.map +1 -1
- package/dist/lib/signing/request-context.d.ts +140 -0
- package/dist/lib/signing/request-context.d.ts.map +1 -0
- package/dist/lib/signing/request-context.js +160 -0
- package/dist/lib/signing/request-context.js.map +1 -0
- package/dist/lib/signing/response-verifier.d.ts +105 -0
- package/dist/lib/signing/response-verifier.d.ts.map +1 -0
- package/dist/lib/signing/response-verifier.js +271 -0
- package/dist/lib/signing/response-verifier.js.map +1 -0
- package/dist/lib/signing/server.d.ts +5 -3
- package/dist/lib/signing/server.d.ts.map +1 -1
- package/dist/lib/signing/server.js +13 -1
- package/dist/lib/signing/server.js.map +1 -1
- package/dist/lib/signing/signer-async.d.ts +8 -2
- package/dist/lib/signing/signer-async.d.ts.map +1 -1
- package/dist/lib/signing/signer-async.js +14 -0
- package/dist/lib/signing/signer-async.js.map +1 -1
- package/dist/lib/signing/signer.d.ts +149 -1
- package/dist/lib/signing/signer.d.ts.map +1 -1
- package/dist/lib/signing/signer.js +164 -0
- package/dist/lib/signing/signer.js.map +1 -1
- package/dist/lib/signing/testing.d.ts +10 -0
- package/dist/lib/signing/testing.d.ts.map +1 -1
- package/dist/lib/signing/testing.js +9 -0
- package/dist/lib/signing/testing.js.map +1 -1
- package/dist/lib/signing/types.d.ts +36 -0
- package/dist/lib/signing/types.d.ts.map +1 -1
- package/dist/lib/signing/types.js +37 -1
- package/dist/lib/signing/types.js.map +1 -1
- package/dist/lib/testing/comply-controller.d.ts +26 -1
- package/dist/lib/testing/comply-controller.d.ts.map +1 -1
- package/dist/lib/testing/comply-controller.js +17 -7
- package/dist/lib/testing/comply-controller.js.map +1 -1
- package/dist/lib/testing/index.d.ts +1 -1
- package/dist/lib/testing/index.d.ts.map +1 -1
- package/dist/lib/testing/index.js.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/builder.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/request-signing/builder.js +10 -1
- package/dist/lib/testing/storyboard/request-signing/builder.js.map +1 -1
- package/dist/lib/testing/storyboard/runner.d.ts +9 -1
- package/dist/lib/testing/storyboard/runner.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/runner.js +17 -1
- package/dist/lib/testing/storyboard/runner.js.map +1 -1
- package/dist/lib/testing/storyboard/types.d.ts +43 -1
- package/dist/lib/testing/storyboard/types.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/types.js.map +1 -1
- package/dist/lib/testing/storyboard/validations.d.ts.map +1 -1
- package/dist/lib/testing/storyboard/validations.js +182 -0
- package/dist/lib/testing/storyboard/validations.js.map +1 -1
- package/dist/lib/version.d.ts +3 -3
- package/dist/lib/version.js +3 -3
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.SigningProviderAlgorithmMismatchError = exports.WebhookSignatureError = exports.RequestSignatureError = void 0;
|
|
3
|
+
exports.SigningProviderAlgorithmMismatchError = exports.ResponseSignatureError = exports.WebhookSignatureError = exports.RequestSignatureError = void 0;
|
|
4
4
|
const errors_1 = require("../errors");
|
|
5
5
|
class RequestSignatureError extends errors_1.ADCPError {
|
|
6
6
|
code;
|
|
@@ -22,6 +22,16 @@ class WebhookSignatureError extends errors_1.ADCPError {
|
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
exports.WebhookSignatureError = WebhookSignatureError;
|
|
25
|
+
class ResponseSignatureError extends errors_1.ADCPError {
|
|
26
|
+
code;
|
|
27
|
+
failedStep;
|
|
28
|
+
constructor(code, failedStep, message, details) {
|
|
29
|
+
super(message, details);
|
|
30
|
+
this.code = code;
|
|
31
|
+
this.failedStep = failedStep;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
exports.ResponseSignatureError = ResponseSignatureError;
|
|
25
35
|
/**
|
|
26
36
|
* Adapter-side error thrown when a `SigningProvider`'s declared `algorithm`
|
|
27
37
|
* doesn't match the algorithm of the underlying key material.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../../src/lib/signing/errors.ts"],"names":[],"mappings":";;;AAAA,sCAAsC;AAoBtC,MAAa,qBAAsB,SAAQ,kBAAS;IACzC,IAAI,CAA4B;IAChC,UAAU,CAAS;IAE5B,YAAY,IAA+B,EAAE,UAAkB,EAAE,OAAe,EAAE,OAAiB;QACjG,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACxB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;IAC/B,CAAC;CACF;AATD,sDASC;AA2CD,MAAa,qBAAsB,SAAQ,kBAAS;IACzC,IAAI,CAA4B;IAChC,UAAU,CAAS;IAE5B,YAAY,IAA+B,EAAE,UAAkB,EAAE,OAAe,EAAE,OAAiB;QACjG,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACxB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;IAC/B,CAAC;CACF;AATD,sDASC;AAUD;;;;;;;;GAQG;AACH,MAAa,qCAAsC,SAAQ,kBAAS;IACzD,IAAI,GAA6B,qCAAqC,CAAC;IACvE,QAAQ,CAAS;IACjB,MAAM,CAAS;IACf,WAAW,CAAS;IAE7B,YAAY,QAAgB,EAAE,MAAc,EAAE,WAAmB;QAC/D,KAAK,CACH,uCAAuC,QAAQ,4BAA4B,MAAM,WAAW,WAAW,MAAM;YAC3G,wGAAwG,CAC3G,CAAC;QACF,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;IACjC,CAAC;CACF;AAfD,sFAeC"}
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../../src/lib/signing/errors.ts"],"names":[],"mappings":";;;AAAA,sCAAsC;AAoBtC,MAAa,qBAAsB,SAAQ,kBAAS;IACzC,IAAI,CAA4B;IAChC,UAAU,CAAS;IAE5B,YAAY,IAA+B,EAAE,UAAkB,EAAE,OAAe,EAAE,OAAiB;QACjG,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACxB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;IAC/B,CAAC;CACF;AATD,sDASC;AA2CD,MAAa,qBAAsB,SAAQ,kBAAS;IACzC,IAAI,CAA4B;IAChC,UAAU,CAAS;IAE5B,YAAY,IAA+B,EAAE,UAAkB,EAAE,OAAe,EAAE,OAAiB;QACjG,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACxB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;IAC/B,CAAC;CACF;AATD,sDASC;AAoCD,MAAa,sBAAuB,SAAQ,kBAAS;IAC1C,IAAI,CAA6B;IACjC,UAAU,CAAS;IAE5B,YAAY,IAAgC,EAAE,UAAkB,EAAE,OAAe,EAAE,OAAiB;QAClG,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACxB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;IAC/B,CAAC;CACF;AATD,wDASC;AAUD;;;;;;;;GAQG;AACH,MAAa,qCAAsC,SAAQ,kBAAS;IACzD,IAAI,GAA6B,qCAAqC,CAAC;IACvE,QAAQ,CAAS;IACjB,MAAM,CAAS;IACf,WAAW,CAAS;IAE7B,YAAY,QAAgB,EAAE,MAAc,EAAE,WAAmB;QAC/D,KAAK,CACH,uCAAuC,QAAQ,4BAA4B,MAAM,WAAW,WAAW,MAAM;YAC3G,wGAAwG,CAC3G,CAAC;QACF,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;IACjC,CAAC;CACF;AAfD,sFAeC"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AdcpJsonWebKey, AdcpSignAlg } from './types';
|
|
2
|
-
export type AdcpUse = 'request-signing' | 'webhook-signing';
|
|
2
|
+
export type AdcpUse = 'request-signing' | 'webhook-signing' | 'response-signing';
|
|
3
3
|
export interface PemToAdcpJwkOptions {
|
|
4
4
|
/** `kid` to embed in the JWK — must match the value published in `Signature-Input`. */
|
|
5
5
|
kid: string;
|
|
@@ -9,6 +9,9 @@ export interface PemToAdcpJwkOptions {
|
|
|
9
9
|
* Purpose binding, enforced by AdCP verifiers at step 8.
|
|
10
10
|
* - `'request-signing'` — for JWKs published at the buyer's `jwks_uri`.
|
|
11
11
|
* - `'webhook-signing'` — for JWKs used to sign outbound webhook callbacks.
|
|
12
|
+
* - `'response-signing'` — for JWKs used to sign outbound responses
|
|
13
|
+
* (RFC 9421 §2.2.9 response signing). Verifier surface is a follow-up;
|
|
14
|
+
* the value is reserved here so signer-side JWKs can declare it now.
|
|
12
15
|
*/
|
|
13
16
|
adcp_use: AdcpUse;
|
|
14
17
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jwks-helpers.d.ts","sourceRoot":"","sources":["../../../src/lib/signing/jwks-helpers.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAkB3D,MAAM,MAAM,OAAO,GAAG,iBAAiB,GAAG,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"jwks-helpers.d.ts","sourceRoot":"","sources":["../../../src/lib/signing/jwks-helpers.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAkB3D,MAAM,MAAM,OAAO,GAAG,iBAAiB,GAAG,iBAAiB,GAAG,kBAAkB,CAAC;AAEjF,MAAM,WAAW,mBAAmB;IAClC,uFAAuF;IACvF,GAAG,EAAE,MAAM,CAAC;IACZ,6EAA6E;IAC7E,SAAS,EAAE,WAAW,CAAC;IACvB;;;;;;;OAOG;IACH,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,mBAAmB,GAAG,cAAc,CA2CtF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jwks-helpers.js","sourceRoot":"","sources":["../../../src/lib/signing/jwks-helpers.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"jwks-helpers.js","sourceRoot":"","sources":["../../../src/lib/signing/jwks-helpers.ts"],"names":[],"mappings":";;AAqEA,oCA2CC;AAhHD,6CAA8C;AAG9C;;;;;;;;;;GAUG;AACH,MAAM,gBAAgB,GAAgC;IACpD,OAAO,EAAE,OAAO;IAChB,mBAAmB,EAAE,OAAO;CAC7B,CAAC;AAoBF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,SAAgB,YAAY,CAAC,GAAW,EAAE,OAA4B;IACpE,wEAAwE;IACxE,uEAAuE;IACvE,sEAAsE;IACtE,uEAAuE;IACvE,iBAAiB;IACjB,IAAI,kCAAkC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACjD,MAAM,IAAI,SAAS,CACjB,2CAA2C;YACzC,gEAAgE;YAChE,wDAAwD,CAC3D,CAAC;IACJ,CAAC;IAED,IAAI,MAAM,CAAC;IACX,IAAI,CAAC;QACH,MAAM,GAAG,IAAA,6BAAe,EAAC,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;IACxD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,SAAS,CACjB,sDAAsD;YACpD,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI;YACvD,4CAA4C,CAC/C,CAAC;IACJ,CAAC;IAED,MAAM,OAAO,GAAG,gBAAgB,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IACpD,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,SAAS,CACjB,wCAAwC,OAAO,CAAC,SAAS,KAAK;YAC5D,cAAc,MAAM,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAC5D,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,CAA4B,CAAC;IAE7E,OAAO;QACL,GAAG,QAAQ;QACX,GAAG,EAAE,OAAO,CAAC,GAAG;QAChB,GAAG,EAAE,OAAO;QACZ,GAAG,EAAE,KAAK;QACV,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,OAAO,EAAE,CAAC,QAAQ,CAAC;KACF,CAAC;AACtB,CAAC"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { AdcpUse } from './jwks-helpers';
|
|
1
2
|
import type { AdcpSignAlg } from './types';
|
|
2
3
|
/**
|
|
3
4
|
* Pluggable signer that the AdCP request- and webhook-signing paths route
|
|
@@ -51,6 +52,22 @@ export interface SigningProvider {
|
|
|
51
52
|
* Wire-format algorithm identifier. Same vocabulary as `ALLOWED_ALGS`.
|
|
52
53
|
*/
|
|
53
54
|
readonly algorithm: AdcpSignAlg;
|
|
55
|
+
/**
|
|
56
|
+
* Purpose binding for the underlying key, parallel to the sync-path
|
|
57
|
+
* `SignerKey.privateKey.adcp_use` gate. When set, the async helpers
|
|
58
|
+
* (`signRequestAsync`, `signWebhookAsync`, `signResponseAsync`) refuse
|
|
59
|
+
* keys whose `adcpUse` doesn't match the helper, with the same error
|
|
60
|
+
* codes the verifier raises at step 8.
|
|
61
|
+
*
|
|
62
|
+
* **Optional and backward-compatible.** Existing providers that omit
|
|
63
|
+
* `adcpUse` skip the gate (no breakage, but no defense-in-depth either).
|
|
64
|
+
* Adapter authors who care about catching IAM misconfig at the signer
|
|
65
|
+
* rather than the verifier should set this — KMS is exactly where one
|
|
66
|
+
* IAM mistake silently grants a single key cross-purpose access, and
|
|
67
|
+
* `request-signing` / `webhook-signing` / `response-signing` keys MUST
|
|
68
|
+
* stay distinct per AdCP step-8 purpose-binding.
|
|
69
|
+
*/
|
|
70
|
+
readonly adcpUse?: AdcpUse;
|
|
54
71
|
/**
|
|
55
72
|
* Stable opaque identifier disambiguating this signer from others
|
|
56
73
|
* advertising the same `kid`. Used as input to the SDK's transport- and
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../../src/lib/signing/provider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,IAAI,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAE/C;;;OAGG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAEvB;;OAEG;IACH,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC;IAEhC;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B"}
|
|
1
|
+
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../../src/lib/signing/provider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,IAAI,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAE/C;;;OAGG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAEvB;;OAEG;IACH,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC;IAEhC;;;;;;;;;;;;;;OAcG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAE3B;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B"}
|
|
@@ -14,6 +14,22 @@ export type ReplayInsertResult = 'ok' | 'replayed' | 'rate_abuse';
|
|
|
14
14
|
export interface ReplayStore {
|
|
15
15
|
has(keyid: string, scope: string, nonce: string, now: number): Promise<boolean>;
|
|
16
16
|
isCapHit(keyid: string, scope: string, now: number): Promise<boolean>;
|
|
17
|
+
/**
|
|
18
|
+
* Atomically check-and-insert. MUST return `'replayed'` if the nonce was
|
|
19
|
+
* already present for `(keyid, scope)`, otherwise insert and return `'ok'`
|
|
20
|
+
* (or `'rate_abuse'` if the cap is hit). The check and the insert MUST be
|
|
21
|
+
* a single uninterruptable operation — concurrent verifier calls racing
|
|
22
|
+
* the same nonce must see exactly one `'ok'` and the rest `'replayed'`.
|
|
23
|
+
*
|
|
24
|
+
* For in-memory stores Node's single-threaded event loop provides the
|
|
25
|
+
* atomicity for free. **Multi-replica adopters writing Redis / Postgres
|
|
26
|
+
* stores MUST implement this as a single atomic operation** (e.g. Redis
|
|
27
|
+
* `SET NX EX`, Postgres `INSERT ... ON CONFLICT DO NOTHING RETURNING`,
|
|
28
|
+
* a `WATCH/MULTI/EXEC` transaction). Implementing as `await has(...)`
|
|
29
|
+
* then `await write(...)` opens a TOCTOU window where two replicas both
|
|
30
|
+
* see "not replayed" and both insert — silently accepting the same
|
|
31
|
+
* signature twice within the window.
|
|
32
|
+
*/
|
|
17
33
|
insert(keyid: string, scope: string, nonce: string, ttlSeconds: number, now: number): Promise<ReplayInsertResult>;
|
|
18
34
|
}
|
|
19
35
|
export interface InMemoryReplayStoreOptions {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"replay.d.ts","sourceRoot":"","sources":["../../../src/lib/signing/replay.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,kBAAkB,GAAG,IAAI,GAAG,UAAU,GAAG,YAAY,CAAC;AAElE;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,WAAW;IAC1B,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAChF,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACtE,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;CACnH;AAED,MAAM,WAAW,0BAA0B;IACzC;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAoBD,qBAAa,mBAAoB,YAAW,WAAW;IACrD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA+B;IACrD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC;;;;;OAKG;IACH,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAqB;gBAEtC,OAAO,GAAE,0BAA+B;IAKpD;;;;;OAKG;IACH,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI;IAIlD,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAO/E,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IASrE,MAAM,CACV,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,kBAAkB,CAAC;IAY9B,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IAI3F,OAAO,CAAC,WAAW;IASnB,OAAO,CAAC,WAAW;IAanB,OAAO,CAAC,KAAK;CAcd"}
|
|
1
|
+
{"version":3,"file":"replay.d.ts","sourceRoot":"","sources":["../../../src/lib/signing/replay.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,kBAAkB,GAAG,IAAI,GAAG,UAAU,GAAG,YAAY,CAAC;AAElE;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,WAAW;IAC1B,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAChF,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACtE;;;;;;;;;;;;;;;OAeG;IACH,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;CACnH;AAED,MAAM,WAAW,0BAA0B;IACzC;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAoBD,qBAAa,mBAAoB,YAAW,WAAW;IACrD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA+B;IACrD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC;;;;;OAKG;IACH,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAqB;gBAEtC,OAAO,GAAE,0BAA+B;IAKpD;;;;;OAKG;IACH,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI;IAIlD,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAO/E,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IASrE,MAAM,CACV,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,UAAU,EAAE,MAAM,EAClB,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,kBAAkB,CAAC;IAY9B,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IAI3F,OAAO,CAAC,WAAW;IASnB,OAAO,CAAC,WAAW;IAanB,OAAO,CAAC,KAAK;CAcd"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"replay.js","sourceRoot":"","sources":["../../../src/lib/signing/replay.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"replay.js","sourceRoot":"","sources":["../../../src/lib/signing/replay.ts"],"names":[],"mappings":";;;AAgEA;;wCAEwC;AACxC,SAAS,YAAY,CAAC,KAAa,EAAE,KAAa;IAChD,OAAO,GAAG,KAAK,OAAO,KAAK,EAAE,CAAC;AAChC,CAAC;AAED,MAAa,mBAAmB;IACb,KAAK,GAAG,IAAI,GAAG,EAAoB,CAAC;IACpC,WAAW,CAAS;IACpB,UAAU,CAAS;IACpC;;;;;OAKG;IACc,YAAY,GAAG,IAAI,GAAG,EAAU,CAAC;IAElD,YAAY,UAAsC,EAAE;QAClD,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,kBAAkB,IAAI,OAAO,CAAC;QACzD,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,iBAAiB,IAAI,EAAE,CAAC;IACpD,CAAC;IAED;;;;;OAKG;IACH,mBAAmB,CAAC,KAAa,EAAE,KAAc;QAC/C,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,YAAY,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;IAClF,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,KAAa,EAAE,KAAa,EAAE,KAAa,EAAE,GAAW;QAChE,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,YAAY,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;QACzD,IAAI,CAAC,KAAK;YAAE,OAAO,KAAK,CAAC;QACzB,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QACvB,OAAO,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IACjC,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,KAAa,EAAE,KAAa,EAAE,GAAW;QACtD,IAAI,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QAC9C,IAAI,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,YAAY,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;YAAE,OAAO,IAAI,CAAC;QACnE,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,YAAY,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC;QACzD,IAAI,CAAC,KAAK;YAAE,OAAO,KAAK,CAAC;QACzB,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QACvB,OAAO,KAAK,CAAC,MAAM,CAAC,IAAI,IAAI,IAAI,CAAC,WAAW,CAAC;IAC/C,CAAC;IAED,KAAK,CAAC,MAAM,CACV,KAAa,EACb,KAAa,EACb,KAAa,EACb,UAAkB,EAClB,GAAW;QAEX,MAAM,SAAS,GAAG,YAAY,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QAC7C,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;QAC1C,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QACvB,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC;YAAE,OAAO,UAAU,CAAC;QAC/C,IAAI,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,IAAI,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;YAC9G,OAAO,YAAY,CAAC;QACtB,CAAC;QACD,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,GAAG,UAAU,CAAC,CAAC;QACjD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,CAAC,KAAa,EAAE,KAAa,EAAE,KAAa,EAAE,UAAkB,EAAE,GAAW;QAClF,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,WAAW,CAAC,YAAY,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,GAAG,UAAU,CAAC,CAAC;IAC1F,CAAC;IAEO,WAAW,CAAC,SAAiB;QACnC,IAAI,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACtC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,KAAK,GAAG,EAAE,MAAM,EAAE,IAAI,GAAG,EAAE,EAAE,OAAO,EAAE,IAAI,GAAG,EAAE,EAAE,gBAAgB,EAAE,MAAM,CAAC,iBAAiB,EAAE,CAAC;YAC9F,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;QACnC,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAEO,WAAW,CAAC,KAAe,EAAE,KAAa,EAAE,SAAiB;QACnE,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC;QACnD,IAAI,MAAM,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACnC,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,GAAG,EAAE,MAAM,EAAE,IAAI,GAAG,EAAE,EAAE,YAAY,EAAE,SAAS,EAAE,CAAC;YACxD,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;QAChC,CAAC;aAAM,IAAI,SAAS,GAAG,MAAM,CAAC,YAAY,EAAE,CAAC;YAC3C,MAAM,CAAC,YAAY,GAAG,SAAS,CAAC;QAClC,CAAC;QACD,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACzB,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAC1B,CAAC;IAEO,KAAK,CAAC,KAAe,EAAE,GAAW;QACxC,wEAAwE;QACxE,wEAAwE;QACxE,2DAA2D;QAC3D,MAAM,aAAa,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC;QACxD,IAAI,aAAa,KAAK,KAAK,CAAC,gBAAgB;YAAE,OAAO;QACrD,KAAK,CAAC,gBAAgB,GAAG,aAAa,CAAC;QACvC,KAAK,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;YACzC,IAAI,MAAM,CAAC,YAAY,IAAI,GAAG,EAAE,CAAC;gBAC/B,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM;oBAAE,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;gBAC9D,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YAC3B,CAAC;QACH,CAAC;IACH,CAAC;CACF;AArGD,kDAqGC"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapter helpers that construct the `{ method, url }` shape that
|
|
3
|
+
* `ResponseLike.request` (and any other caller binding RFC 9421 derived
|
|
4
|
+
* components back to an originating request) requires.
|
|
5
|
+
*
|
|
6
|
+
* Why this exists: `ResponseLike.request.url` MUST be an absolute URL —
|
|
7
|
+
* `canonicalAuthority` / `canonicalTargetUri` parse via `new URL(...)` and
|
|
8
|
+
* throw on a relative path. Express handlers ship `req.url` / `req.originalUrl`
|
|
9
|
+
* as path-only, so adopters reconstruct via
|
|
10
|
+
* `${req.protocol}://${req.get('host')}${req.originalUrl}`. But `req.protocol`
|
|
11
|
+
* lies behind a TLS-terminating proxy unless `trust proxy` is set, and
|
|
12
|
+
* `req.get('host')` is attacker-controllable absent a Host allowlist. A
|
|
13
|
+
* hostile peer that controls these headers can rebind a signature to
|
|
14
|
+
* `attacker.example.com` while the operator believes they're signing for
|
|
15
|
+
* `seller.example.com`.
|
|
16
|
+
*
|
|
17
|
+
* The library can warn (JSDoc on `ResponseLike.request`) but can't enforce.
|
|
18
|
+
* These helpers make the safe path the default path — pass them an inbound
|
|
19
|
+
* request handle from your platform and they emit a hardened
|
|
20
|
+
* `{ method, url }` shape.
|
|
21
|
+
*/
|
|
22
|
+
/**
|
|
23
|
+
* Minimal Express request shape this helper consumes. Kept narrow so
|
|
24
|
+
* `@adcp/sdk` doesn't pull in `@types/express`; importing Express's own
|
|
25
|
+
* `Request` type is safe in adopter code, since structural typing accepts
|
|
26
|
+
* the wider shape anywhere this narrower one is expected.
|
|
27
|
+
*/
|
|
28
|
+
export interface ExpressRequestLike {
|
|
29
|
+
readonly method: string;
|
|
30
|
+
readonly protocol: string;
|
|
31
|
+
readonly originalUrl: string;
|
|
32
|
+
/** Express's typed `Request.get('host')` overload. */
|
|
33
|
+
get(name: 'host'): string | undefined;
|
|
34
|
+
}
|
|
35
|
+
export interface RequestContextFromExpressOptions {
|
|
36
|
+
/**
|
|
37
|
+
* Allowed `Host` header values. When set, the helper throws if the
|
|
38
|
+
* inbound `Host` doesn't match. Strongly recommended in production —
|
|
39
|
+
* absent this, a hostile peer controlling the `Host` header can rebind
|
|
40
|
+
* the signature origin (see file header).
|
|
41
|
+
*
|
|
42
|
+
* Each entry is compared verbatim against `req.get('host')` after
|
|
43
|
+
* trimming and lowercasing. Include port suffixes where applicable
|
|
44
|
+
* (`api.example.com:8080`).
|
|
45
|
+
*/
|
|
46
|
+
hostAllowlist?: ReadonlyArray<string>;
|
|
47
|
+
/**
|
|
48
|
+
* When `true` (default), the helper throws if the reconstructed URL
|
|
49
|
+
* scheme is not `https`. AdCP response / webhook signatures bound to
|
|
50
|
+
* `http://` will fail strict-HTTPS verifier profiles, so this catches
|
|
51
|
+
* the misconfig at construction time. Disable only for local dev /
|
|
52
|
+
* loopback mock servers.
|
|
53
|
+
*/
|
|
54
|
+
forceHttps?: boolean;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Construct a hardened `{ method, url }` from an Express request.
|
|
58
|
+
*
|
|
59
|
+
* **You MUST configure `app.set('trust proxy', ...)`** with the IPs of
|
|
60
|
+
* your trusted reverse proxies before relying on this helper. Without
|
|
61
|
+
* `trust proxy`, Express returns the literal `req.connection.remoteAddress`
|
|
62
|
+
* for `req.protocol` and `req.ip` — which a TLS-terminating proxy will
|
|
63
|
+
* always set to the proxy's address, leaving you blind to whether the
|
|
64
|
+
* original request actually arrived over HTTPS. This helper trusts what
|
|
65
|
+
* Express tells it; the operator is responsible for telling Express what
|
|
66
|
+
* to trust.
|
|
67
|
+
*
|
|
68
|
+
* Throws when the `Host` header is missing, doesn't match `hostAllowlist`,
|
|
69
|
+
* or when the scheme is not `https` (unless `forceHttps: false`).
|
|
70
|
+
*/
|
|
71
|
+
export declare function requestContextFromExpress(req: ExpressRequestLike, options?: RequestContextFromExpressOptions): {
|
|
72
|
+
method: string;
|
|
73
|
+
url: string;
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Fetch / Workers / Deno / Bun `Request` shape. `request.url` is already
|
|
77
|
+
* an absolute URL per WHATWG spec, so there's no host-spoofing surface
|
|
78
|
+
* here — the runtime owns URL construction, not the caller.
|
|
79
|
+
*/
|
|
80
|
+
export interface FetchRequestLike {
|
|
81
|
+
readonly method: string;
|
|
82
|
+
readonly url: string;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Construct `{ method, url }` from a Fetch / WHATWG `Request`. Trivial
|
|
86
|
+
* passthrough; included for API symmetry with the Express / Lambda
|
|
87
|
+
* helpers. No proxy hardening needed — `request.url` is the absolute URL
|
|
88
|
+
* the runtime constructed from the wire, and Workers / Deno / Bun all
|
|
89
|
+
* terminate TLS at the edge so `https` is honest.
|
|
90
|
+
*/
|
|
91
|
+
export declare function requestContextFromFetch(request: FetchRequestLike): {
|
|
92
|
+
method: string;
|
|
93
|
+
url: string;
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* AWS Lambda API Gateway v2 / ALB event shape. Narrow rather than
|
|
97
|
+
* importing `@types/aws-lambda` — adopter code that ships against the
|
|
98
|
+
* full Lambda types can pass the wider object verbatim.
|
|
99
|
+
*/
|
|
100
|
+
export interface LambdaRequestEvent {
|
|
101
|
+
readonly requestContext: {
|
|
102
|
+
readonly domainName?: string;
|
|
103
|
+
readonly http?: {
|
|
104
|
+
readonly method?: string;
|
|
105
|
+
};
|
|
106
|
+
readonly httpMethod?: string;
|
|
107
|
+
};
|
|
108
|
+
readonly rawPath?: string;
|
|
109
|
+
readonly rawQueryString?: string;
|
|
110
|
+
readonly path?: string;
|
|
111
|
+
readonly httpMethod?: string;
|
|
112
|
+
readonly queryStringParameters?: {
|
|
113
|
+
readonly [key: string]: string | undefined;
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
export interface RequestContextFromLambdaOptions {
|
|
117
|
+
/**
|
|
118
|
+
* Same semantics as the Express helper — allowed `domainName` values.
|
|
119
|
+
* Lambda's `event.requestContext.domainName` is set by API Gateway from
|
|
120
|
+
* the matched custom-domain configuration; it's not attacker-controllable
|
|
121
|
+
* the way Express's `req.get('host')` is, but pinning it still catches
|
|
122
|
+
* misrouted traffic (e.g. one tenant's Lambda receiving another tenant's
|
|
123
|
+
* domain via a misconfigured shared API Gateway).
|
|
124
|
+
*/
|
|
125
|
+
hostAllowlist?: ReadonlyArray<string>;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Construct `{ method, url }` from an AWS Lambda API Gateway v2 / ALB
|
|
129
|
+
* event. Reads `requestContext.domainName` for the authority, `rawPath` +
|
|
130
|
+
* `rawQueryString` for the target (v2 / ALB), falling back to `path` +
|
|
131
|
+
* `queryStringParameters` reconstruction (v1).
|
|
132
|
+
*
|
|
133
|
+
* Always emits `https://` — Lambda is not addressable over plain HTTP
|
|
134
|
+
* through API Gateway / ALB in any documented configuration.
|
|
135
|
+
*/
|
|
136
|
+
export declare function requestContextFromLambda(event: LambdaRequestEvent, options?: RequestContextFromLambdaOptions): {
|
|
137
|
+
method: string;
|
|
138
|
+
url: string;
|
|
139
|
+
};
|
|
140
|
+
//# sourceMappingURL=request-context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-context.d.ts","sourceRoot":"","sources":["../../../src/lib/signing/request-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,sDAAsD;IACtD,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;CACvC;AAED,MAAM,WAAW,gCAAgC;IAC/C;;;;;;;;;OASG;IACH,aAAa,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAEtC;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,yBAAyB,CACvC,GAAG,EAAE,kBAAkB,EACvB,OAAO,GAAE,gCAAqC,GAC7C;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAgCjC;AAED;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,gBAAgB,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAoBlG;AAED;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,cAAc,EAAE;QACvB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAC7B,QAAQ,CAAC,IAAI,CAAC,EAAE;YAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;QAC7C,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;KAC9B,CAAC;IACF,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,qBAAqB,CAAC,EAAE;QAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAAA;KAAE,CAAC;CACjF;AAED,MAAM,WAAW,+BAA+B;IAC9C;;;;;;;OAOG;IACH,aAAa,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CACvC;AAED;;;;;;;;GAQG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,EAAE,kBAAkB,EACzB,OAAO,GAAE,+BAAoC,GAC5C;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAoCjC"}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Adapter helpers that construct the `{ method, url }` shape that
|
|
4
|
+
* `ResponseLike.request` (and any other caller binding RFC 9421 derived
|
|
5
|
+
* components back to an originating request) requires.
|
|
6
|
+
*
|
|
7
|
+
* Why this exists: `ResponseLike.request.url` MUST be an absolute URL —
|
|
8
|
+
* `canonicalAuthority` / `canonicalTargetUri` parse via `new URL(...)` and
|
|
9
|
+
* throw on a relative path. Express handlers ship `req.url` / `req.originalUrl`
|
|
10
|
+
* as path-only, so adopters reconstruct via
|
|
11
|
+
* `${req.protocol}://${req.get('host')}${req.originalUrl}`. But `req.protocol`
|
|
12
|
+
* lies behind a TLS-terminating proxy unless `trust proxy` is set, and
|
|
13
|
+
* `req.get('host')` is attacker-controllable absent a Host allowlist. A
|
|
14
|
+
* hostile peer that controls these headers can rebind a signature to
|
|
15
|
+
* `attacker.example.com` while the operator believes they're signing for
|
|
16
|
+
* `seller.example.com`.
|
|
17
|
+
*
|
|
18
|
+
* The library can warn (JSDoc on `ResponseLike.request`) but can't enforce.
|
|
19
|
+
* These helpers make the safe path the default path — pass them an inbound
|
|
20
|
+
* request handle from your platform and they emit a hardened
|
|
21
|
+
* `{ method, url }` shape.
|
|
22
|
+
*/
|
|
23
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
exports.requestContextFromExpress = requestContextFromExpress;
|
|
25
|
+
exports.requestContextFromFetch = requestContextFromFetch;
|
|
26
|
+
exports.requestContextFromLambda = requestContextFromLambda;
|
|
27
|
+
/**
|
|
28
|
+
* Construct a hardened `{ method, url }` from an Express request.
|
|
29
|
+
*
|
|
30
|
+
* **You MUST configure `app.set('trust proxy', ...)`** with the IPs of
|
|
31
|
+
* your trusted reverse proxies before relying on this helper. Without
|
|
32
|
+
* `trust proxy`, Express returns the literal `req.connection.remoteAddress`
|
|
33
|
+
* for `req.protocol` and `req.ip` — which a TLS-terminating proxy will
|
|
34
|
+
* always set to the proxy's address, leaving you blind to whether the
|
|
35
|
+
* original request actually arrived over HTTPS. This helper trusts what
|
|
36
|
+
* Express tells it; the operator is responsible for telling Express what
|
|
37
|
+
* to trust.
|
|
38
|
+
*
|
|
39
|
+
* Throws when the `Host` header is missing, doesn't match `hostAllowlist`,
|
|
40
|
+
* or when the scheme is not `https` (unless `forceHttps: false`).
|
|
41
|
+
*/
|
|
42
|
+
function requestContextFromExpress(req, options = {}) {
|
|
43
|
+
const host = normalizeHost(req.get('host'));
|
|
44
|
+
if (!host) {
|
|
45
|
+
throw new TypeError('requestContextFromExpress: Host header is missing. ' +
|
|
46
|
+
'Express returned no `host` — either the inbound request omitted it, or your reverse proxy stripped it.');
|
|
47
|
+
}
|
|
48
|
+
if (options.hostAllowlist && !hostMatchesAllowlist(host, options.hostAllowlist)) {
|
|
49
|
+
throw new TypeError(`requestContextFromExpress: Host "${host}" is not in hostAllowlist. ` +
|
|
50
|
+
`A hostile peer may be trying to rebind your signature origin via Host-header injection. ` +
|
|
51
|
+
`Add the legitimate value to hostAllowlist, or fix your proxy's Host handling.`);
|
|
52
|
+
}
|
|
53
|
+
const protocol = req.protocol;
|
|
54
|
+
const forceHttps = options.forceHttps !== false;
|
|
55
|
+
if (forceHttps && protocol !== 'https') {
|
|
56
|
+
throw new TypeError(`requestContextFromExpress: protocol is "${protocol}", not "https". ` +
|
|
57
|
+
`Set app.set('trust proxy', ...) so Express sees the X-Forwarded-Proto header, ` +
|
|
58
|
+
`or pass forceHttps: false for local dev (loopback / mock-server testing).`);
|
|
59
|
+
}
|
|
60
|
+
// Defense-in-depth: reject userinfo in originalUrl. A malicious middleware
|
|
61
|
+
// could mutate req.originalUrl to inject `user@` between host and path —
|
|
62
|
+
// canonicalAuthority would then sign the userinfo-bearing variant. Express
|
|
63
|
+
// doesn't normally carry userinfo here but we check rather than trust.
|
|
64
|
+
if (/[@]/.test(req.originalUrl.split('?')[0] ?? '')) {
|
|
65
|
+
throw new TypeError('requestContextFromExpress: originalUrl path must not embed userinfo (@).');
|
|
66
|
+
}
|
|
67
|
+
return { method: req.method, url: `${protocol}://${host}${req.originalUrl}` };
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Construct `{ method, url }` from a Fetch / WHATWG `Request`. Trivial
|
|
71
|
+
* passthrough; included for API symmetry with the Express / Lambda
|
|
72
|
+
* helpers. No proxy hardening needed — `request.url` is the absolute URL
|
|
73
|
+
* the runtime constructed from the wire, and Workers / Deno / Bun all
|
|
74
|
+
* terminate TLS at the edge so `https` is honest.
|
|
75
|
+
*/
|
|
76
|
+
function requestContextFromFetch(request) {
|
|
77
|
+
// WHATWG accepts `https://user:pw@host/path` as a valid Request URL and
|
|
78
|
+
// echoes it back verbatim. `canonicalAuthority` would then sign the
|
|
79
|
+
// userinfo-bearing form, splitting the signature namespace between
|
|
80
|
+
// userinfo and userinfo-stripped variants of the same logical origin.
|
|
81
|
+
// Reject at construction time — userinfo never belongs in a signed
|
|
82
|
+
// `@authority` / `@target-uri`.
|
|
83
|
+
let parsed;
|
|
84
|
+
try {
|
|
85
|
+
parsed = new URL(request.url);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
throw new TypeError(`requestContextFromFetch: request.url "${request.url}" is not a parseable URL.`);
|
|
89
|
+
}
|
|
90
|
+
if (parsed.username || parsed.password) {
|
|
91
|
+
throw new TypeError('requestContextFromFetch: request.url must not embed userinfo. ' +
|
|
92
|
+
'A signed @authority component with userinfo creates a verifier-splitting confusion vector.');
|
|
93
|
+
}
|
|
94
|
+
return { method: request.method, url: request.url };
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Construct `{ method, url }` from an AWS Lambda API Gateway v2 / ALB
|
|
98
|
+
* event. Reads `requestContext.domainName` for the authority, `rawPath` +
|
|
99
|
+
* `rawQueryString` for the target (v2 / ALB), falling back to `path` +
|
|
100
|
+
* `queryStringParameters` reconstruction (v1).
|
|
101
|
+
*
|
|
102
|
+
* Always emits `https://` — Lambda is not addressable over plain HTTP
|
|
103
|
+
* through API Gateway / ALB in any documented configuration.
|
|
104
|
+
*/
|
|
105
|
+
function requestContextFromLambda(event, options = {}) {
|
|
106
|
+
const domainName = event.requestContext.domainName;
|
|
107
|
+
if (!domainName) {
|
|
108
|
+
throw new TypeError('requestContextFromLambda: event.requestContext.domainName is missing. ' +
|
|
109
|
+
'API Gateway v2 / ALB events should always carry this; ' +
|
|
110
|
+
'is this event shaped like something else?');
|
|
111
|
+
}
|
|
112
|
+
const host = normalizeHost(domainName);
|
|
113
|
+
if (!host) {
|
|
114
|
+
throw new TypeError('requestContextFromLambda: domainName is empty after normalization.');
|
|
115
|
+
}
|
|
116
|
+
if (options.hostAllowlist && !hostMatchesAllowlist(host, options.hostAllowlist)) {
|
|
117
|
+
throw new TypeError(`requestContextFromLambda: domainName "${host}" is not in hostAllowlist. ` +
|
|
118
|
+
`Multi-tenant API Gateway misroute? Add the legitimate value, or fix the API mapping.`);
|
|
119
|
+
}
|
|
120
|
+
const method = event.requestContext.http?.method ?? event.requestContext.httpMethod ?? event.httpMethod;
|
|
121
|
+
if (!method) {
|
|
122
|
+
throw new TypeError('requestContextFromLambda: HTTP method is missing from the event.');
|
|
123
|
+
}
|
|
124
|
+
const path = event.rawPath ?? event.path ?? '/';
|
|
125
|
+
// Lambda's domainName won't carry a trailing dot in supported configs but
|
|
126
|
+
// we normalize here so a future shape change can't bypass the allowlist.
|
|
127
|
+
let url = `https://${host}${path}`;
|
|
128
|
+
if (event.rawQueryString) {
|
|
129
|
+
url += `?${event.rawQueryString}`;
|
|
130
|
+
}
|
|
131
|
+
else if (event.queryStringParameters) {
|
|
132
|
+
const params = Object.entries(event.queryStringParameters)
|
|
133
|
+
.filter(([, v]) => v !== undefined)
|
|
134
|
+
.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`);
|
|
135
|
+
if (params.length)
|
|
136
|
+
url += `?${params.join('&')}`;
|
|
137
|
+
}
|
|
138
|
+
return { method, url };
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Normalize a Host / domainName value for allowlist comparison. Lowercase
|
|
142
|
+
* + trim handle the obvious cases; trailing-dot stripping closes a
|
|
143
|
+
* documented Host-header bypass where `example.com.` is a distinct string
|
|
144
|
+
* from `example.com` on some stacks. IPv6 brackets are preserved so the
|
|
145
|
+
* allowlist entry and the inbound value agree on bracket presence.
|
|
146
|
+
*/
|
|
147
|
+
function normalizeHost(raw) {
|
|
148
|
+
if (!raw)
|
|
149
|
+
return '';
|
|
150
|
+
let h = raw.trim().toLowerCase();
|
|
151
|
+
// Strip trailing dot from FQDN. Bracketed IPv6 cannot legitimately
|
|
152
|
+
// carry a trailing dot, so this only fires on hostnames.
|
|
153
|
+
if (h.endsWith('.') && !h.endsWith(']'))
|
|
154
|
+
h = h.slice(0, -1);
|
|
155
|
+
return h;
|
|
156
|
+
}
|
|
157
|
+
function hostMatchesAllowlist(host, allowlist) {
|
|
158
|
+
return allowlist.some(entry => normalizeHost(entry) === host);
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=request-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-context.js","sourceRoot":"","sources":["../../../src/lib/signing/request-context.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RFC 9421 response-signing verifier (§2.2.9).
|
|
3
|
+
*
|
|
4
|
+
* Companion to `verifier.ts` (request signatures) and `webhook-verifier.ts`
|
|
5
|
+
* (webhook callbacks). This verifier runs on the buyer / receiver side of
|
|
6
|
+
* a signed response: a client receives a response from a seller agent and
|
|
7
|
+
* hands it here, with the originating request URL the client sent, for
|
|
8
|
+
* signature validation before parsing the body.
|
|
9
|
+
*
|
|
10
|
+
* Distinct from request / webhook signing:
|
|
11
|
+
* - Tag: `adcp/response-signing/v1`.
|
|
12
|
+
* - Key purpose: `adcp_use: "response-signing"`.
|
|
13
|
+
* - Default covered components: `@status`, `@authority`, `@target-uri`,
|
|
14
|
+
* plus `content-type` + `content-digest` when the body is non-empty.
|
|
15
|
+
* - The originating request URL is carried explicitly on `ResponseLike.request`
|
|
16
|
+
* because RFC 9421 §2.2 binds response signatures to their request
|
|
17
|
+
* context via `@authority` and `@target-uri`. The client supplies the URL
|
|
18
|
+
* it actually sent — a malformed reconstruction (e.g. `req.protocol`
|
|
19
|
+
* lying behind a proxy) will trip step 6a or fail the crypto check.
|
|
20
|
+
*
|
|
21
|
+
* Checklist steps below mirror the 13-step shape in `webhook-verifier.ts`
|
|
22
|
+
* so failures point at the same step numbers the request and webhook
|
|
23
|
+
* verifiers use. Numbers are 1-based.
|
|
24
|
+
*/
|
|
25
|
+
import { type ResponseLike } from './canonicalize';
|
|
26
|
+
import type { JwksResolver } from './jwks';
|
|
27
|
+
import { type ReplayStore } from './replay';
|
|
28
|
+
import { type RevocationStore } from './revocation';
|
|
29
|
+
export interface VerifyResponseOptions {
|
|
30
|
+
jwks: JwksResolver;
|
|
31
|
+
replayStore: ReplayStore;
|
|
32
|
+
revocationStore: RevocationStore;
|
|
33
|
+
/** Now in seconds since epoch. Defaults to `Date.now() / 1000`. */
|
|
34
|
+
now?: () => number;
|
|
35
|
+
/**
|
|
36
|
+
* Optional tag override — spec currently defines exactly
|
|
37
|
+
* `adcp/response-signing/v1`; the override lets test vectors pin a
|
|
38
|
+
* version.
|
|
39
|
+
*/
|
|
40
|
+
requiredTag?: string;
|
|
41
|
+
/** Optional reverse-lookup (kid → publisher URL) for result attribution. */
|
|
42
|
+
agentUrlForKeyid?: (keyid: string) => string | undefined;
|
|
43
|
+
}
|
|
44
|
+
export interface VerifyResponseResult {
|
|
45
|
+
status: 'verified';
|
|
46
|
+
keyid: string;
|
|
47
|
+
agent_url?: string;
|
|
48
|
+
verified_at: number;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Verify an inbound response's RFC 9421 signature.
|
|
52
|
+
*
|
|
53
|
+
* Ordering invariant matches the webhook verifier: cheap invariant checks
|
|
54
|
+
* (tag, alg, window, components) run before JWKS resolution; revocation and
|
|
55
|
+
* rate-abuse run before cryptographic verify so an attacker can't amplify
|
|
56
|
+
* Ed25519/ECDSA work. Replay insert commits only after every earlier check
|
|
57
|
+
* passes — any signature failing crypto verify never consumes a cap entry.
|
|
58
|
+
*
|
|
59
|
+
* The replay scope is `(keyid, @target-uri-of-originating-request)`. Two
|
|
60
|
+
* responses to two different request URLs under the same keyid use
|
|
61
|
+
* independent replay budgets — same shape as webhook verification.
|
|
62
|
+
*
|
|
63
|
+
* Throws {@link ResponseSignatureError} on the first failed step.
|
|
64
|
+
*/
|
|
65
|
+
export declare function verifyResponseSignature(response: ResponseLike, options: VerifyResponseOptions): Promise<VerifyResponseResult>;
|
|
66
|
+
/**
|
|
67
|
+
* Options for {@link createResponseVerifier}. Identical to
|
|
68
|
+
* {@link VerifyResponseOptions} except `replayStore` and `revocationStore`
|
|
69
|
+
* are optional — the factory defaults them once at creation time so replay
|
|
70
|
+
* state is shared across every response the returned verifier handles.
|
|
71
|
+
*/
|
|
72
|
+
export interface CreateResponseVerifierOptions extends Omit<VerifyResponseOptions, 'replayStore' | 'revocationStore'> {
|
|
73
|
+
/**
|
|
74
|
+
* Stores `(keyid, scope, nonce)` tuples for replay detection. Defaults to
|
|
75
|
+
* a fresh {@link InMemoryReplayStore} — suitable for single-process
|
|
76
|
+
* deployments only. **Multi-replica deployments MUST pass an explicit
|
|
77
|
+
* shared store** (Redis, Postgres, etc.); the default in-memory store
|
|
78
|
+
* does not survive process boundaries, so a signature accepted on
|
|
79
|
+
* replica A is invisible to replica B and can be replayed there within
|
|
80
|
+
* the signature window.
|
|
81
|
+
*/
|
|
82
|
+
replayStore?: ReplayStore;
|
|
83
|
+
/**
|
|
84
|
+
* Consulted for revoked `kid` before accepting a signature. Defaults to a
|
|
85
|
+
* fresh {@link InMemoryRevocationStore}, which starts empty and does not
|
|
86
|
+
* poll for updates. Pass a store backed by your secrets manager or admin
|
|
87
|
+
* tooling when you revoke keys at runtime.
|
|
88
|
+
*/
|
|
89
|
+
revocationStore?: RevocationStore;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Create a bound response-signature verifier with shared replay and
|
|
93
|
+
* revocation stores. Mirrors {@link createWebhookVerifier} for the response
|
|
94
|
+
* profile.
|
|
95
|
+
*
|
|
96
|
+
* **Why a factory?** Replay detection requires the same store instance to
|
|
97
|
+
* be consulted across every response. Constructing stores inside a
|
|
98
|
+
* per-response call would silently defeat replay dedup. The factory pattern
|
|
99
|
+
* captures stores in closure scope at wire-up time.
|
|
100
|
+
*
|
|
101
|
+
* **Multi-replica deployments MUST pass an explicit `replayStore`** backed
|
|
102
|
+
* by a shared persistence layer.
|
|
103
|
+
*/
|
|
104
|
+
export declare function createResponseVerifier(options: CreateResponseVerifierOptions): (response: ResponseLike) => Promise<VerifyResponseResult>;
|
|
105
|
+
//# sourceMappingURL=response-verifier.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"response-verifier.d.ts","sourceRoot":"","sources":["../../../src/lib/signing/response-verifier.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAkE,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAKnH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAC3C,OAAO,EAAuB,KAAK,WAAW,EAAE,MAAM,UAAU,CAAC;AACjE,OAAO,EAA2B,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AAS7E,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,YAAY,CAAC;IACnB,WAAW,EAAE,WAAW,CAAC;IACzB,eAAe,EAAE,eAAe,CAAC;IACjC,mEAAmE;IACnE,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;CAC1D;AAED,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,UAAU,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,uBAAuB,CAC3C,QAAQ,EAAE,YAAY,EACtB,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,oBAAoB,CAAC,CAuM/B;AAkHD;;;;;GAKG;AACH,MAAM,WAAW,6BAA8B,SAAQ,IAAI,CAAC,qBAAqB,EAAE,aAAa,GAAG,iBAAiB,CAAC;IACnH;;;;;;;;OAQG;IACH,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B;;;;;OAKG;IACH,eAAe,CAAC,EAAE,eAAe,CAAC;CACnC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,6BAA6B,GACrC,CAAC,QAAQ,EAAE,YAAY,KAAK,OAAO,CAAC,oBAAoB,CAAC,CAI3D"}
|