@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,8 +1,12 @@
|
|
|
1
1
|
import { parse as parseTld } from "tldts";
|
|
2
|
+
import { isIP } from "node:net";
|
|
2
3
|
import { createA2AClient, createMCPClient } from "../../protocols/index.mjs";
|
|
3
4
|
import { isDevelopmentBrandDomain } from "../../brand/domain.mjs";
|
|
4
5
|
import { readBrandJsonUrl, readIdentityPosture } from "./capabilities-types.mjs";
|
|
5
|
-
import {
|
|
6
|
+
import { isAlwaysBlocked, isPrivateIp } from "../../net/index.mjs";
|
|
7
|
+
import { AgentTransportPolicyError, createAgentTransportFetch } from "../../net/agent-transport-fetch.mjs";
|
|
8
|
+
import { checkDelegatedOperatorAuthorization } from "./operator-authorization.mjs";
|
|
9
|
+
import { fetchLegacyBrandJson } from "./legacy-brand.mjs";
|
|
6
10
|
import {
|
|
7
11
|
checkOriginConsistency,
|
|
8
12
|
checkRequiredOrigins,
|
|
@@ -10,9 +14,16 @@ import {
|
|
|
10
14
|
} from "./consistency.mjs";
|
|
11
15
|
import { AgentResolverError } from "./errors.mjs";
|
|
12
16
|
import { eTldPlusOne, sameEtldPlusOne } from "./etld.mjs";
|
|
13
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
MAX_BRAND_JSON_BYTES,
|
|
19
|
+
MAX_JWKS_BYTES,
|
|
20
|
+
safeFetchJson,
|
|
21
|
+
SafeFetchError,
|
|
22
|
+
classifyDiscoveryFailure,
|
|
23
|
+
isPermanentDiscoveryFailure
|
|
24
|
+
} from "./fetch-helpers.mjs";
|
|
14
25
|
import { unwrapProtocolResponse } from "../protocol-response.mjs";
|
|
15
|
-
import { selectAgentByUrl, AgentSelectorError } from "./select-agent.mjs";
|
|
26
|
+
import { selectAgentByUrl, agentJwksUri, AgentSelectorError, canonicalAgentUrl } from "./select-agent.mjs";
|
|
16
27
|
const DEFAULT_TIMEOUT_MS = 1e4;
|
|
17
28
|
async function resolveAgent(agentUrl, options = {}) {
|
|
18
29
|
const trace = [];
|
|
@@ -22,7 +33,7 @@ async function resolveAgent(agentUrl, options = {}) {
|
|
|
22
33
|
const caps = options.bodyCaps ?? {};
|
|
23
34
|
const brandJsonCap = caps.brandJsonBytes ?? MAX_BRAND_JSON_BYTES;
|
|
24
35
|
const jwksCap = caps.jwksBytes ?? MAX_JWKS_BYTES;
|
|
25
|
-
const fetchCapabilities = options.fetchCapabilities ?? defaultFetchCapabilities(agentUrl, options.protocol ?? "mcp");
|
|
36
|
+
const fetchCapabilities = options.fetchCapabilities ?? defaultFetchCapabilities(agentUrl, options.protocol ?? "mcp", allowPrivateIp, timeoutMs);
|
|
26
37
|
let capabilitiesPayload;
|
|
27
38
|
let capabilitiesFetchedAt;
|
|
28
39
|
try {
|
|
@@ -39,7 +50,7 @@ async function resolveAgent(agentUrl, options = {}) {
|
|
|
39
50
|
} catch (err) {
|
|
40
51
|
const detail = {
|
|
41
52
|
agent_url: agentUrl,
|
|
42
|
-
|
|
53
|
+
...classifyDiscoveryFailure(err),
|
|
43
54
|
last_attempt_at: now()
|
|
44
55
|
};
|
|
45
56
|
pushTrace(trace, { step: 1, name: "fetch_capabilities", ok: false, url: agentUrl, detail });
|
|
@@ -50,7 +61,10 @@ async function resolveAgent(agentUrl, options = {}) {
|
|
|
50
61
|
["agent_url"]
|
|
51
62
|
);
|
|
52
63
|
}
|
|
53
|
-
|
|
64
|
+
let brandJsonUrl = readBrandJsonUrl(capabilitiesPayload);
|
|
65
|
+
const identity = capabilitiesPayload?.identity;
|
|
66
|
+
const legacyFallback = options.legacyWebhookFallback === true && identity?.brand_json_url === void 0 && permitsLegacyWebhookDiscovery(capabilitiesPayload);
|
|
67
|
+
if (legacyFallback) brandJsonUrl = `${new URL(agentUrl).origin}/.well-known/brand.json`;
|
|
54
68
|
const identityPosture = readIdentityPosture(capabilitiesPayload);
|
|
55
69
|
const acceptsScheme = (url) => {
|
|
56
70
|
if (!url) return false;
|
|
@@ -68,24 +82,43 @@ async function resolveAgent(agentUrl, options = {}) {
|
|
|
68
82
|
["agent_url"]
|
|
69
83
|
);
|
|
70
84
|
}
|
|
85
|
+
if (!legacyFallback && options.expectedBrandJsonUrl !== void 0 && canonicalAgentUrl(brandJsonUrl) !== canonicalAgentUrl(options.expectedBrandJsonUrl)) {
|
|
86
|
+
throw new AgentResolverError(
|
|
87
|
+
"request_signature_brand_origin_mismatch",
|
|
88
|
+
"Cached operator mapping disagrees with identity.brand_json_url",
|
|
89
|
+
{ agent_url: agentUrl },
|
|
90
|
+
["agent_url"]
|
|
91
|
+
);
|
|
92
|
+
}
|
|
71
93
|
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
|
-
|
|
94
|
+
let agentEtld1 = "";
|
|
95
|
+
let brandEtld1 = "";
|
|
96
|
+
let sameOrigin = true;
|
|
97
|
+
if (!legacyFallback) {
|
|
98
|
+
try {
|
|
99
|
+
agentEtld1 = eTldPlusOne(agentUrl);
|
|
100
|
+
brandEtld1 = eTldPlusOne(brandJsonUrl);
|
|
101
|
+
sameOrigin = sameEtldPlusOne(agentUrl, brandJsonUrl);
|
|
102
|
+
} catch {
|
|
103
|
+
if (allowPrivateIp) {
|
|
104
|
+
const agentHost = new URL(agentUrl).hostname;
|
|
105
|
+
const brandHost = new URL(brandJsonUrl).hostname;
|
|
106
|
+
const agentIsExplicitDevelopmentHost = agentHost === "localhost" || isDevelopmentBrandDomain(agentHost) || parseTld(agentHost, { extractHostname: false }).isIp;
|
|
107
|
+
const brandIsExplicitDevelopmentHost = brandHost === "localhost" || isDevelopmentBrandDomain(brandHost) || parseTld(brandHost, { extractHostname: false }).isIp;
|
|
108
|
+
if (agentIsExplicitDevelopmentHost && brandIsExplicitDevelopmentHost) {
|
|
109
|
+
agentEtld1 = agentHost;
|
|
110
|
+
brandEtld1 = brandHost;
|
|
111
|
+
sameOrigin = agentHost === brandHost;
|
|
112
|
+
} else {
|
|
113
|
+
const detail = { agent_url: agentUrl, brand_json_url: brandJsonUrl };
|
|
114
|
+
pushTrace(trace, { step: 3, name: "etld1_binding", ok: false, detail });
|
|
115
|
+
throw new AgentResolverError(
|
|
116
|
+
"request_signature_brand_origin_mismatch",
|
|
117
|
+
`Cannot compute eTLD+1 for agent or brand.json host`,
|
|
118
|
+
detail,
|
|
119
|
+
["agent_url", "brand_json_url"]
|
|
120
|
+
);
|
|
121
|
+
}
|
|
89
122
|
} else {
|
|
90
123
|
const detail = { agent_url: agentUrl, brand_json_url: brandJsonUrl };
|
|
91
124
|
pushTrace(trace, { step: 3, name: "etld1_binding", ok: false, detail });
|
|
@@ -96,25 +129,24 @@ async function resolveAgent(agentUrl, options = {}) {
|
|
|
96
129
|
["agent_url", "brand_json_url"]
|
|
97
130
|
);
|
|
98
131
|
}
|
|
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
132
|
}
|
|
109
133
|
}
|
|
110
134
|
let brandJson;
|
|
111
135
|
let brandJsonFetchedAt;
|
|
136
|
+
let brandJsonCacheControl;
|
|
112
137
|
try {
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
})
|
|
138
|
+
const fetchOptions = { allowPrivateIp, timeoutMs, maxBodyBytes: brandJsonCap, lookup: options.lookup };
|
|
139
|
+
const fetched = legacyFallback ? await fetchLegacyBrandJson(agentUrl, fetchOptions) : await safeFetchJson(brandJsonUrl, "brand.json", fetchOptions);
|
|
140
|
+
if (legacyFallback && "url" in fetched) brandJsonUrl = fetched.url;
|
|
141
|
+
brandJsonCacheControl = fetched.headers["cache-control"];
|
|
142
|
+
if (legacyFallback && options.expectedBrandJsonUrl !== void 0 && canonicalAgentUrl(brandJsonUrl) !== canonicalAgentUrl(options.expectedBrandJsonUrl) && canonicalAgentUrl(`${new URL(agentUrl).origin}/.well-known/brand.json`) !== canonicalAgentUrl(options.expectedBrandJsonUrl)) {
|
|
143
|
+
throw new AgentResolverError(
|
|
144
|
+
"request_signature_brand_origin_mismatch",
|
|
145
|
+
"Cached mapping disagrees with legacy discovery",
|
|
146
|
+
{ agent_url: agentUrl },
|
|
147
|
+
["agent_url"]
|
|
148
|
+
);
|
|
149
|
+
}
|
|
118
150
|
brandJson = fetched.body;
|
|
119
151
|
brandJsonFetchedAt = fetched.fetchedAt;
|
|
120
152
|
pushTrace(trace, {
|
|
@@ -125,6 +157,7 @@ async function resolveAgent(agentUrl, options = {}) {
|
|
|
125
157
|
url: brandJsonUrl
|
|
126
158
|
});
|
|
127
159
|
} catch (err) {
|
|
160
|
+
if (err instanceof AgentResolverError) throw err;
|
|
128
161
|
if (err instanceof SafeFetchError && /strict-JSON parse/.test(err.message)) {
|
|
129
162
|
const detail2 = {
|
|
130
163
|
brand_json_url: brandJsonUrl,
|
|
@@ -168,7 +201,17 @@ async function resolveAgent(agentUrl, options = {}) {
|
|
|
168
201
|
["agent_url"]
|
|
169
202
|
);
|
|
170
203
|
}
|
|
171
|
-
|
|
204
|
+
if (options.requiredOperatorBrand !== void 0 || options.requiredOperatorScope !== void 0 || options.requiredOperatorCountry !== void 0) {
|
|
205
|
+
const authorization = checkDelegatedOperatorAuthorization(brandJson, agentEtld1, now(), options);
|
|
206
|
+
if (!authorization)
|
|
207
|
+
throw new AgentResolverError(
|
|
208
|
+
"request_signature_brand_origin_mismatch",
|
|
209
|
+
"Delegated operator does not satisfy the receiver account authorization policy",
|
|
210
|
+
{ agent_url: agentUrl },
|
|
211
|
+
["agent_url"]
|
|
212
|
+
);
|
|
213
|
+
operatorAuthorizationValidUntil = authorization.validUntil;
|
|
214
|
+
}
|
|
172
215
|
pushTrace(trace, {
|
|
173
216
|
step: 3,
|
|
174
217
|
name: "etld1_binding",
|
|
@@ -185,7 +228,7 @@ async function resolveAgent(agentUrl, options = {}) {
|
|
|
185
228
|
}
|
|
186
229
|
let agentEntry;
|
|
187
230
|
try {
|
|
188
|
-
agentEntry = selectAgentByUrl(brandJson, agentUrl);
|
|
231
|
+
agentEntry = selectAgentByUrl(brandJson, agentUrl, options);
|
|
189
232
|
pushTrace(trace, { step: 5, name: "select_agent", ok: true, detail: { url: agentEntry.url } });
|
|
190
233
|
} catch (err) {
|
|
191
234
|
if (err instanceof AgentSelectorError) {
|
|
@@ -208,17 +251,19 @@ async function resolveAgent(agentUrl, options = {}) {
|
|
|
208
251
|
pushTrace(trace, { step: 5, name: "select_agent", ok: false, detail });
|
|
209
252
|
throw new AgentResolverError(code, err.message, detail, attacker);
|
|
210
253
|
}
|
|
211
|
-
throw
|
|
254
|
+
throw new AgentResolverError(
|
|
255
|
+
"request_signature_brand_json_malformed",
|
|
256
|
+
"Matched agent has an invalid URL or JWKS source",
|
|
257
|
+
{ brand_json_url: brandJsonUrl },
|
|
258
|
+
["brand_json_url"]
|
|
259
|
+
);
|
|
212
260
|
}
|
|
213
|
-
const
|
|
214
|
-
const jwksUriFromEntry = typeof declaredJwksUri === "string" && acceptsScheme(declaredJwksUri) ? declaredJwksUri : void 0;
|
|
215
|
-
const jwksUri = jwksUriFromEntry ?? `${originOf(agentUrl)}/.well-known/jwks.json`;
|
|
261
|
+
const jwksUri = agentJwksUri(agentEntry);
|
|
216
262
|
pushTrace(trace, { step: 6, name: "resolve_jwks_uri", ok: true, detail: { jwks_uri: jwksUri } });
|
|
217
|
-
const consistencyResults = runConsistencyChecks({
|
|
263
|
+
const consistencyResults = legacyFallback ? [] : runConsistencyChecks({
|
|
218
264
|
capabilitiesPayload,
|
|
219
265
|
identityPosture,
|
|
220
266
|
jwksUri,
|
|
221
|
-
publisherPinnedWebhookSigning: options.publisherPinned?.webhook_signing === true,
|
|
222
267
|
extraDeclaredPurposes: options.declaredPurposes ?? []
|
|
223
268
|
});
|
|
224
269
|
const failedConsistency = consistencyResults.filter((r) => r.ok === false);
|
|
@@ -251,7 +296,8 @@ async function resolveAgent(agentUrl, options = {}) {
|
|
|
251
296
|
const fetched = await safeFetchJson(jwksUri, "jwks", {
|
|
252
297
|
allowPrivateIp,
|
|
253
298
|
timeoutMs,
|
|
254
|
-
maxBodyBytes: jwksCap
|
|
299
|
+
maxBodyBytes: jwksCap,
|
|
300
|
+
lookup: options.lookup
|
|
255
301
|
});
|
|
256
302
|
if (!fetched.body || typeof fetched.body !== "object" || !Array.isArray(fetched.body.keys)) {
|
|
257
303
|
throw new SafeFetchError("jwks", "fetch_failed", "JWKS document has no keys[] array");
|
|
@@ -288,8 +334,11 @@ async function resolveAgent(agentUrl, options = {}) {
|
|
|
288
334
|
);
|
|
289
335
|
}
|
|
290
336
|
return {
|
|
291
|
-
agentUrl,
|
|
337
|
+
agentUrl: canonicalAgentUrl(agentEntry.url),
|
|
292
338
|
brandJsonUrl,
|
|
339
|
+
brandJson,
|
|
340
|
+
...legacyFallback && { legacyWebhookFallback: true },
|
|
341
|
+
...brandJsonCacheControl !== void 0 && { brandJsonCacheControl },
|
|
293
342
|
agentEntry,
|
|
294
343
|
jwksUri,
|
|
295
344
|
jwks,
|
|
@@ -310,101 +359,59 @@ function checkAllowPrivateIp(requested) {
|
|
|
310
359
|
"resolveAgent: allowPrivateIp=true refused outside {NODE_ENV=test, NODE_ENV=development}. Set NODE_ENV appropriately for dev/test, or set ADCP_RESOLVER_ALLOW_PRIVATE_IP=1 as an explicit ops acknowledgment if you genuinely need private-IP discovery in this process."
|
|
311
360
|
);
|
|
312
361
|
}
|
|
313
|
-
function defaultFetchCapabilities(agentUrl, protocol) {
|
|
362
|
+
function defaultFetchCapabilities(agentUrl, protocol, allowPrivateIp, timeoutMs) {
|
|
314
363
|
return async () => {
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
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;
|
|
364
|
+
const url = new URL(agentUrl);
|
|
365
|
+
const hostname = url.hostname.replace(/^\[|\]$/g, "");
|
|
366
|
+
if (!allowPrivateIp && url.protocol !== "https:" || isIP(hostname) && (isAlwaysBlocked(hostname) || !allowPrivateIp && isPrivateIp(hostname)))
|
|
367
|
+
throw new AgentTransportPolicyError("Signing discovery URL refused by address or HTTPS policy.");
|
|
368
|
+
let failure;
|
|
369
|
+
const upstream = createAgentTransportFetch(agentUrl, {
|
|
370
|
+
allowPrivateIp,
|
|
371
|
+
allowPrivateInitialOrigin: false,
|
|
372
|
+
requireHttps: true
|
|
373
|
+
});
|
|
374
|
+
let postSucceeded = false;
|
|
375
|
+
const trustedFetchFn = async (input, init) => {
|
|
376
|
+
const method = init?.method ?? (input instanceof Request ? input.method : "GET");
|
|
377
|
+
try {
|
|
378
|
+
const response = await upstream(input, init);
|
|
379
|
+
const isGet = method.toUpperCase() === "GET";
|
|
380
|
+
const observed = response.status >= 400 ? { dns_error: "fetch_failed", http_status: response.status } : void 0;
|
|
381
|
+
if (protocol === "mcp" && method.toUpperCase() === "POST" && response.ok) postSucceeded = true;
|
|
382
|
+
if (!(protocol === "mcp" && isGet && (postSucceeded || response.status === 405)) && !(isGet && observed && failure && !isPermanentDiscoveryFailure(failure) && isPermanentDiscoveryFailure(observed)))
|
|
383
|
+
failure = observed;
|
|
384
|
+
return response;
|
|
385
|
+
} catch (error) {
|
|
386
|
+
if (!(protocol === "mcp" && method.toUpperCase() === "GET" && postSucceeded))
|
|
387
|
+
failure = classifyDiscoveryFailure(error);
|
|
388
|
+
throw error;
|
|
348
389
|
}
|
|
390
|
+
};
|
|
391
|
+
const transport = { trustedFetchFn, requestTimeoutMs: timeoutMs };
|
|
392
|
+
const client = protocol === "a2a" ? createA2AClient(agentUrl, void 0, void 0, void 0, void 0, transport) : createMCPClient(agentUrl, void 0, void 0, void 0, void 0, transport);
|
|
393
|
+
try {
|
|
394
|
+
return await client.callTool("get_adcp_capabilities", {});
|
|
395
|
+
} catch (error) {
|
|
396
|
+
if (failure)
|
|
397
|
+
throw new SafeFetchError(
|
|
398
|
+
"capabilities",
|
|
399
|
+
failure.dns_error,
|
|
400
|
+
"Capabilities fetch failed",
|
|
401
|
+
failure.http_status
|
|
402
|
+
);
|
|
403
|
+
throw error;
|
|
349
404
|
}
|
|
350
|
-
|
|
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);
|
|
366
|
-
}
|
|
367
|
-
return matched ? { validUntil: latestValidUntil } : void 0;
|
|
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);
|
|
405
|
+
};
|
|
381
406
|
}
|
|
382
|
-
function
|
|
383
|
-
if (
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
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;
|
|
407
|
+
function findAuthorizedOperator(brandJson, agentEtld1, _now, _options) {
|
|
408
|
+
if (!brandJson || typeof brandJson !== "object") return void 0;
|
|
409
|
+
const obj = brandJson;
|
|
410
|
+
if (!obj.house || typeof obj.house !== "object" || Array.isArray(obj.house) || !Array.isArray(obj.brands)) {
|
|
411
|
+
return void 0;
|
|
399
412
|
}
|
|
400
|
-
|
|
401
|
-
return
|
|
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);
|
|
413
|
+
if (!Array.isArray(obj.authorized_operators)) return void 0;
|
|
414
|
+
return obj.authorized_operators.some((op) => op && typeof op === "object" && op.domain === agentEtld1) ? {} : void 0;
|
|
408
415
|
}
|
|
409
416
|
function pushTrace(trace, step) {
|
|
410
417
|
trace.push(step);
|
|
@@ -426,14 +433,25 @@ function runConsistencyChecks(args) {
|
|
|
426
433
|
const result = checkOriginConsistency({
|
|
427
434
|
purpose,
|
|
428
435
|
declaredOrigin,
|
|
429
|
-
resolvedJwksUri: args.jwksUri
|
|
430
|
-
publisherPinned: purpose === "webhook_signing" && args.publisherPinnedWebhookSigning
|
|
436
|
+
resolvedJwksUri: args.jwksUri
|
|
431
437
|
});
|
|
432
438
|
if (result.ok === false) results.push(result);
|
|
433
439
|
}
|
|
434
440
|
}
|
|
435
441
|
return results;
|
|
436
442
|
}
|
|
443
|
+
function permitsLegacyWebhookDiscovery(payload) {
|
|
444
|
+
if (!payload || typeof payload !== "object") return true;
|
|
445
|
+
const caps = payload;
|
|
446
|
+
const major = (value) => typeof value === "string" ? Number(/^(\d+)\./.exec(value)?.[1]) : value;
|
|
447
|
+
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))
|
|
448
|
+
return false;
|
|
449
|
+
if (caps.adcp_version !== void 0) return typeof caps.adcp_version === "string" && /^3\./.test(caps.adcp_version);
|
|
450
|
+
if (Array.isArray(caps.adcp?.supported_versions))
|
|
451
|
+
return caps.adcp.supported_versions.some((v) => typeof v === "string" && /^3\./.test(v));
|
|
452
|
+
if (Array.isArray(caps.adcp?.major_versions)) return caps.adcp.major_versions.includes(3);
|
|
453
|
+
return true;
|
|
454
|
+
}
|
|
437
455
|
export {
|
|
438
456
|
resolveAgent
|
|
439
457
|
};
|
|
@@ -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
|