@adcp/sdk 14.0.0 → 14.2.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/dist/lib/adapters/implicit-account-store.d.mts +12 -7
- package/dist/lib/adapters/implicit-account-store.d.ts +12 -7
- package/dist/lib/adapters/implicit-account-store.js +69 -15
- package/dist/lib/adapters/implicit-account-store.mjs +69 -15
- package/dist/lib/core/AgentClient.d.mts +1 -0
- package/dist/lib/core/AgentClient.d.ts +1 -0
- package/dist/lib/core/AgentClient.js +3 -0
- package/dist/lib/core/AgentClient.mjs +3 -0
- package/dist/lib/core/SingleAgentClient.d.mts +35 -2
- package/dist/lib/core/SingleAgentClient.d.ts +35 -2
- package/dist/lib/core/SingleAgentClient.js +377 -36
- package/dist/lib/core/SingleAgentClient.mjs +387 -38
- package/dist/lib/core/TaskExecutor.d.mts +3 -1
- package/dist/lib/core/TaskExecutor.d.ts +3 -1
- package/dist/lib/core/TaskExecutor.js +17 -12
- package/dist/lib/core/TaskExecutor.mjs +17 -12
- package/dist/lib/core/account-key.d.mts +3 -0
- package/dist/lib/core/account-key.d.ts +3 -0
- package/dist/lib/core/account-key.js +41 -0
- package/dist/lib/core/account-key.mjs +17 -0
- package/dist/lib/core/account-resolution.d.mts +2 -0
- package/dist/lib/core/account-resolution.d.ts +2 -0
- package/dist/lib/core/buyer-account-registry.d.mts +93 -0
- package/dist/lib/core/buyer-account-registry.d.ts +93 -0
- package/dist/lib/core/buyer-account-registry.js +602 -0
- package/dist/lib/core/buyer-account-registry.mjs +578 -0
- package/dist/lib/core/product-cache.d.mts +18 -0
- package/dist/lib/core/product-cache.d.ts +18 -0
- package/dist/lib/core/product-cache.js +137 -0
- package/dist/lib/core/product-cache.mjs +112 -0
- package/dist/lib/errors/index.d.mts +40 -1
- package/dist/lib/errors/index.d.ts +40 -1
- package/dist/lib/errors/index.js +69 -3
- package/dist/lib/errors/index.mjs +64 -3
- package/dist/lib/governance/authorization.d.mts +17 -1
- package/dist/lib/governance/authorization.d.ts +17 -1
- package/dist/lib/governance/authorization.js +55 -7
- package/dist/lib/governance/authorization.mjs +59 -7
- package/dist/lib/governance/index.d.mts +2 -2
- package/dist/lib/governance/index.d.ts +2 -2
- package/dist/lib/governance/index.js +2 -0
- package/dist/lib/governance/index.mjs +3 -1
- package/dist/lib/index.d.mts +7 -4
- package/dist/lib/index.d.ts +7 -4
- package/dist/lib/index.js +29 -0
- package/dist/lib/index.mjs +31 -1
- package/dist/lib/net/agent-transport-fetch.d.mts +4 -0
- package/dist/lib/net/agent-transport-fetch.d.ts +4 -0
- package/dist/lib/net/agent-transport-fetch.js +18 -5
- package/dist/lib/net/agent-transport-fetch.mjs +17 -5
- package/dist/lib/protocols/a2a.js +9 -1
- package/dist/lib/protocols/a2a.mjs +9 -1
- package/dist/lib/protocols/index.js +9 -2
- package/dist/lib/protocols/index.mjs +9 -2
- package/dist/lib/protocols/mcp-modern.js +2 -1
- package/dist/lib/protocols/mcp-modern.mjs +2 -1
- package/dist/lib/protocols/mcp.js +5 -2
- package/dist/lib/protocols/mcp.mjs +5 -2
- package/dist/lib/protocols/rawResponseCapture.d.mts +6 -0
- package/dist/lib/protocols/rawResponseCapture.d.ts +6 -0
- package/dist/lib/protocols/rawResponseCapture.js +41 -29
- package/dist/lib/protocols/rawResponseCapture.mjs +40 -29
- package/dist/lib/protocols/signedRequestRejection.d.mts +9 -0
- package/dist/lib/protocols/signedRequestRejection.d.ts +9 -0
- package/dist/lib/protocols/signedRequestRejection.js +209 -0
- package/dist/lib/protocols/signedRequestRejection.mjs +189 -0
- package/dist/lib/protocols/transportDiagnostics.d.mts +1 -0
- package/dist/lib/protocols/transportDiagnostics.d.ts +1 -0
- package/dist/lib/protocols/transportDiagnostics.js +2 -0
- package/dist/lib/protocols/transportDiagnostics.mjs +1 -0
- package/dist/lib/registry/types.generated.d.mts +112 -45
- package/dist/lib/registry/types.generated.d.ts +112 -45
- package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
- package/dist/lib/server/account-provisioning.d.mts +2 -0
- package/dist/lib/server/account-provisioning.d.ts +2 -0
- package/dist/lib/server/account-provisioning.js +30 -0
- package/dist/lib/server/account-provisioning.mjs +6 -0
- package/dist/lib/server/account-reference-warnings.d.mts +12 -0
- package/dist/lib/server/account-reference-warnings.d.ts +12 -0
- package/dist/lib/server/account-reference-warnings.js +48 -0
- package/dist/lib/server/account-reference-warnings.mjs +23 -0
- package/dist/lib/server/auth-signature.js +1 -0
- package/dist/lib/server/auth-signature.mjs +1 -0
- package/dist/lib/server/create-adcp-server.d.mts +34 -0
- package/dist/lib/server/create-adcp-server.d.ts +34 -0
- package/dist/lib/server/create-adcp-server.js +225 -14
- package/dist/lib/server/create-adcp-server.mjs +225 -14
- package/dist/lib/server/decisioning/account.d.mts +2 -0
- package/dist/lib/server/decisioning/account.d.ts +2 -0
- package/dist/lib/server/decisioning/runtime/from-platform.js +49 -10
- package/dist/lib/server/decisioning/runtime/from-platform.mjs +49 -10
- package/dist/lib/server/index.d.mts +2 -2
- package/dist/lib/server/index.d.ts +2 -2
- package/dist/lib/server/index.js +2 -0
- package/dist/lib/server/index.mjs +3 -1
- package/dist/lib/signing/agent-resolver/consistency.d.mts +6 -13
- package/dist/lib/signing/agent-resolver/consistency.d.ts +6 -13
- package/dist/lib/signing/agent-resolver/consistency.js +0 -1
- package/dist/lib/signing/agent-resolver/consistency.mjs +0 -1
- package/dist/lib/signing/agent-resolver/errors.d.mts +3 -1
- package/dist/lib/signing/agent-resolver/errors.d.ts +3 -1
- package/dist/lib/signing/agent-resolver/errors.js +6 -0
- package/dist/lib/signing/agent-resolver/errors.mjs +6 -0
- package/dist/lib/signing/agent-resolver/fetch-helpers.d.mts +11 -0
- package/dist/lib/signing/agent-resolver/fetch-helpers.d.ts +11 -0
- package/dist/lib/signing/agent-resolver/fetch-helpers.js +37 -2
- package/dist/lib/signing/agent-resolver/fetch-helpers.mjs +40 -3
- package/dist/lib/signing/agent-resolver/jwks-set.js +24 -3
- package/dist/lib/signing/agent-resolver/jwks-set.mjs +24 -3
- package/dist/lib/signing/agent-resolver/legacy-brand.d.mts +15 -0
- package/dist/lib/signing/agent-resolver/legacy-brand.d.ts +15 -0
- package/dist/lib/signing/agent-resolver/legacy-brand.js +60 -0
- package/dist/lib/signing/agent-resolver/legacy-brand.mjs +36 -0
- package/dist/lib/signing/agent-resolver/operator-authorization.d.mts +13 -0
- package/dist/lib/signing/agent-resolver/operator-authorization.d.ts +13 -0
- package/dist/lib/signing/agent-resolver/operator-authorization.js +108 -0
- package/dist/lib/signing/agent-resolver/operator-authorization.mjs +84 -0
- package/dist/lib/signing/agent-resolver/resolve-agent.d.mts +14 -4
- package/dist/lib/signing/agent-resolver/resolve-agent.d.ts +14 -4
- package/dist/lib/signing/agent-resolver/resolve-agent.js +148 -137
- package/dist/lib/signing/agent-resolver/resolve-agent.mjs +157 -139
- package/dist/lib/signing/agent-resolver/resolved-agent-jwks.d.mts +7 -1
- package/dist/lib/signing/agent-resolver/resolved-agent-jwks.d.ts +7 -1
- package/dist/lib/signing/agent-resolver/resolved-agent-jwks.js +50 -17
- package/dist/lib/signing/agent-resolver/resolved-agent-jwks.mjs +50 -17
- package/dist/lib/signing/agent-resolver/select-agent.d.mts +14 -15
- package/dist/lib/signing/agent-resolver/select-agent.d.ts +14 -15
- package/dist/lib/signing/agent-resolver/select-agent.js +98 -13
- package/dist/lib/signing/agent-resolver/select-agent.mjs +95 -13
- package/dist/lib/signing/brand-jwks.d.mts +29 -75
- package/dist/lib/signing/brand-jwks.d.ts +29 -75
- package/dist/lib/signing/brand-jwks.js +120 -182
- package/dist/lib/signing/brand-jwks.mjs +120 -182
- package/dist/lib/signing/errors.d.mts +7 -3
- package/dist/lib/signing/errors.d.ts +7 -3
- package/dist/lib/signing/errors.js +7 -2
- package/dist/lib/signing/errors.mjs +7 -2
- package/dist/lib/signing/jwks-https.d.mts +7 -0
- package/dist/lib/signing/jwks-https.d.ts +7 -0
- package/dist/lib/signing/jwks-https.js +31 -8
- package/dist/lib/signing/jwks-https.mjs +31 -8
- package/dist/lib/signing/jwks.d.mts +8 -0
- package/dist/lib/signing/jwks.d.ts +8 -0
- package/dist/lib/signing/middleware.js +2 -1
- package/dist/lib/signing/middleware.mjs +2 -1
- package/dist/lib/signing/publisher-pins.d.mts +11 -0
- package/dist/lib/signing/publisher-pins.d.ts +11 -0
- package/dist/lib/signing/publisher-pins.js +125 -0
- package/dist/lib/signing/publisher-pins.mjs +101 -0
- package/dist/lib/signing/server.d.mts +1 -0
- package/dist/lib/signing/server.d.ts +1 -0
- package/dist/lib/signing/types.d.mts +5 -0
- package/dist/lib/signing/types.d.ts +5 -0
- package/dist/lib/signing/verifier.js +50 -5
- package/dist/lib/signing/verifier.mjs +50 -5
- package/dist/lib/signing/webhook-verifier.d.mts +7 -2
- package/dist/lib/signing/webhook-verifier.d.ts +7 -2
- package/dist/lib/signing/webhook-verifier.js +42 -2
- package/dist/lib/signing/webhook-verifier.mjs +42 -2
- package/dist/lib/testing/storyboard/account-policy.d.mts +2 -0
- package/dist/lib/testing/storyboard/account-policy.d.ts +2 -0
- package/dist/lib/testing/storyboard/account-policy.js +35 -0
- package/dist/lib/testing/storyboard/account-policy.mjs +11 -0
- package/dist/lib/testing/storyboard/context.js +6 -0
- package/dist/lib/testing/storyboard/context.mjs +6 -0
- package/dist/lib/testing/storyboard/request-builder.js +12 -2
- package/dist/lib/testing/storyboard/request-builder.mjs +12 -2
- package/dist/lib/testing/storyboard/runner.js +3 -2
- package/dist/lib/testing/storyboard/runner.mjs +3 -2
- package/dist/lib/testing/storyboard/validations.d.mts +1 -1
- package/dist/lib/testing/storyboard/validations.d.ts +1 -1
- package/dist/lib/types/accept-proposal.d.ts +19 -1
- package/dist/lib/types/buy-products.d.ts +19 -1
- package/dist/lib/types/check-governance.d.ts +19 -1
- package/dist/lib/types/comply-test-controller.d.ts +19 -1
- package/dist/lib/types/control-media-buy.d.ts +19 -1
- package/dist/lib/types/core.generated.d.mts +14 -1
- package/dist/lib/types/core.generated.d.ts +14 -1
- package/dist/lib/types/create-media-buy.d.ts +14 -1
- package/dist/lib/types/get-media-buys.d.ts +14 -1
- package/dist/lib/types/get-products.d.ts +19 -1
- package/dist/lib/types/list-products.d.ts +19 -1
- package/dist/lib/types/refine-proposals.d.ts +19 -1
- package/dist/lib/types/request-proposals.d.ts +19 -1
- package/dist/lib/types/schemas.generated.d.ts +12 -3
- package/dist/lib/types/schemas.generated.js +4 -1
- package/dist/lib/types/schemas.generated.mjs +4 -1
- package/dist/lib/types/tools.generated.d.mts +14 -1
- package/dist/lib/types/tools.generated.d.ts +14 -1
- package/dist/lib/types/update-media-buy.d.ts +14 -1
- package/dist/lib/version.d.mts +3 -3
- package/dist/lib/version.d.ts +3 -3
- package/dist/lib/version.js +3 -3
- package/dist/lib/version.mjs +3 -3
- package/dist/lib/webhooks/index.d.mts +24 -0
- package/dist/lib/webhooks/index.d.ts +24 -0
- package/dist/lib/webhooks/index.js +50 -24
- package/dist/lib/webhooks/index.mjs +49 -24
- package/dist/lib/wholesale-feed-sync/index.d.mts +2 -0
- package/dist/lib/wholesale-feed-sync/index.d.ts +2 -0
- package/dist/lib/wholesale-feed-sync/index.js +7 -0
- package/dist/lib/wholesale-feed-sync/index.mjs +4 -0
- package/dist/lib/wholesale-feed-sync/mirror.d.mts +97 -0
- package/dist/lib/wholesale-feed-sync/mirror.d.ts +97 -0
- package/dist/lib/wholesale-feed-sync/mirror.js +350 -0
- package/dist/lib/wholesale-feed-sync/mirror.mjs +322 -0
- package/dist/lib/wholesale-feed-sync/sync.d.mts +13 -29
- package/dist/lib/wholesale-feed-sync/sync.d.ts +13 -29
- package/dist/lib/wholesale-feed-sync/sync.js +208 -281
- package/dist/lib/wholesale-feed-sync/sync.mjs +213 -281
- package/dist/lib/wholesale-feed-sync/types.d.mts +4 -3
- package/dist/lib/wholesale-feed-sync/types.d.ts +4 -3
- package/docs/README.md +6 -0
- package/docs/TYPE-SUMMARY.md +2 -2
- package/docs/guides/BUILD-AN-AGENT.md +2 -2
- package/docs/guides/BUYER-QUICKSTART-3.2.md +2 -0
- package/docs/guides/BUYER-STORAGE.md +3 -0
- package/docs/guides/FIRST-CALL-TO-A-SELLER.md +106 -0
- package/docs/guides/SIGNING-GUIDE.md +16 -7
- package/docs/guides/account-resolution.md +132 -10
- package/docs/llms.txt +3 -2
- package/docs/migration-14.0-to-14.1.md +85 -0
- package/docs/migration-14.x-rc-worksheet.md +4 -4
- package/docs/migration-4.x-to-5.x.md +1 -0
- package/docs/migration-agent-resolution-3.3.md +125 -0
- package/docs/recipes/verifying-inbound-webhooks.md +60 -15
- package/package.json +3 -2
- package/skills/adcp-brand.previous/SKILL.md +0 -200
- package/skills/adcp-creative.previous/SKILL.md +0 -305
- package/skills/adcp-governance.previous/SKILL.md +0 -566
- package/skills/adcp-measurement.previous/SKILL.md +0 -136
- package/skills/adcp-media-buy.previous/SKILL.md +0 -556
- package/skills/adcp-si.previous/SKILL.md +0 -206
- package/skills/adcp-signals.previous/SKILL.md +0 -204
|
@@ -1,38 +1,59 @@
|
|
|
1
1
|
import { ssrfSafeFetch } from "../net/index.mjs";
|
|
2
|
+
import { parseStrictJson } from "./agent-resolver/strict-json.mjs";
|
|
2
3
|
const DEFAULT_MIN_COOLDOWN_SECONDS = 30;
|
|
3
4
|
const DEFAULT_MAX_AGE_SECONDS = 3600;
|
|
4
5
|
class HttpsJwksResolver {
|
|
5
6
|
url;
|
|
6
7
|
minCooldown;
|
|
7
8
|
maxAge;
|
|
9
|
+
minCacheAge;
|
|
10
|
+
failClosed;
|
|
8
11
|
allowPrivateIp;
|
|
9
12
|
lookup;
|
|
10
13
|
now;
|
|
11
14
|
cache;
|
|
12
15
|
inFlight;
|
|
16
|
+
lastAttemptAt = Number.NEGATIVE_INFINITY;
|
|
13
17
|
constructor(url, options = {}) {
|
|
14
18
|
this.url = url;
|
|
15
19
|
this.minCooldown = options.minCooldownSeconds ?? DEFAULT_MIN_COOLDOWN_SECONDS;
|
|
16
20
|
this.maxAge = options.maxAgeSeconds ?? DEFAULT_MAX_AGE_SECONDS;
|
|
21
|
+
this.minCacheAge = Math.min(options.minCacheAgeSeconds ?? 0, this.maxAge);
|
|
22
|
+
if (!Number.isFinite(this.maxAge) || this.maxAge < 0) {
|
|
23
|
+
throw new TypeError("maxAgeSeconds must be a finite non-negative number");
|
|
24
|
+
}
|
|
25
|
+
if (!Number.isFinite(this.minCooldown) || this.minCooldown < 0) {
|
|
26
|
+
throw new TypeError("minCooldownSeconds must be a finite non-negative number");
|
|
27
|
+
}
|
|
28
|
+
if (!Number.isFinite(options.minCacheAgeSeconds ?? 0) || (options.minCacheAgeSeconds ?? 0) < 0) {
|
|
29
|
+
throw new TypeError("minCacheAgeSeconds must be a finite non-negative number");
|
|
30
|
+
}
|
|
31
|
+
this.failClosed = options.failClosed === true;
|
|
17
32
|
this.allowPrivateIp = options.allowPrivateIp ?? false;
|
|
18
33
|
this.lookup = options.lookup;
|
|
19
34
|
this.now = options.now ?? (() => Math.floor(Date.now() / 1e3));
|
|
20
35
|
}
|
|
21
36
|
async resolve(keyid) {
|
|
22
37
|
if (!this.cache) {
|
|
38
|
+
if (!this.inFlight && this.now() - this.lastAttemptAt < this.minCooldown) {
|
|
39
|
+
throw new Error("JWKS lookup is in its retry cooldown");
|
|
40
|
+
}
|
|
23
41
|
await this.refresh();
|
|
24
42
|
} else if (this.cache.keys.has(keyid)) {
|
|
25
|
-
if (this.now()
|
|
43
|
+
if (this.now() < this.cache.expiresAt) {
|
|
26
44
|
return this.cache.keys.get(keyid) ?? null;
|
|
27
45
|
}
|
|
28
|
-
if (this.now() - this.
|
|
29
|
-
await this.refresh().catch(() => {
|
|
46
|
+
if (this.now() - this.lastAttemptAt >= this.minCooldown) {
|
|
47
|
+
await this.refresh().catch((error) => {
|
|
48
|
+
if (this.failClosed) throw error;
|
|
30
49
|
});
|
|
31
50
|
}
|
|
32
|
-
} else if (this.now() - this.
|
|
33
|
-
await this.refresh().catch(() => {
|
|
51
|
+
} else if (this.now() - this.lastAttemptAt >= this.minCooldown) {
|
|
52
|
+
await this.refresh().catch((error) => {
|
|
53
|
+
if (this.failClosed) throw error;
|
|
34
54
|
});
|
|
35
55
|
}
|
|
56
|
+
if (this.failClosed && this.cache && this.now() >= this.cache.expiresAt) throw new Error("JWKS snapshot expired");
|
|
36
57
|
return this.cache?.keys.get(keyid) ?? null;
|
|
37
58
|
}
|
|
38
59
|
/**
|
|
@@ -49,6 +70,7 @@ class HttpsJwksResolver {
|
|
|
49
70
|
await this.inFlight;
|
|
50
71
|
return;
|
|
51
72
|
}
|
|
73
|
+
this.lastAttemptAt = this.now();
|
|
52
74
|
this.inFlight = this.doRefresh().finally(() => {
|
|
53
75
|
this.inFlight = void 0;
|
|
54
76
|
});
|
|
@@ -80,7 +102,7 @@ class HttpsJwksResolver {
|
|
|
80
102
|
const text = Buffer.from(res.body).toString("utf8");
|
|
81
103
|
let parsed;
|
|
82
104
|
try {
|
|
83
|
-
parsed =
|
|
105
|
+
parsed = parseStrictJson(text);
|
|
84
106
|
} catch {
|
|
85
107
|
throw new Error(`JWKS fetch ${this.url} returned non-JSON body`);
|
|
86
108
|
}
|
|
@@ -95,6 +117,7 @@ class HttpsJwksResolver {
|
|
|
95
117
|
for (const entry of keysField) {
|
|
96
118
|
if (entry && typeof entry === "object" && typeof entry.kid === "string") {
|
|
97
119
|
const jwk = entry;
|
|
120
|
+
if (byKid.has(jwk.kid)) throw new Error("JWKS has duplicate key identifiers");
|
|
98
121
|
byKid.set(jwk.kid, jwk);
|
|
99
122
|
}
|
|
100
123
|
}
|
|
@@ -107,11 +130,11 @@ class HttpsJwksResolver {
|
|
|
107
130
|
}
|
|
108
131
|
computeExpiry(headers) {
|
|
109
132
|
const cacheCtl = headers["cache-control"]?.toLowerCase() ?? "";
|
|
110
|
-
if (/\bno-store\b|\bno-cache\b/.test(cacheCtl)) return this.now();
|
|
133
|
+
if (/\bno-store\b|\bno-cache\b/.test(cacheCtl)) return this.now() + this.minCacheAge;
|
|
111
134
|
const match = /max-age\s*=\s*(\d+)/.exec(cacheCtl);
|
|
112
135
|
if (match) {
|
|
113
136
|
const serverMax = Number(match[1]);
|
|
114
|
-
if (Number.isFinite(serverMax)) return this.now() + Math.min(serverMax, this.maxAge);
|
|
137
|
+
if (Number.isFinite(serverMax)) return this.now() + Math.max(this.minCacheAge, Math.min(serverMax, this.maxAge));
|
|
115
138
|
}
|
|
116
139
|
return this.now() + this.maxAge;
|
|
117
140
|
}
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import type { AdcpJsonWebKey } from './types.mjs';
|
|
2
2
|
export interface JwksResolution {
|
|
3
3
|
jwk: AdcpJsonWebKey | null;
|
|
4
|
+
/** Canonical agent identity established by capability-bound discovery. */
|
|
5
|
+
agentUrl?: string;
|
|
6
|
+
/** Exact capability-selected operator document for signed-buyer governance. */
|
|
7
|
+
operatorRecord?: {
|
|
8
|
+
url: string;
|
|
9
|
+
document: unknown;
|
|
10
|
+
};
|
|
11
|
+
legacyWebhookFallback?: boolean;
|
|
4
12
|
/** Epoch seconds for a delegated-operator authorization boundary, when applicable. */
|
|
5
13
|
operatorAuthorizationValidUntil?: number;
|
|
6
14
|
}
|
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import type { AdcpJsonWebKey } from './types';
|
|
2
2
|
export interface JwksResolution {
|
|
3
3
|
jwk: AdcpJsonWebKey | null;
|
|
4
|
+
/** Canonical agent identity established by capability-bound discovery. */
|
|
5
|
+
agentUrl?: string;
|
|
6
|
+
/** Exact capability-selected operator document for signed-buyer governance. */
|
|
7
|
+
operatorRecord?: {
|
|
8
|
+
url: string;
|
|
9
|
+
document: unknown;
|
|
10
|
+
};
|
|
11
|
+
legacyWebhookFallback?: boolean;
|
|
4
12
|
/** Epoch seconds for a delegated-operator authorization boundary, when applicable. */
|
|
5
13
|
operatorAuthorizationValidUntil?: number;
|
|
6
14
|
}
|
|
@@ -49,7 +49,8 @@ function createExpressVerifier(options) {
|
|
|
49
49
|
req.verifiedSigner = {
|
|
50
50
|
keyid: result.keyid,
|
|
51
51
|
agent_url: result.agent_url,
|
|
52
|
-
verified_at: result.verified_at
|
|
52
|
+
verified_at: result.verified_at,
|
|
53
|
+
...result.operatorRecord !== void 0 && { operatorRecord: result.operatorRecord }
|
|
53
54
|
};
|
|
54
55
|
}
|
|
55
56
|
next();
|
|
@@ -26,7 +26,8 @@ function createExpressVerifier(options) {
|
|
|
26
26
|
req.verifiedSigner = {
|
|
27
27
|
keyid: result.keyid,
|
|
28
28
|
agent_url: result.agent_url,
|
|
29
|
-
verified_at: result.verified_at
|
|
29
|
+
verified_at: result.verified_at,
|
|
30
|
+
...result.operatorRecord !== void 0 && { operatorRecord: result.operatorRecord }
|
|
30
31
|
};
|
|
31
32
|
}
|
|
32
33
|
next();
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { AdcpJsonWebKey } from './types.mjs';
|
|
2
|
+
export interface PublisherSigningKeyPin {
|
|
3
|
+
/** Publisher identity from the buyer's own record, for local diagnostics. */
|
|
4
|
+
publisher: string;
|
|
5
|
+
/** undefined means no pin; an empty pin accepts no keys. */
|
|
6
|
+
signingKeys?: readonly Record<string, unknown>[];
|
|
7
|
+
/** Fetch adagents.json without its usual cache. Reuse a callback bound to this agent/publisher/tenant across deliveries; refreshes share a 30-second cooldown. Return null only for confirmed pin removal; throw on failure. */
|
|
8
|
+
refresh: () => Promise<readonly Record<string, unknown>[] | null>;
|
|
9
|
+
}
|
|
10
|
+
/** Intersect an already discovered JWKS key with every applicable publisher pin. */
|
|
11
|
+
export declare function assertPublisherPins(jwk: AdcpJsonWebKey, publishers: readonly PublisherSigningKeyPin[], now: () => number): Promise<void>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { AdcpJsonWebKey } from './types';
|
|
2
|
+
export interface PublisherSigningKeyPin {
|
|
3
|
+
/** Publisher identity from the buyer's own record, for local diagnostics. */
|
|
4
|
+
publisher: string;
|
|
5
|
+
/** undefined means no pin; an empty pin accepts no keys. */
|
|
6
|
+
signingKeys?: readonly Record<string, unknown>[];
|
|
7
|
+
/** Fetch adagents.json without its usual cache. Reuse a callback bound to this agent/publisher/tenant across deliveries; refreshes share a 30-second cooldown. Return null only for confirmed pin removal; throw on failure. */
|
|
8
|
+
refresh: () => Promise<readonly Record<string, unknown>[] | null>;
|
|
9
|
+
}
|
|
10
|
+
/** Intersect an already discovered JWKS key with every applicable publisher pin. */
|
|
11
|
+
export declare function assertPublisherPins(jwk: AdcpJsonWebKey, publishers: readonly PublisherSigningKeyPin[], now: () => number): Promise<void>;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var publisher_pins_exports = {};
|
|
20
|
+
__export(publisher_pins_exports, {
|
|
21
|
+
assertPublisherPins: () => assertPublisherPins
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(publisher_pins_exports);
|
|
24
|
+
var import_jose = require("jose");
|
|
25
|
+
var import_errors = require('./agent-resolver/errors.js');
|
|
26
|
+
const refreshes = /* @__PURE__ */ new WeakMap();
|
|
27
|
+
async function refreshPin(publisher, now) {
|
|
28
|
+
let states = refreshes.get(publisher.refresh);
|
|
29
|
+
if (!states) refreshes.set(publisher.refresh, states = /* @__PURE__ */ new Map());
|
|
30
|
+
const checkedAt = now();
|
|
31
|
+
const existing = states.get(publisher.publisher);
|
|
32
|
+
if (existing && (existing.inFlight || checkedAt - existing.attemptedAt < 30) && existing.inputPins !== JSON.stringify(publisher.signingKeys)) {
|
|
33
|
+
throw new Error("Publisher pin changed during the refresh cooldown");
|
|
34
|
+
}
|
|
35
|
+
if (existing?.inFlight) return existing.inFlight;
|
|
36
|
+
if (existing && checkedAt - existing.attemptedAt < 30) {
|
|
37
|
+
if (existing.error !== void 0) throw existing.error;
|
|
38
|
+
return existing.snapshot;
|
|
39
|
+
}
|
|
40
|
+
const state = { attemptedAt: checkedAt, inputPins: JSON.stringify(publisher.signingKeys) };
|
|
41
|
+
if (states.size >= 512) {
|
|
42
|
+
const evictable = [...states].find(([, candidate]) => !candidate.inFlight);
|
|
43
|
+
if (!evictable) throw new Error("Publisher refresh capacity exceeded");
|
|
44
|
+
states.delete(evictable[0]);
|
|
45
|
+
}
|
|
46
|
+
states.set(publisher.publisher, state);
|
|
47
|
+
state.inFlight = Promise.resolve().then(async () => {
|
|
48
|
+
try {
|
|
49
|
+
const snapshot = await publisher.refresh();
|
|
50
|
+
if (snapshot === void 0) throw new TypeError("Publisher refresh must return a confirmed pin or null");
|
|
51
|
+
state.snapshot = snapshot === null ? null : Object.freeze(snapshot.map((key) => Object.freeze({ ...key })));
|
|
52
|
+
return state.snapshot;
|
|
53
|
+
} catch (error) {
|
|
54
|
+
state.error = error ?? new Error("Publisher refresh failed");
|
|
55
|
+
throw state.error;
|
|
56
|
+
} finally {
|
|
57
|
+
state.inFlight = void 0;
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
return state.inFlight;
|
|
61
|
+
}
|
|
62
|
+
async function thumbprint(key) {
|
|
63
|
+
const members = key.kty === "OKP" ? ["crv", "x"] : key.kty === "EC" ? ["crv", "x", "y"] : key.kty === "RSA" ? ["n", "e"] : void 0;
|
|
64
|
+
if (!members || members.some((name) => typeof key[name] !== "string" || key[name] === "")) return void 0;
|
|
65
|
+
try {
|
|
66
|
+
return await (0, import_jose.calculateJwkThumbprint)(key, "sha256");
|
|
67
|
+
} catch {
|
|
68
|
+
return void 0;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
async function matchingValidity(keyThumbprint, pins) {
|
|
72
|
+
if (pins === void 0) return [Infinity];
|
|
73
|
+
const validity = [];
|
|
74
|
+
for (const pin of pins) {
|
|
75
|
+
if (!pin || typeof pin !== "object" || await thumbprint(pin) !== keyThumbprint) continue;
|
|
76
|
+
if (pin.revoked_at === void 0) validity.push(Infinity);
|
|
77
|
+
else if (typeof pin.revoked_at === "string") {
|
|
78
|
+
const revoked = Date.parse(pin.revoked_at) / 1e3;
|
|
79
|
+
validity.push(Number.isFinite(revoked) ? revoked : -Infinity);
|
|
80
|
+
} else validity.push(-Infinity);
|
|
81
|
+
}
|
|
82
|
+
return validity.length ? [Math.min(...validity)] : [];
|
|
83
|
+
}
|
|
84
|
+
async function matches(keyThumbprint, pins, now) {
|
|
85
|
+
const validity = await matchingValidity(keyThumbprint, pins);
|
|
86
|
+
return validity.some((until) => now() < until);
|
|
87
|
+
}
|
|
88
|
+
async function assertPublisherPins(jwk, publishers, now) {
|
|
89
|
+
const fingerprint = await thumbprint(jwk);
|
|
90
|
+
if (!fingerprint) throw new import_errors.AgentResolverError("request_signature_key_unknown", "Invalid public key", {});
|
|
91
|
+
const applicable = [];
|
|
92
|
+
const supplied = [];
|
|
93
|
+
for (const publisher of publishers) {
|
|
94
|
+
const inputPins = JSON.stringify(publisher.signingKeys);
|
|
95
|
+
supplied.push(inputPins);
|
|
96
|
+
const recent = refreshes.get(publisher.refresh)?.get(publisher.publisher);
|
|
97
|
+
let selected = recent?.snapshot !== void 0 && recent.inputPins === inputPins && now() - recent.attemptedAt < 30 ? recent.snapshot ?? void 0 : publisher.signingKeys;
|
|
98
|
+
if (!await matches(fingerprint, selected, now)) {
|
|
99
|
+
const refreshed = await refreshPin(publisher, now);
|
|
100
|
+
selected = refreshed ?? void 0;
|
|
101
|
+
if (!await matches(fingerprint, selected, now))
|
|
102
|
+
throw new import_errors.AgentResolverError(
|
|
103
|
+
"request_signature_key_unknown",
|
|
104
|
+
"Key is outside the applicable publisher pin",
|
|
105
|
+
{}
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
applicable.push(selected);
|
|
109
|
+
}
|
|
110
|
+
const validity = await Promise.all(applicable.map((selected) => matchingValidity(fingerprint, selected)));
|
|
111
|
+
const checkedInputs = publishers.map((publisher) => JSON.stringify(publisher.signingKeys));
|
|
112
|
+
for (let i = 0; i < publishers.length; i++) {
|
|
113
|
+
if (supplied[i] !== checkedInputs[i]) {
|
|
114
|
+
validity.push(await matchingValidity(fingerprint, publishers[i].signingKeys));
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const checkedAt = now();
|
|
118
|
+
if (publishers.some((publisher, i) => JSON.stringify(publisher.signingKeys) !== checkedInputs[i]) || validity.some((bounds) => !bounds.some((until) => checkedAt < until))) {
|
|
119
|
+
throw new import_errors.AgentResolverError("request_signature_key_unknown", "Key is outside the applicable publisher pin", {});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
123
|
+
0 && (module.exports = {
|
|
124
|
+
assertPublisherPins
|
|
125
|
+
});
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { calculateJwkThumbprint } from "jose";
|
|
2
|
+
import { AgentResolverError } from "./agent-resolver/errors.mjs";
|
|
3
|
+
const refreshes = /* @__PURE__ */ new WeakMap();
|
|
4
|
+
async function refreshPin(publisher, now) {
|
|
5
|
+
let states = refreshes.get(publisher.refresh);
|
|
6
|
+
if (!states) refreshes.set(publisher.refresh, states = /* @__PURE__ */ new Map());
|
|
7
|
+
const checkedAt = now();
|
|
8
|
+
const existing = states.get(publisher.publisher);
|
|
9
|
+
if (existing && (existing.inFlight || checkedAt - existing.attemptedAt < 30) && existing.inputPins !== JSON.stringify(publisher.signingKeys)) {
|
|
10
|
+
throw new Error("Publisher pin changed during the refresh cooldown");
|
|
11
|
+
}
|
|
12
|
+
if (existing?.inFlight) return existing.inFlight;
|
|
13
|
+
if (existing && checkedAt - existing.attemptedAt < 30) {
|
|
14
|
+
if (existing.error !== void 0) throw existing.error;
|
|
15
|
+
return existing.snapshot;
|
|
16
|
+
}
|
|
17
|
+
const state = { attemptedAt: checkedAt, inputPins: JSON.stringify(publisher.signingKeys) };
|
|
18
|
+
if (states.size >= 512) {
|
|
19
|
+
const evictable = [...states].find(([, candidate]) => !candidate.inFlight);
|
|
20
|
+
if (!evictable) throw new Error("Publisher refresh capacity exceeded");
|
|
21
|
+
states.delete(evictable[0]);
|
|
22
|
+
}
|
|
23
|
+
states.set(publisher.publisher, state);
|
|
24
|
+
state.inFlight = Promise.resolve().then(async () => {
|
|
25
|
+
try {
|
|
26
|
+
const snapshot = await publisher.refresh();
|
|
27
|
+
if (snapshot === void 0) throw new TypeError("Publisher refresh must return a confirmed pin or null");
|
|
28
|
+
state.snapshot = snapshot === null ? null : Object.freeze(snapshot.map((key) => Object.freeze({ ...key })));
|
|
29
|
+
return state.snapshot;
|
|
30
|
+
} catch (error) {
|
|
31
|
+
state.error = error ?? new Error("Publisher refresh failed");
|
|
32
|
+
throw state.error;
|
|
33
|
+
} finally {
|
|
34
|
+
state.inFlight = void 0;
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
return state.inFlight;
|
|
38
|
+
}
|
|
39
|
+
async function thumbprint(key) {
|
|
40
|
+
const members = key.kty === "OKP" ? ["crv", "x"] : key.kty === "EC" ? ["crv", "x", "y"] : key.kty === "RSA" ? ["n", "e"] : void 0;
|
|
41
|
+
if (!members || members.some((name) => typeof key[name] !== "string" || key[name] === "")) return void 0;
|
|
42
|
+
try {
|
|
43
|
+
return await calculateJwkThumbprint(key, "sha256");
|
|
44
|
+
} catch {
|
|
45
|
+
return void 0;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async function matchingValidity(keyThumbprint, pins) {
|
|
49
|
+
if (pins === void 0) return [Infinity];
|
|
50
|
+
const validity = [];
|
|
51
|
+
for (const pin of pins) {
|
|
52
|
+
if (!pin || typeof pin !== "object" || await thumbprint(pin) !== keyThumbprint) continue;
|
|
53
|
+
if (pin.revoked_at === void 0) validity.push(Infinity);
|
|
54
|
+
else if (typeof pin.revoked_at === "string") {
|
|
55
|
+
const revoked = Date.parse(pin.revoked_at) / 1e3;
|
|
56
|
+
validity.push(Number.isFinite(revoked) ? revoked : -Infinity);
|
|
57
|
+
} else validity.push(-Infinity);
|
|
58
|
+
}
|
|
59
|
+
return validity.length ? [Math.min(...validity)] : [];
|
|
60
|
+
}
|
|
61
|
+
async function matches(keyThumbprint, pins, now) {
|
|
62
|
+
const validity = await matchingValidity(keyThumbprint, pins);
|
|
63
|
+
return validity.some((until) => now() < until);
|
|
64
|
+
}
|
|
65
|
+
async function assertPublisherPins(jwk, publishers, now) {
|
|
66
|
+
const fingerprint = await thumbprint(jwk);
|
|
67
|
+
if (!fingerprint) throw new AgentResolverError("request_signature_key_unknown", "Invalid public key", {});
|
|
68
|
+
const applicable = [];
|
|
69
|
+
const supplied = [];
|
|
70
|
+
for (const publisher of publishers) {
|
|
71
|
+
const inputPins = JSON.stringify(publisher.signingKeys);
|
|
72
|
+
supplied.push(inputPins);
|
|
73
|
+
const recent = refreshes.get(publisher.refresh)?.get(publisher.publisher);
|
|
74
|
+
let selected = recent?.snapshot !== void 0 && recent.inputPins === inputPins && now() - recent.attemptedAt < 30 ? recent.snapshot ?? void 0 : publisher.signingKeys;
|
|
75
|
+
if (!await matches(fingerprint, selected, now)) {
|
|
76
|
+
const refreshed = await refreshPin(publisher, now);
|
|
77
|
+
selected = refreshed ?? void 0;
|
|
78
|
+
if (!await matches(fingerprint, selected, now))
|
|
79
|
+
throw new AgentResolverError(
|
|
80
|
+
"request_signature_key_unknown",
|
|
81
|
+
"Key is outside the applicable publisher pin",
|
|
82
|
+
{}
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
applicable.push(selected);
|
|
86
|
+
}
|
|
87
|
+
const validity = await Promise.all(applicable.map((selected) => matchingValidity(fingerprint, selected)));
|
|
88
|
+
const checkedInputs = publishers.map((publisher) => JSON.stringify(publisher.signingKeys));
|
|
89
|
+
for (let i = 0; i < publishers.length; i++) {
|
|
90
|
+
if (supplied[i] !== checkedInputs[i]) {
|
|
91
|
+
validity.push(await matchingValidity(fingerprint, publishers[i].signingKeys));
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const checkedAt = now();
|
|
95
|
+
if (publishers.some((publisher, i) => JSON.stringify(publisher.signingKeys) !== checkedInputs[i]) || validity.some((bounds) => !bounds.some((until) => checkedAt < until))) {
|
|
96
|
+
throw new AgentResolverError("request_signature_key_unknown", "Key is outside the applicable publisher pin", {});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
export {
|
|
100
|
+
assertPublisherPins
|
|
101
|
+
};
|
|
@@ -30,3 +30,4 @@ export { createExpressVerifier, type ExpressLike, type ExpressMiddlewareOptions
|
|
|
30
30
|
export { finalizeResponseSignature, prepareResponseSignature, signResponse, type PreparedResponseSignature, type SignedResponse, type SignResponseOptions, } from './signer.mjs';
|
|
31
31
|
export { signResponseAsync } from './signer-async.mjs';
|
|
32
32
|
export { resolveAgent, ResolvedAgentJwksResolver, getAgentJwks, createAgentJwksSet, AgentResolverError, attackerInfluencedFields, ATTACKER_INFLUENCED, readBrandJsonUrl, readIdentityPosture, type AgentResolution, type AgentProtocol, type AuthorizedOperatorScope, type DelegatedOperatorAuthorizationContext, type AgentResolverErrorCode, type AgentResolverErrorDetail, type AgentEntry, type AgentJwksResult, type CapabilitiesWithBrandJsonUrl, type CreateAgentJwksSetOptions, type FetchCapabilitiesFn, type GetAgentJwksOptions, type IdentityKeyOriginPurpose, type IdentityKeyOrigins, type IdentityPosture, type ResolveAgentOptions, type ResolvedAgentJwksResolverOptions, type TraceStep, } from './agent-resolver/index.mjs';
|
|
33
|
+
export type { PublisherSigningKeyPin } from './publisher-pins.mjs';
|
|
@@ -30,3 +30,4 @@ export { createExpressVerifier, type ExpressLike, type ExpressMiddlewareOptions
|
|
|
30
30
|
export { finalizeResponseSignature, prepareResponseSignature, signResponse, type PreparedResponseSignature, type SignedResponse, type SignResponseOptions, } from './signer';
|
|
31
31
|
export { signResponseAsync } from './signer-async';
|
|
32
32
|
export { resolveAgent, ResolvedAgentJwksResolver, getAgentJwks, createAgentJwksSet, AgentResolverError, attackerInfluencedFields, ATTACKER_INFLUENCED, readBrandJsonUrl, readIdentityPosture, type AgentResolution, type AgentProtocol, type AuthorizedOperatorScope, type DelegatedOperatorAuthorizationContext, type AgentResolverErrorCode, type AgentResolverErrorDetail, type AgentEntry, type AgentJwksResult, type CapabilitiesWithBrandJsonUrl, type CreateAgentJwksSetOptions, type FetchCapabilitiesFn, type GetAgentJwksOptions, type IdentityKeyOriginPurpose, type IdentityKeyOrigins, type IdentityPosture, type ResolveAgentOptions, type ResolvedAgentJwksResolverOptions, type TraceStep, } from './agent-resolver';
|
|
33
|
+
export type { PublisherSigningKeyPin } from './publisher-pins';
|
|
@@ -60,6 +60,11 @@ export interface RevocationSnapshot {
|
|
|
60
60
|
export interface VerifiedSigner {
|
|
61
61
|
keyid: string;
|
|
62
62
|
agent_url?: string;
|
|
63
|
+
/** Exact operator record selected during verified agent discovery. */
|
|
64
|
+
operatorRecord?: {
|
|
65
|
+
url: string;
|
|
66
|
+
document: unknown;
|
|
67
|
+
};
|
|
63
68
|
verified_at: number;
|
|
64
69
|
}
|
|
65
70
|
/**
|
|
@@ -60,6 +60,11 @@ export interface RevocationSnapshot {
|
|
|
60
60
|
export interface VerifiedSigner {
|
|
61
61
|
keyid: string;
|
|
62
62
|
agent_url?: string;
|
|
63
|
+
/** Exact operator record selected during verified agent discovery. */
|
|
64
|
+
operatorRecord?: {
|
|
65
|
+
url: string;
|
|
66
|
+
document: unknown;
|
|
67
|
+
};
|
|
63
68
|
verified_at: number;
|
|
64
69
|
}
|
|
65
70
|
/**
|
|
@@ -32,7 +32,8 @@ var import_crypto = require('./crypto.js');
|
|
|
32
32
|
var import_webhook_auth_detection = require('./webhook-auth-detection.js');
|
|
33
33
|
var import_types = require('./types.js');
|
|
34
34
|
async function verifyRequestSignature(request, options) {
|
|
35
|
-
const
|
|
35
|
+
const currentTime = options.now ?? (() => Math.floor(Date.now() / 1e3));
|
|
36
|
+
const now = currentTime();
|
|
36
37
|
const pinnedBinaryEncoding = options.adcpVersion !== void 0 ? (0, import_content_digest.requestSigningEncodingForVersion)(options.adcpVersion) : void 0;
|
|
37
38
|
const sigInputHeader = (0, import_canonicalize.getHeaderValue)(request.headers, "Signature-Input");
|
|
38
39
|
const sigHeader = (0, import_canonicalize.getHeaderValue)(request.headers, "Signature");
|
|
@@ -119,14 +120,38 @@ async function verifyRequestSignature(request, options) {
|
|
|
119
120
|
const effectiveCapability = pinnedBinaryEncoding === "rfc8941-base64" ? { ...options.capability, covers_content_digest: "required" } : options.capability;
|
|
120
121
|
validateCoveredComponents(parsedInput.components, effectiveCapability, request);
|
|
121
122
|
let jwk;
|
|
123
|
+
let resolvedAgentUrl;
|
|
124
|
+
let authorizationValidUntil;
|
|
125
|
+
let operatorRecord;
|
|
122
126
|
try {
|
|
123
|
-
|
|
127
|
+
if (options.jwks.resolveWithMetadata) {
|
|
128
|
+
const resolution = await options.jwks.resolveWithMetadata(parsedInput.params.keyid);
|
|
129
|
+
if (resolution.legacyWebhookFallback)
|
|
130
|
+
throw new import_errors2.AgentResolverError(
|
|
131
|
+
"request_signature_brand_json_url_missing",
|
|
132
|
+
"Legacy discovery is limited to webhook verification",
|
|
133
|
+
{}
|
|
134
|
+
);
|
|
135
|
+
jwk = resolution.jwk;
|
|
136
|
+
resolvedAgentUrl = resolution.agentUrl;
|
|
137
|
+
operatorRecord = resolution.operatorRecord;
|
|
138
|
+
authorizationValidUntil = resolution.operatorAuthorizationValidUntil;
|
|
139
|
+
} else {
|
|
140
|
+
jwk = await options.jwks.resolve(parsedInput.params.keyid);
|
|
141
|
+
}
|
|
124
142
|
} catch (err) {
|
|
125
143
|
if (err instanceof import_errors2.AgentResolverError) {
|
|
126
|
-
throw new import_errors.RequestSignatureError(err.code, 7, err.message, err.detail);
|
|
144
|
+
throw new import_errors.RequestSignatureError(err.code, 7, err.message, err.detail, err.recovery);
|
|
127
145
|
}
|
|
128
146
|
throw err;
|
|
129
147
|
}
|
|
148
|
+
if (authorizationValidUntil !== void 0 && currentTime() >= authorizationValidUntil) {
|
|
149
|
+
throw new import_errors.RequestSignatureError(
|
|
150
|
+
"request_signature_brand_origin_mismatch",
|
|
151
|
+
7,
|
|
152
|
+
"Delegated operator authorization expired before verification"
|
|
153
|
+
);
|
|
154
|
+
}
|
|
130
155
|
if (!jwk) {
|
|
131
156
|
throw new import_errors.RequestSignatureError(
|
|
132
157
|
"request_signature_key_unknown",
|
|
@@ -222,6 +247,13 @@ async function verifyRequestSignature(request, options) {
|
|
|
222
247
|
throw err;
|
|
223
248
|
}
|
|
224
249
|
}
|
|
250
|
+
if (authorizationValidUntil !== void 0 && currentTime() >= authorizationValidUntil) {
|
|
251
|
+
throw new import_errors.RequestSignatureError(
|
|
252
|
+
"request_signature_brand_origin_mismatch",
|
|
253
|
+
7,
|
|
254
|
+
"Delegated operator authorization expired during verification"
|
|
255
|
+
);
|
|
256
|
+
}
|
|
225
257
|
const remaining = parsedInput.params.expires - now + import_types.CLOCK_SKEW_TOLERANCE_SECONDS;
|
|
226
258
|
const ttl = Math.max(remaining, import_types.MAX_SIGNATURE_WINDOW_SECONDS + import_types.CLOCK_SKEW_TOLERANCE_SECONDS);
|
|
227
259
|
const insertResult = await options.replayStore.insert(jwk.kid, replayScope, parsedInput.params.nonce, ttl, now);
|
|
@@ -239,8 +271,21 @@ async function verifyRequestSignature(request, options) {
|
|
|
239
271
|
`Per-keyid replay cache cap exceeded for keyid=${jwk.kid}`
|
|
240
272
|
);
|
|
241
273
|
}
|
|
242
|
-
|
|
243
|
-
|
|
274
|
+
if (authorizationValidUntil !== void 0 && currentTime() >= authorizationValidUntil) {
|
|
275
|
+
throw new import_errors.RequestSignatureError(
|
|
276
|
+
"request_signature_brand_origin_mismatch",
|
|
277
|
+
7,
|
|
278
|
+
"Delegated operator authorization expired before acceptance"
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
const agent_url = resolvedAgentUrl ?? options.agentUrlForKeyid?.(jwk.kid);
|
|
282
|
+
return {
|
|
283
|
+
status: "verified",
|
|
284
|
+
keyid: jwk.kid,
|
|
285
|
+
agent_url,
|
|
286
|
+
verified_at: now,
|
|
287
|
+
...operatorRecord !== void 0 && { operatorRecord }
|
|
288
|
+
};
|
|
244
289
|
}
|
|
245
290
|
function signatureEncodingCandidates(pinned, digestPolicy) {
|
|
246
291
|
if (!pinned) return ["legacy-base64url", "rfc8941-base64"];
|
|
@@ -25,7 +25,8 @@ import {
|
|
|
25
25
|
REQUEST_SIGNING_TAG
|
|
26
26
|
} from "./types.mjs";
|
|
27
27
|
async function verifyRequestSignature(request, options) {
|
|
28
|
-
const
|
|
28
|
+
const currentTime = options.now ?? (() => Math.floor(Date.now() / 1e3));
|
|
29
|
+
const now = currentTime();
|
|
29
30
|
const pinnedBinaryEncoding = options.adcpVersion !== void 0 ? requestSigningEncodingForVersion(options.adcpVersion) : void 0;
|
|
30
31
|
const sigInputHeader = getHeaderValue(request.headers, "Signature-Input");
|
|
31
32
|
const sigHeader = getHeaderValue(request.headers, "Signature");
|
|
@@ -112,14 +113,38 @@ async function verifyRequestSignature(request, options) {
|
|
|
112
113
|
const effectiveCapability = pinnedBinaryEncoding === "rfc8941-base64" ? { ...options.capability, covers_content_digest: "required" } : options.capability;
|
|
113
114
|
validateCoveredComponents(parsedInput.components, effectiveCapability, request);
|
|
114
115
|
let jwk;
|
|
116
|
+
let resolvedAgentUrl;
|
|
117
|
+
let authorizationValidUntil;
|
|
118
|
+
let operatorRecord;
|
|
115
119
|
try {
|
|
116
|
-
|
|
120
|
+
if (options.jwks.resolveWithMetadata) {
|
|
121
|
+
const resolution = await options.jwks.resolveWithMetadata(parsedInput.params.keyid);
|
|
122
|
+
if (resolution.legacyWebhookFallback)
|
|
123
|
+
throw new AgentResolverError(
|
|
124
|
+
"request_signature_brand_json_url_missing",
|
|
125
|
+
"Legacy discovery is limited to webhook verification",
|
|
126
|
+
{}
|
|
127
|
+
);
|
|
128
|
+
jwk = resolution.jwk;
|
|
129
|
+
resolvedAgentUrl = resolution.agentUrl;
|
|
130
|
+
operatorRecord = resolution.operatorRecord;
|
|
131
|
+
authorizationValidUntil = resolution.operatorAuthorizationValidUntil;
|
|
132
|
+
} else {
|
|
133
|
+
jwk = await options.jwks.resolve(parsedInput.params.keyid);
|
|
134
|
+
}
|
|
117
135
|
} catch (err) {
|
|
118
136
|
if (err instanceof AgentResolverError) {
|
|
119
|
-
throw new RequestSignatureError(err.code, 7, err.message, err.detail);
|
|
137
|
+
throw new RequestSignatureError(err.code, 7, err.message, err.detail, err.recovery);
|
|
120
138
|
}
|
|
121
139
|
throw err;
|
|
122
140
|
}
|
|
141
|
+
if (authorizationValidUntil !== void 0 && currentTime() >= authorizationValidUntil) {
|
|
142
|
+
throw new RequestSignatureError(
|
|
143
|
+
"request_signature_brand_origin_mismatch",
|
|
144
|
+
7,
|
|
145
|
+
"Delegated operator authorization expired before verification"
|
|
146
|
+
);
|
|
147
|
+
}
|
|
123
148
|
if (!jwk) {
|
|
124
149
|
throw new RequestSignatureError(
|
|
125
150
|
"request_signature_key_unknown",
|
|
@@ -215,6 +240,13 @@ async function verifyRequestSignature(request, options) {
|
|
|
215
240
|
throw err;
|
|
216
241
|
}
|
|
217
242
|
}
|
|
243
|
+
if (authorizationValidUntil !== void 0 && currentTime() >= authorizationValidUntil) {
|
|
244
|
+
throw new RequestSignatureError(
|
|
245
|
+
"request_signature_brand_origin_mismatch",
|
|
246
|
+
7,
|
|
247
|
+
"Delegated operator authorization expired during verification"
|
|
248
|
+
);
|
|
249
|
+
}
|
|
218
250
|
const remaining = parsedInput.params.expires - now + CLOCK_SKEW_TOLERANCE_SECONDS;
|
|
219
251
|
const ttl = Math.max(remaining, MAX_SIGNATURE_WINDOW_SECONDS + CLOCK_SKEW_TOLERANCE_SECONDS);
|
|
220
252
|
const insertResult = await options.replayStore.insert(jwk.kid, replayScope, parsedInput.params.nonce, ttl, now);
|
|
@@ -232,8 +264,21 @@ async function verifyRequestSignature(request, options) {
|
|
|
232
264
|
`Per-keyid replay cache cap exceeded for keyid=${jwk.kid}`
|
|
233
265
|
);
|
|
234
266
|
}
|
|
235
|
-
|
|
236
|
-
|
|
267
|
+
if (authorizationValidUntil !== void 0 && currentTime() >= authorizationValidUntil) {
|
|
268
|
+
throw new RequestSignatureError(
|
|
269
|
+
"request_signature_brand_origin_mismatch",
|
|
270
|
+
7,
|
|
271
|
+
"Delegated operator authorization expired before acceptance"
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
const agent_url = resolvedAgentUrl ?? options.agentUrlForKeyid?.(jwk.kid);
|
|
275
|
+
return {
|
|
276
|
+
status: "verified",
|
|
277
|
+
keyid: jwk.kid,
|
|
278
|
+
agent_url,
|
|
279
|
+
verified_at: now,
|
|
280
|
+
...operatorRecord !== void 0 && { operatorRecord }
|
|
281
|
+
};
|
|
237
282
|
}
|
|
238
283
|
function signatureEncodingCandidates(pinned, digestPolicy) {
|
|
239
284
|
if (!pinned) return ["legacy-base64url", "rfc8941-base64"];
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
* `content-type`, and `content-digest` — `content-digest` is unconditional
|
|
15
15
|
* for webhooks (vs policy-driven on requests) because every webhook
|
|
16
16
|
* carries a JSON body.
|
|
17
|
-
* - JWKS resolved via the
|
|
18
|
-
*
|
|
17
|
+
* - JWKS resolved via the capabilities-selected operator brand.json;
|
|
18
|
+
* publisher pins narrow its keys for applicable inventory.
|
|
19
19
|
*
|
|
20
20
|
* Checklist steps below mirror the 14-step shape in
|
|
21
21
|
* `docs/building/implementation/security.mdx#verifier-checklist-for-webhooks`
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
* match the security doc.
|
|
24
24
|
*/
|
|
25
25
|
import type { JwksResolver } from './jwks.mjs';
|
|
26
|
+
import { type PublisherSigningKeyPin } from './publisher-pins.mjs';
|
|
26
27
|
import { type ReplayStore } from './replay.mjs';
|
|
27
28
|
import { type RevocationStore } from './revocation.mjs';
|
|
28
29
|
export declare const WEBHOOK_SIGNING_TAG = "adcp/webhook-signing/v1";
|
|
@@ -42,6 +43,10 @@ export interface WebhookRequestLike {
|
|
|
42
43
|
export declare const WEBHOOK_MANDATORY_COMPONENTS: ReadonlyArray<string>;
|
|
43
44
|
export interface VerifyWebhookOptions {
|
|
44
45
|
jwks: JwksResolver;
|
|
46
|
+
/** Applicable publishers from the verifier's own media-buy record. */
|
|
47
|
+
publisherPins?: readonly PublisherSigningKeyPin[];
|
|
48
|
+
/** Log the underlying request_signature_* discovery cause locally. */
|
|
49
|
+
onKeyResolutionError?: (error: unknown) => void;
|
|
45
50
|
replayStore: ReplayStore;
|
|
46
51
|
revocationStore: RevocationStore;
|
|
47
52
|
/** Now in seconds since epoch. Defaults to `Date.now() / 1000`. */
|