@adcp/sdk 14.0.0 → 14.1.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 +31 -1
- package/dist/lib/core/SingleAgentClient.d.ts +31 -1
- package/dist/lib/core/SingleAgentClient.js +355 -35
- package/dist/lib/core/SingleAgentClient.mjs +365 -37
- package/dist/lib/core/TaskExecutor.js +2 -1
- package/dist/lib/core/TaskExecutor.mjs +2 -1
- 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 +65 -0
- package/dist/lib/core/buyer-account-registry.d.ts +65 -0
- package/dist/lib/core/buyer-account-registry.js +518 -0
- package/dist/lib/core/buyer-account-registry.mjs +494 -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 +5 -3
- package/dist/lib/index.d.ts +5 -3
- package/dist/lib/index.js +20 -0
- package/dist/lib/index.mjs +19 -0
- 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 +1 -1
- package/dist/lib/signing/agent-resolver/errors.d.ts +1 -1
- package/dist/lib/signing/agent-resolver/fetch-helpers.d.mts +2 -0
- package/dist/lib/signing/agent-resolver/fetch-helpers.d.ts +2 -0
- package/dist/lib/signing/agent-resolver/fetch-helpers.js +2 -1
- package/dist/lib/signing/agent-resolver/fetch-helpers.mjs +2 -1
- 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 +101 -132
- package/dist/lib/signing/agent-resolver/resolve-agent.mjs +102 -133
- 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 +27 -75
- package/dist/lib/signing/brand-jwks.d.ts +27 -75
- package/dist/lib/signing/brand-jwks.js +112 -182
- package/dist/lib/signing/brand-jwks.mjs +112 -182
- package/dist/lib/signing/errors.d.mts +3 -1
- package/dist/lib/signing/errors.d.ts +3 -1
- package/dist/lib/signing/errors.js +4 -1
- package/dist/lib/signing/errors.mjs +4 -1
- 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 +49 -4
- package/dist/lib/signing/verifier.mjs +49 -4
- 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 +43 -3
- 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/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/wholesale-feed-sync/sync.d.mts +1 -0
- package/dist/lib/wholesale-feed-sync/sync.d.ts +1 -0
- package/dist/lib/wholesale-feed-sync/sync.js +92 -21
- package/dist/lib/wholesale-feed-sync/sync.mjs +92 -21
- package/dist/lib/wholesale-feed-sync/types.d.mts +4 -3
- package/dist/lib/wholesale-feed-sync/types.d.ts +4 -3
- 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/FIRST-CALL-TO-A-SELLER.md +104 -0
- package/docs/guides/SIGNING-GUIDE.md +16 -7
- package/docs/guides/account-resolution.md +86 -0
- package/docs/llms.txt +3 -2
- 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 +123 -0
- package/docs/recipes/verifying-inbound-webhooks.md +56 -15
- package/package.json +2 -2
|
@@ -2,7 +2,8 @@ import { parse as parseTld } from "tldts";
|
|
|
2
2
|
import { createA2AClient, createMCPClient } from "../../protocols/index.mjs";
|
|
3
3
|
import { isDevelopmentBrandDomain } from "../../brand/domain.mjs";
|
|
4
4
|
import { readBrandJsonUrl, readIdentityPosture } from "./capabilities-types.mjs";
|
|
5
|
-
import {
|
|
5
|
+
import { checkDelegatedOperatorAuthorization } from "./operator-authorization.mjs";
|
|
6
|
+
import { fetchLegacyBrandJson } from "./legacy-brand.mjs";
|
|
6
7
|
import {
|
|
7
8
|
checkOriginConsistency,
|
|
8
9
|
checkRequiredOrigins,
|
|
@@ -12,7 +13,7 @@ import { AgentResolverError } from "./errors.mjs";
|
|
|
12
13
|
import { eTldPlusOne, sameEtldPlusOne } from "./etld.mjs";
|
|
13
14
|
import { MAX_BRAND_JSON_BYTES, MAX_JWKS_BYTES, safeFetchJson, SafeFetchError } from "./fetch-helpers.mjs";
|
|
14
15
|
import { unwrapProtocolResponse } from "../protocol-response.mjs";
|
|
15
|
-
import { selectAgentByUrl, AgentSelectorError } from "./select-agent.mjs";
|
|
16
|
+
import { selectAgentByUrl, agentJwksUri, AgentSelectorError, canonicalAgentUrl } from "./select-agent.mjs";
|
|
16
17
|
const DEFAULT_TIMEOUT_MS = 1e4;
|
|
17
18
|
async function resolveAgent(agentUrl, options = {}) {
|
|
18
19
|
const trace = [];
|
|
@@ -50,7 +51,10 @@ async function resolveAgent(agentUrl, options = {}) {
|
|
|
50
51
|
["agent_url"]
|
|
51
52
|
);
|
|
52
53
|
}
|
|
53
|
-
|
|
54
|
+
let brandJsonUrl = readBrandJsonUrl(capabilitiesPayload);
|
|
55
|
+
const identity = capabilitiesPayload?.identity;
|
|
56
|
+
const legacyFallback = options.legacyWebhookFallback === true && identity?.brand_json_url === void 0 && permitsLegacyWebhookDiscovery(capabilitiesPayload);
|
|
57
|
+
if (legacyFallback) brandJsonUrl = `${new URL(agentUrl).origin}/.well-known/brand.json`;
|
|
54
58
|
const identityPosture = readIdentityPosture(capabilitiesPayload);
|
|
55
59
|
const acceptsScheme = (url) => {
|
|
56
60
|
if (!url) return false;
|
|
@@ -68,24 +72,43 @@ async function resolveAgent(agentUrl, options = {}) {
|
|
|
68
72
|
["agent_url"]
|
|
69
73
|
);
|
|
70
74
|
}
|
|
75
|
+
if (!legacyFallback && options.expectedBrandJsonUrl !== void 0 && canonicalAgentUrl(brandJsonUrl) !== canonicalAgentUrl(options.expectedBrandJsonUrl)) {
|
|
76
|
+
throw new AgentResolverError(
|
|
77
|
+
"request_signature_brand_origin_mismatch",
|
|
78
|
+
"Cached operator mapping disagrees with identity.brand_json_url",
|
|
79
|
+
{ agent_url: agentUrl },
|
|
80
|
+
["agent_url"]
|
|
81
|
+
);
|
|
82
|
+
}
|
|
71
83
|
pushTrace(trace, { step: 2, name: "read_brand_json_url", ok: true, detail: { brand_json_url: brandJsonUrl } });
|
|
72
|
-
let agentEtld1;
|
|
73
|
-
let brandEtld1;
|
|
74
|
-
let sameOrigin;
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
84
|
+
let agentEtld1 = "";
|
|
85
|
+
let brandEtld1 = "";
|
|
86
|
+
let sameOrigin = true;
|
|
87
|
+
if (!legacyFallback) {
|
|
88
|
+
try {
|
|
89
|
+
agentEtld1 = eTldPlusOne(agentUrl);
|
|
90
|
+
brandEtld1 = eTldPlusOne(brandJsonUrl);
|
|
91
|
+
sameOrigin = sameEtldPlusOne(agentUrl, brandJsonUrl);
|
|
92
|
+
} catch {
|
|
93
|
+
if (allowPrivateIp) {
|
|
94
|
+
const agentHost = new URL(agentUrl).hostname;
|
|
95
|
+
const brandHost = new URL(brandJsonUrl).hostname;
|
|
96
|
+
const agentIsExplicitDevelopmentHost = agentHost === "localhost" || isDevelopmentBrandDomain(agentHost) || parseTld(agentHost, { extractHostname: false }).isIp;
|
|
97
|
+
const brandIsExplicitDevelopmentHost = brandHost === "localhost" || isDevelopmentBrandDomain(brandHost) || parseTld(brandHost, { extractHostname: false }).isIp;
|
|
98
|
+
if (agentIsExplicitDevelopmentHost && brandIsExplicitDevelopmentHost) {
|
|
99
|
+
agentEtld1 = agentHost;
|
|
100
|
+
brandEtld1 = brandHost;
|
|
101
|
+
sameOrigin = agentHost === brandHost;
|
|
102
|
+
} else {
|
|
103
|
+
const detail = { agent_url: agentUrl, brand_json_url: brandJsonUrl };
|
|
104
|
+
pushTrace(trace, { step: 3, name: "etld1_binding", ok: false, detail });
|
|
105
|
+
throw new AgentResolverError(
|
|
106
|
+
"request_signature_brand_origin_mismatch",
|
|
107
|
+
`Cannot compute eTLD+1 for agent or brand.json host`,
|
|
108
|
+
detail,
|
|
109
|
+
["agent_url", "brand_json_url"]
|
|
110
|
+
);
|
|
111
|
+
}
|
|
89
112
|
} else {
|
|
90
113
|
const detail = { agent_url: agentUrl, brand_json_url: brandJsonUrl };
|
|
91
114
|
pushTrace(trace, { step: 3, name: "etld1_binding", ok: false, detail });
|
|
@@ -96,25 +119,24 @@ async function resolveAgent(agentUrl, options = {}) {
|
|
|
96
119
|
["agent_url", "brand_json_url"]
|
|
97
120
|
);
|
|
98
121
|
}
|
|
99
|
-
} else {
|
|
100
|
-
const detail = { agent_url: agentUrl, brand_json_url: brandJsonUrl };
|
|
101
|
-
pushTrace(trace, { step: 3, name: "etld1_binding", ok: false, detail });
|
|
102
|
-
throw new AgentResolverError(
|
|
103
|
-
"request_signature_brand_origin_mismatch",
|
|
104
|
-
`Cannot compute eTLD+1 for agent or brand.json host`,
|
|
105
|
-
detail,
|
|
106
|
-
["agent_url", "brand_json_url"]
|
|
107
|
-
);
|
|
108
122
|
}
|
|
109
123
|
}
|
|
110
124
|
let brandJson;
|
|
111
125
|
let brandJsonFetchedAt;
|
|
126
|
+
let brandJsonCacheControl;
|
|
112
127
|
try {
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
})
|
|
128
|
+
const fetchOptions = { allowPrivateIp, timeoutMs, maxBodyBytes: brandJsonCap, lookup: options.lookup };
|
|
129
|
+
const fetched = legacyFallback ? await fetchLegacyBrandJson(agentUrl, fetchOptions) : await safeFetchJson(brandJsonUrl, "brand.json", fetchOptions);
|
|
130
|
+
if (legacyFallback && "url" in fetched) brandJsonUrl = fetched.url;
|
|
131
|
+
brandJsonCacheControl = fetched.headers["cache-control"];
|
|
132
|
+
if (legacyFallback && options.expectedBrandJsonUrl !== void 0 && canonicalAgentUrl(brandJsonUrl) !== canonicalAgentUrl(options.expectedBrandJsonUrl) && canonicalAgentUrl(`${new URL(agentUrl).origin}/.well-known/brand.json`) !== canonicalAgentUrl(options.expectedBrandJsonUrl)) {
|
|
133
|
+
throw new AgentResolverError(
|
|
134
|
+
"request_signature_brand_origin_mismatch",
|
|
135
|
+
"Cached mapping disagrees with legacy discovery",
|
|
136
|
+
{ agent_url: agentUrl },
|
|
137
|
+
["agent_url"]
|
|
138
|
+
);
|
|
139
|
+
}
|
|
118
140
|
brandJson = fetched.body;
|
|
119
141
|
brandJsonFetchedAt = fetched.fetchedAt;
|
|
120
142
|
pushTrace(trace, {
|
|
@@ -125,6 +147,7 @@ async function resolveAgent(agentUrl, options = {}) {
|
|
|
125
147
|
url: brandJsonUrl
|
|
126
148
|
});
|
|
127
149
|
} catch (err) {
|
|
150
|
+
if (err instanceof AgentResolverError) throw err;
|
|
128
151
|
if (err instanceof SafeFetchError && /strict-JSON parse/.test(err.message)) {
|
|
129
152
|
const detail2 = {
|
|
130
153
|
brand_json_url: brandJsonUrl,
|
|
@@ -168,7 +191,17 @@ async function resolveAgent(agentUrl, options = {}) {
|
|
|
168
191
|
["agent_url"]
|
|
169
192
|
);
|
|
170
193
|
}
|
|
171
|
-
|
|
194
|
+
if (options.requiredOperatorBrand !== void 0 || options.requiredOperatorScope !== void 0 || options.requiredOperatorCountry !== void 0) {
|
|
195
|
+
const authorization = checkDelegatedOperatorAuthorization(brandJson, agentEtld1, now(), options);
|
|
196
|
+
if (!authorization)
|
|
197
|
+
throw new AgentResolverError(
|
|
198
|
+
"request_signature_brand_origin_mismatch",
|
|
199
|
+
"Delegated operator does not satisfy the receiver account authorization policy",
|
|
200
|
+
{ agent_url: agentUrl },
|
|
201
|
+
["agent_url"]
|
|
202
|
+
);
|
|
203
|
+
operatorAuthorizationValidUntil = authorization.validUntil;
|
|
204
|
+
}
|
|
172
205
|
pushTrace(trace, {
|
|
173
206
|
step: 3,
|
|
174
207
|
name: "etld1_binding",
|
|
@@ -185,7 +218,7 @@ async function resolveAgent(agentUrl, options = {}) {
|
|
|
185
218
|
}
|
|
186
219
|
let agentEntry;
|
|
187
220
|
try {
|
|
188
|
-
agentEntry = selectAgentByUrl(brandJson, agentUrl);
|
|
221
|
+
agentEntry = selectAgentByUrl(brandJson, agentUrl, options);
|
|
189
222
|
pushTrace(trace, { step: 5, name: "select_agent", ok: true, detail: { url: agentEntry.url } });
|
|
190
223
|
} catch (err) {
|
|
191
224
|
if (err instanceof AgentSelectorError) {
|
|
@@ -208,17 +241,19 @@ async function resolveAgent(agentUrl, options = {}) {
|
|
|
208
241
|
pushTrace(trace, { step: 5, name: "select_agent", ok: false, detail });
|
|
209
242
|
throw new AgentResolverError(code, err.message, detail, attacker);
|
|
210
243
|
}
|
|
211
|
-
throw
|
|
244
|
+
throw new AgentResolverError(
|
|
245
|
+
"request_signature_brand_json_malformed",
|
|
246
|
+
"Matched agent has an invalid URL or JWKS source",
|
|
247
|
+
{ brand_json_url: brandJsonUrl },
|
|
248
|
+
["brand_json_url"]
|
|
249
|
+
);
|
|
212
250
|
}
|
|
213
|
-
const
|
|
214
|
-
const jwksUriFromEntry = typeof declaredJwksUri === "string" && acceptsScheme(declaredJwksUri) ? declaredJwksUri : void 0;
|
|
215
|
-
const jwksUri = jwksUriFromEntry ?? `${originOf(agentUrl)}/.well-known/jwks.json`;
|
|
251
|
+
const jwksUri = agentJwksUri(agentEntry);
|
|
216
252
|
pushTrace(trace, { step: 6, name: "resolve_jwks_uri", ok: true, detail: { jwks_uri: jwksUri } });
|
|
217
|
-
const consistencyResults = runConsistencyChecks({
|
|
253
|
+
const consistencyResults = legacyFallback ? [] : runConsistencyChecks({
|
|
218
254
|
capabilitiesPayload,
|
|
219
255
|
identityPosture,
|
|
220
256
|
jwksUri,
|
|
221
|
-
publisherPinnedWebhookSigning: options.publisherPinned?.webhook_signing === true,
|
|
222
257
|
extraDeclaredPurposes: options.declaredPurposes ?? []
|
|
223
258
|
});
|
|
224
259
|
const failedConsistency = consistencyResults.filter((r) => r.ok === false);
|
|
@@ -251,7 +286,8 @@ async function resolveAgent(agentUrl, options = {}) {
|
|
|
251
286
|
const fetched = await safeFetchJson(jwksUri, "jwks", {
|
|
252
287
|
allowPrivateIp,
|
|
253
288
|
timeoutMs,
|
|
254
|
-
maxBodyBytes: jwksCap
|
|
289
|
+
maxBodyBytes: jwksCap,
|
|
290
|
+
lookup: options.lookup
|
|
255
291
|
});
|
|
256
292
|
if (!fetched.body || typeof fetched.body !== "object" || !Array.isArray(fetched.body.keys)) {
|
|
257
293
|
throw new SafeFetchError("jwks", "fetch_failed", "JWKS document has no keys[] array");
|
|
@@ -288,8 +324,11 @@ async function resolveAgent(agentUrl, options = {}) {
|
|
|
288
324
|
);
|
|
289
325
|
}
|
|
290
326
|
return {
|
|
291
|
-
agentUrl,
|
|
327
|
+
agentUrl: canonicalAgentUrl(agentEntry.url),
|
|
292
328
|
brandJsonUrl,
|
|
329
|
+
brandJson,
|
|
330
|
+
...legacyFallback && { legacyWebhookFallback: true },
|
|
331
|
+
...brandJsonCacheControl !== void 0 && { brandJsonCacheControl },
|
|
293
332
|
agentEntry,
|
|
294
333
|
jwksUri,
|
|
295
334
|
jwks,
|
|
@@ -316,95 +355,14 @@ function defaultFetchCapabilities(agentUrl, protocol) {
|
|
|
316
355
|
return client.callTool("get_adcp_capabilities", {});
|
|
317
356
|
};
|
|
318
357
|
}
|
|
319
|
-
|
|
320
|
-
"all",
|
|
321
|
-
"media_buying",
|
|
322
|
-
"creative_generation",
|
|
323
|
-
"rights_clearance",
|
|
324
|
-
"governance",
|
|
325
|
-
"measurement",
|
|
326
|
-
"agent_operations"
|
|
327
|
-
]);
|
|
328
|
-
const AUTHORIZED_OPERATOR_DOMAIN = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/;
|
|
329
|
-
function findAuthorizedOperator(brandJson, agentEtld1, now, options) {
|
|
358
|
+
function findAuthorizedOperator(brandJson, agentEtld1, _now, _options) {
|
|
330
359
|
if (!brandJson || typeof brandJson !== "object") return void 0;
|
|
331
|
-
const
|
|
332
|
-
if (!Array.isArray(
|
|
333
|
-
|
|
334
|
-
let matched = false;
|
|
335
|
-
for (const op of operators) {
|
|
336
|
-
if (!op || typeof op !== "object" || Array.isArray(op)) continue;
|
|
337
|
-
const candidate = op;
|
|
338
|
-
const domain = candidate.domain;
|
|
339
|
-
const brands = candidate.brands;
|
|
340
|
-
if (typeof domain !== "string" || !AUTHORIZED_OPERATOR_DOMAIN.test(domain) || !isValidBrandGrant(brands)) {
|
|
341
|
-
continue;
|
|
342
|
-
}
|
|
343
|
-
try {
|
|
344
|
-
if (eTldPlusOne(domain) !== agentEtld1) continue;
|
|
345
|
-
} catch {
|
|
346
|
-
if (!(options.allowPrivateIp && isDevelopmentBrandDomain(domain) && domain === agentEtld1)) {
|
|
347
|
-
continue;
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
const scopes = candidate.scopes;
|
|
351
|
-
if (scopes !== void 0 && !isValidScopeGrant(scopes)) continue;
|
|
352
|
-
const countries = candidate.countries;
|
|
353
|
-
if (countries !== void 0 && !isValidCountryGrant(countries)) continue;
|
|
354
|
-
const validFrom = parseOptionalRfc3339(candidate.valid_from);
|
|
355
|
-
const validUntil = parseOptionalRfc3339(candidate.valid_until);
|
|
356
|
-
if (validFrom === null || validUntil === null) continue;
|
|
357
|
-
if (validFrom !== void 0 && validUntil !== void 0 && validFrom >= validUntil) continue;
|
|
358
|
-
if (validFrom !== void 0 && now < validFrom) continue;
|
|
359
|
-
if (validUntil !== void 0 && now >= validUntil) continue;
|
|
360
|
-
if (!matchesGrant(brands, options.requiredOperatorBrand, "*")) continue;
|
|
361
|
-
if (scopes !== void 0 && !matchesGrant(scopes, options.requiredOperatorScope, "all")) continue;
|
|
362
|
-
if (countries !== void 0 && !matchesGrant(countries, options.requiredOperatorCountry)) continue;
|
|
363
|
-
matched = true;
|
|
364
|
-
if (validUntil === void 0) return {};
|
|
365
|
-
latestValidUntil = Math.max(latestValidUntil ?? Number.NEGATIVE_INFINITY, validUntil);
|
|
360
|
+
const obj = brandJson;
|
|
361
|
+
if (!obj.house || typeof obj.house !== "object" || Array.isArray(obj.house) || !Array.isArray(obj.brands)) {
|
|
362
|
+
return void 0;
|
|
366
363
|
}
|
|
367
|
-
|
|
368
|
-
}
|
|
369
|
-
function isValidBrandGrant(value) {
|
|
370
|
-
return Array.isArray(value) && value.length > 0 && value.every((item) => typeof item === "string" && (item === "*" || /^[a-z0-9_]+$/.test(item)));
|
|
371
|
-
}
|
|
372
|
-
function isValidScopeGrant(value) {
|
|
373
|
-
return Array.isArray(value) && value.length > 0 && new Set(value).size === value.length && value.every((item) => typeof item === "string" && AUTHORIZED_OPERATOR_SCOPES.has(item));
|
|
374
|
-
}
|
|
375
|
-
function isValidCountryGrant(value) {
|
|
376
|
-
return Array.isArray(value) && value.every((item) => typeof item === "string" && /^[A-Z]{2}$/.test(item));
|
|
377
|
-
}
|
|
378
|
-
function matchesGrant(grants, required, wildcard) {
|
|
379
|
-
if (wildcard !== void 0 && grants.includes(wildcard)) return true;
|
|
380
|
-
return required !== void 0 && grants.includes(required);
|
|
381
|
-
}
|
|
382
|
-
function parseOptionalRfc3339(value) {
|
|
383
|
-
if (value === void 0) return void 0;
|
|
384
|
-
if (typeof value !== "string") return null;
|
|
385
|
-
const match = /^(\d{4})-(\d{2})-(\d{2})[Tt](\d{2}):(\d{2}):(\d{2})(?:\.(\d+))?([Zz]|([+-])(\d{2}):(\d{2}))$/.exec(
|
|
386
|
-
value
|
|
387
|
-
);
|
|
388
|
-
if (!match) return null;
|
|
389
|
-
const year = Number(match[1]);
|
|
390
|
-
const month = Number(match[2]);
|
|
391
|
-
const day = Number(match[3]);
|
|
392
|
-
const hour = Number(match[4]);
|
|
393
|
-
const minute = Number(match[5]);
|
|
394
|
-
const second = Number(match[6]);
|
|
395
|
-
const offsetHour = match[10] === void 0 ? 0 : Number(match[10]);
|
|
396
|
-
const offsetMinute = match[11] === void 0 ? 0 : Number(match[11]);
|
|
397
|
-
if (month < 1 || month > 12 || day < 1 || day > daysInMonth(year, month) || hour > 23 || minute > 59 || second > 59 || offsetHour > 23 || offsetMinute > 59) {
|
|
398
|
-
return null;
|
|
399
|
-
}
|
|
400
|
-
const parsed = Date.parse(value) / 1e3;
|
|
401
|
-
return Number.isFinite(parsed) ? parsed : null;
|
|
402
|
-
}
|
|
403
|
-
function daysInMonth(year, month) {
|
|
404
|
-
return new Date(Date.UTC(year, month, 0)).getUTCDate();
|
|
405
|
-
}
|
|
406
|
-
function originOf(url) {
|
|
407
|
-
return canonicalizeOrigin(url);
|
|
364
|
+
if (!Array.isArray(obj.authorized_operators)) return void 0;
|
|
365
|
+
return obj.authorized_operators.some((op) => op && typeof op === "object" && op.domain === agentEtld1) ? {} : void 0;
|
|
408
366
|
}
|
|
409
367
|
function pushTrace(trace, step) {
|
|
410
368
|
trace.push(step);
|
|
@@ -426,14 +384,25 @@ function runConsistencyChecks(args) {
|
|
|
426
384
|
const result = checkOriginConsistency({
|
|
427
385
|
purpose,
|
|
428
386
|
declaredOrigin,
|
|
429
|
-
resolvedJwksUri: args.jwksUri
|
|
430
|
-
publisherPinned: purpose === "webhook_signing" && args.publisherPinnedWebhookSigning
|
|
387
|
+
resolvedJwksUri: args.jwksUri
|
|
431
388
|
});
|
|
432
389
|
if (result.ok === false) results.push(result);
|
|
433
390
|
}
|
|
434
391
|
}
|
|
435
392
|
return results;
|
|
436
393
|
}
|
|
394
|
+
function permitsLegacyWebhookDiscovery(payload) {
|
|
395
|
+
if (!payload || typeof payload !== "object") return true;
|
|
396
|
+
const caps = payload;
|
|
397
|
+
const major = (value) => typeof value === "string" ? Number(/^(\d+)\./.exec(value)?.[1]) : value;
|
|
398
|
+
if (Number(major(caps.adcp_version)) >= 4 || Array.isArray(caps.adcp?.supported_versions) && caps.adcp.supported_versions.some((v) => Number(major(v)) >= 4) || Array.isArray(caps.adcp?.major_versions) && caps.adcp.major_versions.some((v) => typeof v === "number" && v >= 4))
|
|
399
|
+
return false;
|
|
400
|
+
if (caps.adcp_version !== void 0) return typeof caps.adcp_version === "string" && /^3\./.test(caps.adcp_version);
|
|
401
|
+
if (Array.isArray(caps.adcp?.supported_versions))
|
|
402
|
+
return caps.adcp.supported_versions.some((v) => typeof v === "string" && /^3\./.test(v));
|
|
403
|
+
if (Array.isArray(caps.adcp?.major_versions)) return caps.adcp.major_versions.includes(3);
|
|
404
|
+
return true;
|
|
405
|
+
}
|
|
437
406
|
export {
|
|
438
407
|
resolveAgent
|
|
439
408
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { AdcpJsonWebKey } from '../types.mjs';
|
|
2
2
|
import type { JwksResolution, JwksResolver } from '../jwks.mjs';
|
|
3
3
|
import { type AgentProtocol, type AgentResolution, type ResolveAgentOptions } from './resolve-agent.mjs';
|
|
4
|
-
export interface ResolvedAgentJwksResolverOptions extends Pick<ResolveAgentOptions, 'fetchCapabilities' | 'allowPrivateIp' | 'bodyCaps' | 'timeoutMs' | 'now' | 'requiredOperatorBrand' | 'requiredOperatorScope' | 'requiredOperatorCountry'> {
|
|
4
|
+
export interface ResolvedAgentJwksResolverOptions extends Pick<ResolveAgentOptions, 'fetchCapabilities' | 'agentType' | 'agentId' | 'expectedBrandJsonUrl' | 'legacyWebhookFallback' | 'lookup' | 'allowPrivateIp' | 'bodyCaps' | 'timeoutMs' | 'now' | 'requiredOperatorBrand' | 'requiredOperatorScope' | 'requiredOperatorCountry'> {
|
|
5
5
|
/** Positive cache lifetime in seconds. Defaults to 300. */
|
|
6
6
|
cacheTtlSeconds?: number;
|
|
7
7
|
/** Cooldown before an unknown kid can force another discovery. Defaults to 30. */
|
|
@@ -18,14 +18,20 @@ export declare class ResolvedAgentJwksResolver implements JwksResolver {
|
|
|
18
18
|
private readonly cacheTtlSeconds;
|
|
19
19
|
private readonly unknownKidCooldownSeconds;
|
|
20
20
|
private readonly resolveAgentFn;
|
|
21
|
+
private canonicalAgentUrl?;
|
|
22
|
+
private legacyWebhookFallback?;
|
|
23
|
+
private operatorRecord?;
|
|
24
|
+
get resolvedAgentUrl(): string | undefined;
|
|
21
25
|
private keys;
|
|
22
26
|
private expiresAt;
|
|
23
27
|
private operatorAuthorizationValidUntil?;
|
|
24
28
|
private lastUnknownKidRefresh;
|
|
29
|
+
private lastDiscoveryAttemptAt;
|
|
25
30
|
private inFlight?;
|
|
26
31
|
constructor(agentUrl: string, protocol: AgentProtocol, options?: ResolvedAgentJwksResolverOptions);
|
|
27
32
|
resolve(keyid: string): Promise<AdcpJsonWebKey | null>;
|
|
28
33
|
resolveWithMetadata(keyid: string): Promise<JwksResolution>;
|
|
34
|
+
forceRefresh(): Promise<void>;
|
|
29
35
|
private resolution;
|
|
30
36
|
private refresh;
|
|
31
37
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { AdcpJsonWebKey } from '../types';
|
|
2
2
|
import type { JwksResolution, JwksResolver } from '../jwks';
|
|
3
3
|
import { type AgentProtocol, type AgentResolution, type ResolveAgentOptions } from './resolve-agent';
|
|
4
|
-
export interface ResolvedAgentJwksResolverOptions extends Pick<ResolveAgentOptions, 'fetchCapabilities' | 'allowPrivateIp' | 'bodyCaps' | 'timeoutMs' | 'now' | 'requiredOperatorBrand' | 'requiredOperatorScope' | 'requiredOperatorCountry'> {
|
|
4
|
+
export interface ResolvedAgentJwksResolverOptions extends Pick<ResolveAgentOptions, 'fetchCapabilities' | 'agentType' | 'agentId' | 'expectedBrandJsonUrl' | 'legacyWebhookFallback' | 'lookup' | 'allowPrivateIp' | 'bodyCaps' | 'timeoutMs' | 'now' | 'requiredOperatorBrand' | 'requiredOperatorScope' | 'requiredOperatorCountry'> {
|
|
5
5
|
/** Positive cache lifetime in seconds. Defaults to 300. */
|
|
6
6
|
cacheTtlSeconds?: number;
|
|
7
7
|
/** Cooldown before an unknown kid can force another discovery. Defaults to 30. */
|
|
@@ -18,14 +18,20 @@ export declare class ResolvedAgentJwksResolver implements JwksResolver {
|
|
|
18
18
|
private readonly cacheTtlSeconds;
|
|
19
19
|
private readonly unknownKidCooldownSeconds;
|
|
20
20
|
private readonly resolveAgentFn;
|
|
21
|
+
private canonicalAgentUrl?;
|
|
22
|
+
private legacyWebhookFallback?;
|
|
23
|
+
private operatorRecord?;
|
|
24
|
+
get resolvedAgentUrl(): string | undefined;
|
|
21
25
|
private keys;
|
|
22
26
|
private expiresAt;
|
|
23
27
|
private operatorAuthorizationValidUntil?;
|
|
24
28
|
private lastUnknownKidRefresh;
|
|
29
|
+
private lastDiscoveryAttemptAt;
|
|
25
30
|
private inFlight?;
|
|
26
31
|
constructor(agentUrl: string, protocol: AgentProtocol, options?: ResolvedAgentJwksResolverOptions);
|
|
27
32
|
resolve(keyid: string): Promise<AdcpJsonWebKey | null>;
|
|
28
33
|
resolveWithMetadata(keyid: string): Promise<JwksResolution>;
|
|
34
|
+
forceRefresh(): Promise<void>;
|
|
29
35
|
private resolution;
|
|
30
36
|
private refresh;
|
|
31
37
|
}
|
|
@@ -46,10 +46,17 @@ class ResolvedAgentJwksResolver {
|
|
|
46
46
|
cacheTtlSeconds;
|
|
47
47
|
unknownKidCooldownSeconds;
|
|
48
48
|
resolveAgentFn;
|
|
49
|
+
canonicalAgentUrl;
|
|
50
|
+
legacyWebhookFallback;
|
|
51
|
+
operatorRecord;
|
|
52
|
+
get resolvedAgentUrl() {
|
|
53
|
+
return this.canonicalAgentUrl;
|
|
54
|
+
}
|
|
49
55
|
keys = /* @__PURE__ */ new Map();
|
|
50
56
|
expiresAt = 0;
|
|
51
57
|
operatorAuthorizationValidUntil;
|
|
52
58
|
lastUnknownKidRefresh = Number.NEGATIVE_INFINITY;
|
|
59
|
+
lastDiscoveryAttemptAt = Number.NEGATIVE_INFINITY;
|
|
53
60
|
inFlight;
|
|
54
61
|
async resolve(keyid) {
|
|
55
62
|
return (await this.resolveWithMetadata(keyid)).jwk;
|
|
@@ -58,6 +65,15 @@ class ResolvedAgentJwksResolver {
|
|
|
58
65
|
const now = this.now();
|
|
59
66
|
let refreshed = false;
|
|
60
67
|
if (now >= this.expiresAt) {
|
|
68
|
+
if (!this.inFlight && now - this.lastDiscoveryAttemptAt < 30) {
|
|
69
|
+
const expiredAuthorization = this.operatorAuthorizationValidUntil !== void 0 && now >= this.operatorAuthorizationValidUntil;
|
|
70
|
+
throw new import_errors.AgentResolverError(
|
|
71
|
+
expiredAuthorization ? "request_signature_brand_origin_mismatch" : "request_signature_brand_json_unreachable",
|
|
72
|
+
"Expired operator mapping cannot be used during the discovery cooldown",
|
|
73
|
+
{ agent_url: this.agentUrl },
|
|
74
|
+
["agent_url"]
|
|
75
|
+
);
|
|
76
|
+
}
|
|
61
77
|
await this.refresh();
|
|
62
78
|
refreshed = true;
|
|
63
79
|
}
|
|
@@ -67,14 +83,22 @@ class ResolvedAgentJwksResolver {
|
|
|
67
83
|
this.lastUnknownKidRefresh = now;
|
|
68
84
|
return this.resolution(null);
|
|
69
85
|
}
|
|
70
|
-
if (now - this.lastUnknownKidRefresh
|
|
86
|
+
if (now - Math.max(this.lastUnknownKidRefresh, this.lastDiscoveryAttemptAt) < Math.max(30, this.unknownKidCooldownSeconds))
|
|
87
|
+
return this.resolution(null);
|
|
71
88
|
await this.refresh();
|
|
72
89
|
this.lastUnknownKidRefresh = now;
|
|
73
90
|
return this.resolution(this.keys.get(keyid) ?? null);
|
|
74
91
|
}
|
|
92
|
+
async forceRefresh() {
|
|
93
|
+
this.expiresAt = 0;
|
|
94
|
+
await this.refresh();
|
|
95
|
+
}
|
|
75
96
|
resolution(jwk) {
|
|
76
97
|
return {
|
|
77
98
|
jwk,
|
|
99
|
+
...this.operatorRecord !== void 0 && { operatorRecord: this.operatorRecord },
|
|
100
|
+
...this.legacyWebhookFallback && { legacyWebhookFallback: true },
|
|
101
|
+
...this.resolvedAgentUrl !== void 0 && { agentUrl: this.resolvedAgentUrl },
|
|
78
102
|
...this.operatorAuthorizationValidUntil !== void 0 && {
|
|
79
103
|
operatorAuthorizationValidUntil: this.operatorAuthorizationValidUntil
|
|
80
104
|
}
|
|
@@ -82,23 +106,11 @@ class ResolvedAgentJwksResolver {
|
|
|
82
106
|
}
|
|
83
107
|
async refresh() {
|
|
84
108
|
if (this.inFlight) return this.inFlight;
|
|
109
|
+
this.lastDiscoveryAttemptAt = this.now();
|
|
85
110
|
this.inFlight = (async () => {
|
|
86
111
|
const resolution = await this.resolveAgentFn(this.agentUrl, {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
...this.options.allowPrivateIp !== void 0 && { allowPrivateIp: this.options.allowPrivateIp },
|
|
90
|
-
...this.options.bodyCaps && { bodyCaps: this.options.bodyCaps },
|
|
91
|
-
...this.options.timeoutMs !== void 0 && { timeoutMs: this.options.timeoutMs },
|
|
92
|
-
...this.options.now && { now: this.options.now },
|
|
93
|
-
...this.options.requiredOperatorBrand !== void 0 && {
|
|
94
|
-
requiredOperatorBrand: this.options.requiredOperatorBrand
|
|
95
|
-
},
|
|
96
|
-
...this.options.requiredOperatorScope !== void 0 && {
|
|
97
|
-
requiredOperatorScope: this.options.requiredOperatorScope
|
|
98
|
-
},
|
|
99
|
-
...this.options.requiredOperatorCountry !== void 0 && {
|
|
100
|
-
requiredOperatorCountry: this.options.requiredOperatorCountry
|
|
101
|
-
}
|
|
112
|
+
...this.options,
|
|
113
|
+
protocol: this.protocol
|
|
102
114
|
});
|
|
103
115
|
const refreshedAt = this.now();
|
|
104
116
|
if (resolution.operatorAuthorizationValidUntil !== void 0 && refreshedAt >= resolution.operatorAuthorizationValidUntil) {
|
|
@@ -112,13 +124,28 @@ class ResolvedAgentJwksResolver {
|
|
|
112
124
|
const next = /* @__PURE__ */ new Map();
|
|
113
125
|
for (const candidate of resolution.jwks.keys) {
|
|
114
126
|
if (candidate && typeof candidate === "object" && typeof candidate.kid === "string") {
|
|
127
|
+
if (next.has(candidate.kid))
|
|
128
|
+
throw new import_errors.AgentResolverError(
|
|
129
|
+
"request_signature_key_unknown",
|
|
130
|
+
"JWKS has duplicate key identifiers",
|
|
131
|
+
{ agent_url: this.agentUrl },
|
|
132
|
+
["agent_url"]
|
|
133
|
+
);
|
|
115
134
|
next.set(candidate.kid, candidate);
|
|
116
135
|
}
|
|
117
136
|
}
|
|
118
137
|
this.keys = next;
|
|
138
|
+
this.canonicalAgentUrl = resolution.agentUrl;
|
|
139
|
+
this.operatorRecord = resolution.brandJson === void 0 ? void 0 : { url: resolution.brandJsonUrl, document: resolution.brandJson };
|
|
140
|
+
this.legacyWebhookFallback = resolution.legacyWebhookFallback;
|
|
119
141
|
this.operatorAuthorizationValidUntil = resolution.operatorAuthorizationValidUntil;
|
|
120
142
|
this.expiresAt = Math.min(
|
|
121
|
-
refreshedAt +
|
|
143
|
+
refreshedAt + Math.min(
|
|
144
|
+
this.cacheTtlSeconds,
|
|
145
|
+
1800,
|
|
146
|
+
Math.max(30, cacheLifetime(resolution.brandJsonCacheControl)),
|
|
147
|
+
Math.max(60, cacheLifetime(resolution.jwksCacheControl))
|
|
148
|
+
),
|
|
122
149
|
resolution.operatorAuthorizationValidUntil ?? Number.POSITIVE_INFINITY
|
|
123
150
|
);
|
|
124
151
|
})().finally(() => {
|
|
@@ -127,6 +154,12 @@ class ResolvedAgentJwksResolver {
|
|
|
127
154
|
return this.inFlight;
|
|
128
155
|
}
|
|
129
156
|
}
|
|
157
|
+
function cacheLifetime(cacheControl) {
|
|
158
|
+
if (!cacheControl) return Infinity;
|
|
159
|
+
if (/\bno-cache\b|\bno-store\b/i.test(cacheControl)) return 0;
|
|
160
|
+
const match = /(?:^|,)\s*max-age\s*=\s*"?(\d+)/i.exec(cacheControl);
|
|
161
|
+
return match ? Number(match[1]) : Infinity;
|
|
162
|
+
}
|
|
130
163
|
// Annotate the CommonJS export names for ESM import in node:
|
|
131
164
|
0 && (module.exports = {
|
|
132
165
|
ResolvedAgentJwksResolver
|
|
@@ -23,10 +23,17 @@ class ResolvedAgentJwksResolver {
|
|
|
23
23
|
cacheTtlSeconds;
|
|
24
24
|
unknownKidCooldownSeconds;
|
|
25
25
|
resolveAgentFn;
|
|
26
|
+
canonicalAgentUrl;
|
|
27
|
+
legacyWebhookFallback;
|
|
28
|
+
operatorRecord;
|
|
29
|
+
get resolvedAgentUrl() {
|
|
30
|
+
return this.canonicalAgentUrl;
|
|
31
|
+
}
|
|
26
32
|
keys = /* @__PURE__ */ new Map();
|
|
27
33
|
expiresAt = 0;
|
|
28
34
|
operatorAuthorizationValidUntil;
|
|
29
35
|
lastUnknownKidRefresh = Number.NEGATIVE_INFINITY;
|
|
36
|
+
lastDiscoveryAttemptAt = Number.NEGATIVE_INFINITY;
|
|
30
37
|
inFlight;
|
|
31
38
|
async resolve(keyid) {
|
|
32
39
|
return (await this.resolveWithMetadata(keyid)).jwk;
|
|
@@ -35,6 +42,15 @@ class ResolvedAgentJwksResolver {
|
|
|
35
42
|
const now = this.now();
|
|
36
43
|
let refreshed = false;
|
|
37
44
|
if (now >= this.expiresAt) {
|
|
45
|
+
if (!this.inFlight && now - this.lastDiscoveryAttemptAt < 30) {
|
|
46
|
+
const expiredAuthorization = this.operatorAuthorizationValidUntil !== void 0 && now >= this.operatorAuthorizationValidUntil;
|
|
47
|
+
throw new AgentResolverError(
|
|
48
|
+
expiredAuthorization ? "request_signature_brand_origin_mismatch" : "request_signature_brand_json_unreachable",
|
|
49
|
+
"Expired operator mapping cannot be used during the discovery cooldown",
|
|
50
|
+
{ agent_url: this.agentUrl },
|
|
51
|
+
["agent_url"]
|
|
52
|
+
);
|
|
53
|
+
}
|
|
38
54
|
await this.refresh();
|
|
39
55
|
refreshed = true;
|
|
40
56
|
}
|
|
@@ -44,14 +60,22 @@ class ResolvedAgentJwksResolver {
|
|
|
44
60
|
this.lastUnknownKidRefresh = now;
|
|
45
61
|
return this.resolution(null);
|
|
46
62
|
}
|
|
47
|
-
if (now - this.lastUnknownKidRefresh
|
|
63
|
+
if (now - Math.max(this.lastUnknownKidRefresh, this.lastDiscoveryAttemptAt) < Math.max(30, this.unknownKidCooldownSeconds))
|
|
64
|
+
return this.resolution(null);
|
|
48
65
|
await this.refresh();
|
|
49
66
|
this.lastUnknownKidRefresh = now;
|
|
50
67
|
return this.resolution(this.keys.get(keyid) ?? null);
|
|
51
68
|
}
|
|
69
|
+
async forceRefresh() {
|
|
70
|
+
this.expiresAt = 0;
|
|
71
|
+
await this.refresh();
|
|
72
|
+
}
|
|
52
73
|
resolution(jwk) {
|
|
53
74
|
return {
|
|
54
75
|
jwk,
|
|
76
|
+
...this.operatorRecord !== void 0 && { operatorRecord: this.operatorRecord },
|
|
77
|
+
...this.legacyWebhookFallback && { legacyWebhookFallback: true },
|
|
78
|
+
...this.resolvedAgentUrl !== void 0 && { agentUrl: this.resolvedAgentUrl },
|
|
55
79
|
...this.operatorAuthorizationValidUntil !== void 0 && {
|
|
56
80
|
operatorAuthorizationValidUntil: this.operatorAuthorizationValidUntil
|
|
57
81
|
}
|
|
@@ -59,23 +83,11 @@ class ResolvedAgentJwksResolver {
|
|
|
59
83
|
}
|
|
60
84
|
async refresh() {
|
|
61
85
|
if (this.inFlight) return this.inFlight;
|
|
86
|
+
this.lastDiscoveryAttemptAt = this.now();
|
|
62
87
|
this.inFlight = (async () => {
|
|
63
88
|
const resolution = await this.resolveAgentFn(this.agentUrl, {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
...this.options.allowPrivateIp !== void 0 && { allowPrivateIp: this.options.allowPrivateIp },
|
|
67
|
-
...this.options.bodyCaps && { bodyCaps: this.options.bodyCaps },
|
|
68
|
-
...this.options.timeoutMs !== void 0 && { timeoutMs: this.options.timeoutMs },
|
|
69
|
-
...this.options.now && { now: this.options.now },
|
|
70
|
-
...this.options.requiredOperatorBrand !== void 0 && {
|
|
71
|
-
requiredOperatorBrand: this.options.requiredOperatorBrand
|
|
72
|
-
},
|
|
73
|
-
...this.options.requiredOperatorScope !== void 0 && {
|
|
74
|
-
requiredOperatorScope: this.options.requiredOperatorScope
|
|
75
|
-
},
|
|
76
|
-
...this.options.requiredOperatorCountry !== void 0 && {
|
|
77
|
-
requiredOperatorCountry: this.options.requiredOperatorCountry
|
|
78
|
-
}
|
|
89
|
+
...this.options,
|
|
90
|
+
protocol: this.protocol
|
|
79
91
|
});
|
|
80
92
|
const refreshedAt = this.now();
|
|
81
93
|
if (resolution.operatorAuthorizationValidUntil !== void 0 && refreshedAt >= resolution.operatorAuthorizationValidUntil) {
|
|
@@ -89,13 +101,28 @@ class ResolvedAgentJwksResolver {
|
|
|
89
101
|
const next = /* @__PURE__ */ new Map();
|
|
90
102
|
for (const candidate of resolution.jwks.keys) {
|
|
91
103
|
if (candidate && typeof candidate === "object" && typeof candidate.kid === "string") {
|
|
104
|
+
if (next.has(candidate.kid))
|
|
105
|
+
throw new AgentResolverError(
|
|
106
|
+
"request_signature_key_unknown",
|
|
107
|
+
"JWKS has duplicate key identifiers",
|
|
108
|
+
{ agent_url: this.agentUrl },
|
|
109
|
+
["agent_url"]
|
|
110
|
+
);
|
|
92
111
|
next.set(candidate.kid, candidate);
|
|
93
112
|
}
|
|
94
113
|
}
|
|
95
114
|
this.keys = next;
|
|
115
|
+
this.canonicalAgentUrl = resolution.agentUrl;
|
|
116
|
+
this.operatorRecord = resolution.brandJson === void 0 ? void 0 : { url: resolution.brandJsonUrl, document: resolution.brandJson };
|
|
117
|
+
this.legacyWebhookFallback = resolution.legacyWebhookFallback;
|
|
96
118
|
this.operatorAuthorizationValidUntil = resolution.operatorAuthorizationValidUntil;
|
|
97
119
|
this.expiresAt = Math.min(
|
|
98
|
-
refreshedAt +
|
|
120
|
+
refreshedAt + Math.min(
|
|
121
|
+
this.cacheTtlSeconds,
|
|
122
|
+
1800,
|
|
123
|
+
Math.max(30, cacheLifetime(resolution.brandJsonCacheControl)),
|
|
124
|
+
Math.max(60, cacheLifetime(resolution.jwksCacheControl))
|
|
125
|
+
),
|
|
99
126
|
resolution.operatorAuthorizationValidUntil ?? Number.POSITIVE_INFINITY
|
|
100
127
|
);
|
|
101
128
|
})().finally(() => {
|
|
@@ -104,6 +131,12 @@ class ResolvedAgentJwksResolver {
|
|
|
104
131
|
return this.inFlight;
|
|
105
132
|
}
|
|
106
133
|
}
|
|
134
|
+
function cacheLifetime(cacheControl) {
|
|
135
|
+
if (!cacheControl) return Infinity;
|
|
136
|
+
if (/\bno-cache\b|\bno-store\b/i.test(cacheControl)) return 0;
|
|
137
|
+
const match = /(?:^|,)\s*max-age\s*=\s*"?(\d+)/i.exec(cacheControl);
|
|
138
|
+
return match ? Number(match[1]) : Infinity;
|
|
139
|
+
}
|
|
107
140
|
export {
|
|
108
141
|
ResolvedAgentJwksResolver
|
|
109
142
|
};
|