@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,7 +1,11 @@
|
|
|
1
|
+
import { classifyDiscoveryFailure, isPermanentDiscoveryFailure } from "./agent-resolver/fetch-helpers.mjs";
|
|
1
2
|
import { ssrfSafeFetch } from "../net/index.mjs";
|
|
2
|
-
import {
|
|
3
|
+
import { ResolvedAgentJwksResolver } from "./agent-resolver/resolved-agent-jwks.mjs";
|
|
4
|
+
import { parseStrictJson } from "./agent-resolver/strict-json.mjs";
|
|
5
|
+
import { canonicalAgentUrl } from "./agent-resolver/select-agent.mjs";
|
|
3
6
|
class BrandJsonResolverError extends Error {
|
|
4
7
|
code;
|
|
8
|
+
recovery;
|
|
5
9
|
cause;
|
|
6
10
|
/** HTTP status for `fetch_failed` responses, when a response was received. */
|
|
7
11
|
httpStatus;
|
|
@@ -11,6 +15,7 @@ class BrandJsonResolverError extends Error {
|
|
|
11
15
|
this.code = code;
|
|
12
16
|
this.httpStatus = details.httpStatus;
|
|
13
17
|
this.cause = details.cause;
|
|
18
|
+
this.recovery = code !== "fetch_failed" || isPermanentDiscoveryFailure(classifyDiscoveryFailure(this)) ? "terminal" : "transient";
|
|
14
19
|
}
|
|
15
20
|
}
|
|
16
21
|
const DEFAULT_MIN_COOLDOWN_SECONDS = 30;
|
|
@@ -18,115 +23,115 @@ const DEFAULT_MAX_AGE_SECONDS = 3600;
|
|
|
18
23
|
const DEFAULT_MAX_REDIRECTS = 3;
|
|
19
24
|
const BARE_HOSTNAME = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/;
|
|
20
25
|
class BrandJsonJwksResolver {
|
|
21
|
-
url;
|
|
22
|
-
selector;
|
|
23
|
-
minCooldown;
|
|
24
|
-
maxAge;
|
|
25
|
-
maxRedirects;
|
|
26
|
-
allowPrivateIp;
|
|
27
|
-
lookup;
|
|
28
|
-
jwksOptions;
|
|
29
|
-
now;
|
|
30
|
-
snapshot;
|
|
31
|
-
inner;
|
|
32
|
-
inFlight;
|
|
33
26
|
constructor(brandJsonUrl, options) {
|
|
34
|
-
this.
|
|
35
|
-
this.
|
|
27
|
+
this.brandJsonUrl = brandJsonUrl;
|
|
28
|
+
this.options = options;
|
|
29
|
+
for (const lifetime of [options.maxAgeSeconds, options.jwksOptions?.maxAgeSeconds]) {
|
|
30
|
+
if (lifetime !== void 0 && (!Number.isFinite(lifetime) || lifetime <= 0))
|
|
31
|
+
throw new TypeError("maxAgeSeconds must be a finite positive number.");
|
|
32
|
+
}
|
|
33
|
+
for (const cooldown of [options.minCooldownSeconds, options.jwksOptions?.minCooldownSeconds]) {
|
|
34
|
+
if (cooldown !== void 0 && (!Number.isFinite(cooldown) || cooldown < 0))
|
|
35
|
+
throw new TypeError("minCooldownSeconds must be a finite non-negative number.");
|
|
36
|
+
}
|
|
37
|
+
if (options.agentUrl !== void 0) this.resolver = this.createResolver(options.agentUrl, brandJsonUrl);
|
|
38
|
+
}
|
|
39
|
+
brandJsonUrl;
|
|
40
|
+
options;
|
|
41
|
+
resolver;
|
|
42
|
+
onboarding;
|
|
43
|
+
lastOnboardingAttempt = Number.NEGATIVE_INFINITY;
|
|
44
|
+
lastOnboardingError;
|
|
45
|
+
createResolver(agentUrl, operatorUrl) {
|
|
46
|
+
const options = this.options;
|
|
47
|
+
return new ResolvedAgentJwksResolver(agentUrl, options.protocol ?? "mcp", {
|
|
36
48
|
agentType: options.agentType,
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
49
|
+
agentId: options.agentId,
|
|
50
|
+
expectedBrandJsonUrl: operatorUrl,
|
|
51
|
+
fetchCapabilities: options.fetchCapabilities,
|
|
52
|
+
legacyWebhookFallback: options.legacyWebhookFallback ?? true,
|
|
53
|
+
allowPrivateIp: options.allowPrivateIp,
|
|
54
|
+
lookup: options.lookup,
|
|
55
|
+
now: options.now,
|
|
56
|
+
cacheTtlSeconds: Math.min(
|
|
57
|
+
options.maxAgeSeconds ?? DEFAULT_MAX_AGE_SECONDS,
|
|
58
|
+
options.jwksOptions?.maxAgeSeconds ?? 1800
|
|
59
|
+
),
|
|
60
|
+
unknownKidCooldownSeconds: options.minCooldownSeconds ?? options.jwksOptions?.minCooldownSeconds ?? DEFAULT_MIN_COOLDOWN_SECONDS
|
|
61
|
+
});
|
|
47
62
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
if (!this.inner) return null;
|
|
63
|
-
const hit = await this.inner.resolve(keyid);
|
|
64
|
-
if (hit) return hit;
|
|
65
|
-
if (this.snapshot && this.now() - this.snapshot.fetchedAt >= this.minCooldown) {
|
|
66
|
-
await this.refresh().catch(() => {
|
|
63
|
+
async confirmedResolver() {
|
|
64
|
+
if (this.resolver) return this.resolver;
|
|
65
|
+
if (this.onboarding) return this.onboarding;
|
|
66
|
+
const now = this.options.now?.() ?? Date.now() / 1e3;
|
|
67
|
+
if (now - this.lastOnboardingAttempt < 30)
|
|
68
|
+
throw this.lastOnboardingError ?? new BrandJsonResolverError("fetch_failed", "Operator onboarding discovery is in its retry cooldown.");
|
|
69
|
+
this.lastOnboardingAttempt = now;
|
|
70
|
+
this.onboarding = (async () => {
|
|
71
|
+
const record = await fetchBrandJson({
|
|
72
|
+
startUrl: this.brandJsonUrl,
|
|
73
|
+
maxRedirects: this.options.maxRedirects,
|
|
74
|
+
allowPrivateIp: this.options.allowPrivateIp,
|
|
75
|
+
lookup: this.options.lookup
|
|
67
76
|
});
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
77
|
+
const agentUrl = inferOnboardingAgentUrl(record.data, this.options);
|
|
78
|
+
const resolver = this.createResolver(agentUrl, record.finalUrl);
|
|
79
|
+
await resolver.forceRefresh();
|
|
80
|
+
this.resolver = resolver;
|
|
81
|
+
this.lastOnboardingError = void 0;
|
|
82
|
+
return resolver;
|
|
83
|
+
})().catch((error) => {
|
|
84
|
+
this.lastOnboardingError = error;
|
|
85
|
+
throw error;
|
|
86
|
+
}).finally(() => {
|
|
87
|
+
this.onboarding = void 0;
|
|
88
|
+
});
|
|
89
|
+
return this.onboarding;
|
|
90
|
+
}
|
|
91
|
+
async resolve(keyid) {
|
|
92
|
+
return (await this.confirmedResolver()).resolve(keyid);
|
|
93
|
+
}
|
|
94
|
+
async resolveWithMetadata(keyid) {
|
|
95
|
+
return (await this.confirmedResolver()).resolveWithMetadata(keyid);
|
|
71
96
|
}
|
|
72
|
-
/**
|
|
73
|
-
* The agent URL we resolved `jwks_uri` from. Populated after the first
|
|
74
|
-
* successful refresh; useful for verifier result attribution
|
|
75
|
-
* (`VerifyWebhookOptions.agentUrlForKeyid`).
|
|
76
|
-
*/
|
|
77
97
|
get agentUrl() {
|
|
78
|
-
return this.
|
|
98
|
+
return this.resolver?.resolvedAgentUrl;
|
|
79
99
|
}
|
|
80
|
-
/** Force a refetch of both brand.json and the inner JWKS, bypassing the cooldown. */
|
|
81
100
|
async forceRefresh() {
|
|
82
|
-
this.
|
|
83
|
-
this.
|
|
84
|
-
await this.refresh();
|
|
85
|
-
}
|
|
86
|
-
async refresh() {
|
|
87
|
-
if (this.inFlight) {
|
|
88
|
-
await this.inFlight;
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
this.inFlight = this.doRefresh().finally(() => {
|
|
92
|
-
this.inFlight = void 0;
|
|
93
|
-
});
|
|
94
|
-
await this.inFlight;
|
|
101
|
+
if (this.resolver) await this.resolver.forceRefresh();
|
|
102
|
+
else await this.confirmedResolver();
|
|
95
103
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
...this.snapshot,
|
|
107
|
-
...fetched.etag && { etag: fetched.etag },
|
|
108
|
-
fetchedAt: this.now(),
|
|
109
|
-
expiresAt: this.now() + computeLifetime(fetched.cacheControl, this.maxAge)
|
|
110
|
-
};
|
|
111
|
-
return;
|
|
104
|
+
}
|
|
105
|
+
function inferOnboardingAgentUrl(document, selector) {
|
|
106
|
+
const record = document;
|
|
107
|
+
const candidates = (agents) => !Array.isArray(agents) ? [] : agents.filter(
|
|
108
|
+
(entry) => entry?.type === selector.agentType && (selector.agentId === void 0 || entry.id === selector.agentId) && typeof entry.url === "string"
|
|
109
|
+
).flatMap((entry) => {
|
|
110
|
+
try {
|
|
111
|
+
return [canonicalAgentUrl(entry.url)];
|
|
112
|
+
} catch {
|
|
113
|
+
return [];
|
|
112
114
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
115
|
+
});
|
|
116
|
+
let urls;
|
|
117
|
+
if (isPortfolioHouse(record.house)) {
|
|
118
|
+
const house = record.house;
|
|
119
|
+
urls = [];
|
|
120
|
+
if (selector.brandId !== void 0 && Array.isArray(record.brands)) {
|
|
121
|
+
const brands = record.brands.filter((brand) => brand?.id === selector.brandId);
|
|
122
|
+
if (brands.length > 1)
|
|
123
|
+
throw new BrandJsonResolverError("agent_ambiguous", "Onboarding brand selector is ambiguous.");
|
|
124
|
+
urls = candidates(brands[0]?.agents);
|
|
121
125
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
126
|
+
if (urls.length === 0) urls = candidates(house.agents);
|
|
127
|
+
} else urls = candidates(record.agents);
|
|
128
|
+
const unique = [...new Set(urls)];
|
|
129
|
+
if (unique.length !== 1)
|
|
130
|
+
throw new BrandJsonResolverError(
|
|
131
|
+
unique.length === 0 ? "agent_not_found" : "agent_ambiguous",
|
|
132
|
+
"Onboarding must identify exactly one agent URL; supply agentUrl explicitly."
|
|
133
|
+
);
|
|
134
|
+
return unique[0];
|
|
130
135
|
}
|
|
131
136
|
const MAX_BRAND_JSON_TIMEOUT_MS = 1e4;
|
|
132
137
|
const MAX_BRAND_JSON_BODY_BYTES = 262144;
|
|
@@ -147,6 +152,8 @@ async function fetchBrandJson(args) {
|
|
|
147
152
|
const allowPrivateIp = args.allowPrivateIp === true;
|
|
148
153
|
const seen = /* @__PURE__ */ new Set();
|
|
149
154
|
let url = canonicalizeUrl(args.startUrl, allowPrivateIp);
|
|
155
|
+
const initial = new URL(url);
|
|
156
|
+
let httpRedirects = 0;
|
|
150
157
|
for (let hop = 0; hop <= maxRedirects; hop++) {
|
|
151
158
|
if (seen.has(url)) {
|
|
152
159
|
throw new BrandJsonResolverError("redirect_loop", `brand.json redirect loop detected`);
|
|
@@ -169,6 +176,19 @@ async function fetchBrandJson(args) {
|
|
|
169
176
|
} catch (cause) {
|
|
170
177
|
throw new BrandJsonResolverError("fetch_failed", "Unable to fetch brand.json", { cause });
|
|
171
178
|
}
|
|
179
|
+
if (args.domainDerived && hop === 0 && [301, 302, 303, 307, 308].includes(res.status)) {
|
|
180
|
+
const location = res.headers.location;
|
|
181
|
+
if (!location || httpRedirects++ >= 3)
|
|
182
|
+
throw new BrandJsonResolverError("fetch_failed", "Invalid domain-derived redirect");
|
|
183
|
+
const target = new URL(location, url);
|
|
184
|
+
const baseHost = initial.hostname.startsWith("www.") ? initial.hostname.slice(4) : initial.hostname;
|
|
185
|
+
if (target.hostname !== baseHost && target.hostname !== `www.${baseHost}` || target.port !== initial.port || target.protocol !== initial.protocol || target.username || target.password) {
|
|
186
|
+
throw new BrandJsonResolverError("invalid_url", "brand.json redirect leaves its original host/www boundary");
|
|
187
|
+
}
|
|
188
|
+
url = canonicalizeUrl(target.href, allowPrivateIp);
|
|
189
|
+
hop--;
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
172
192
|
if (hop === 0 && res.status === 304) {
|
|
173
193
|
return {
|
|
174
194
|
status: "not_modified",
|
|
@@ -186,7 +206,7 @@ async function fetchBrandJson(args) {
|
|
|
186
206
|
const text = Buffer.from(res.body).toString("utf8");
|
|
187
207
|
let parsed;
|
|
188
208
|
try {
|
|
189
|
-
parsed =
|
|
209
|
+
parsed = parseStrictJson(text);
|
|
190
210
|
} catch {
|
|
191
211
|
throw new BrandJsonResolverError("invalid_body", `brand.json response is not valid JSON`);
|
|
192
212
|
}
|
|
@@ -278,88 +298,6 @@ function assertBrandJsonShape(obj) {
|
|
|
278
298
|
}
|
|
279
299
|
}
|
|
280
300
|
}
|
|
281
|
-
function selectAgent(data, finalBrandUrl, selector) {
|
|
282
|
-
if (!data || typeof data !== "object") {
|
|
283
|
-
throw new BrandJsonResolverError(
|
|
284
|
-
"agent_not_found",
|
|
285
|
-
`brand.json has no agent matching ${describeSelector(selector)}`
|
|
286
|
-
);
|
|
287
|
-
}
|
|
288
|
-
const obj = data;
|
|
289
|
-
let picked;
|
|
290
|
-
if (isPortfolioHouse(obj.house)) {
|
|
291
|
-
const house = obj.house;
|
|
292
|
-
if (selector.brandId !== void 0) {
|
|
293
|
-
const brands = Array.isArray(obj.brands) ? obj.brands : [];
|
|
294
|
-
const brand = brands.find((b) => b && b.id === selector.brandId);
|
|
295
|
-
if (brand) picked = pickAgent(brand.agents, finalBrandUrl, selector);
|
|
296
|
-
}
|
|
297
|
-
picked ??= pickAgent(house.agents, finalBrandUrl, selector);
|
|
298
|
-
} else {
|
|
299
|
-
picked = pickAgent(obj.agents, finalBrandUrl, selector);
|
|
300
|
-
}
|
|
301
|
-
if (!picked) {
|
|
302
|
-
throw new BrandJsonResolverError(
|
|
303
|
-
"agent_not_found",
|
|
304
|
-
`brand.json has no agent matching ${describeSelector(selector)}`
|
|
305
|
-
);
|
|
306
|
-
}
|
|
307
|
-
return picked;
|
|
308
|
-
}
|
|
309
|
-
function pickAgent(agents, finalBrandUrl, selector) {
|
|
310
|
-
if (!Array.isArray(agents)) return void 0;
|
|
311
|
-
const matches = agents.filter((a) => {
|
|
312
|
-
if (!a || typeof a !== "object") return false;
|
|
313
|
-
const e = a;
|
|
314
|
-
if (e.type !== selector.agentType) return false;
|
|
315
|
-
if (selector.agentId !== void 0 && e.id !== selector.agentId) return false;
|
|
316
|
-
return typeof e.url === "string";
|
|
317
|
-
});
|
|
318
|
-
if (matches.length === 0) return void 0;
|
|
319
|
-
if (matches.length > 1 && selector.agentId === void 0) {
|
|
320
|
-
throw new BrandJsonResolverError(
|
|
321
|
-
"agent_ambiguous",
|
|
322
|
-
`brand.json declares ${matches.length} agents of type "${selector.agentType}"; pass \`agentId\` to disambiguate (choices: ${matches.map((m) => m.id ?? "<no-id>").join(", ")})`
|
|
323
|
-
);
|
|
324
|
-
}
|
|
325
|
-
const agent = matches[0];
|
|
326
|
-
const url = agent.url;
|
|
327
|
-
const jwksUri = agent.jwks_uri ?? defaultJwksUri(url, finalBrandUrl);
|
|
328
|
-
return { url, jwksUri };
|
|
329
|
-
}
|
|
330
|
-
function defaultJwksUri(agentUrl, finalBrandUrl) {
|
|
331
|
-
let agent;
|
|
332
|
-
try {
|
|
333
|
-
agent = new URL(agentUrl);
|
|
334
|
-
} catch {
|
|
335
|
-
throw new BrandJsonResolverError("invalid_url", `agent.url is not a valid URL`);
|
|
336
|
-
}
|
|
337
|
-
const brand = new URL(finalBrandUrl);
|
|
338
|
-
if (agent.origin !== brand.origin) {
|
|
339
|
-
throw new BrandJsonResolverError(
|
|
340
|
-
"jwks_origin_mismatch",
|
|
341
|
-
`agent.url origin (${agent.origin}) does not match brand.json origin (${brand.origin}); publisher must declare an explicit jwks_uri for cross-origin agents`
|
|
342
|
-
);
|
|
343
|
-
}
|
|
344
|
-
return `${agent.origin}/.well-known/jwks.json`;
|
|
345
|
-
}
|
|
346
|
-
function describeSelector(selector) {
|
|
347
|
-
const parts = [`type=${selector.agentType}`];
|
|
348
|
-
if (selector.agentId !== void 0) parts.push(`id=${selector.agentId}`);
|
|
349
|
-
if (selector.brandId !== void 0) parts.push(`brand=${selector.brandId}`);
|
|
350
|
-
return parts.join(" ");
|
|
351
|
-
}
|
|
352
|
-
function computeLifetime(cacheControl, maxAge) {
|
|
353
|
-
if (!cacheControl) return maxAge;
|
|
354
|
-
const lower = cacheControl.toLowerCase();
|
|
355
|
-
if (/\bno-store\b|\bno-cache\b/.test(lower)) return 0;
|
|
356
|
-
const match = /max-age\s*=\s*(\d+)/.exec(lower);
|
|
357
|
-
if (match) {
|
|
358
|
-
const serverMax = Number(match[1]);
|
|
359
|
-
if (Number.isFinite(serverMax)) return Math.min(serverMax, maxAge);
|
|
360
|
-
}
|
|
361
|
-
return maxAge;
|
|
362
|
-
}
|
|
363
301
|
export {
|
|
364
302
|
BrandJsonJwksResolver,
|
|
365
303
|
BrandJsonResolverError,
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { ADCPError } from '../errors/index.mjs';
|
|
2
2
|
import type { RequestSigningErrorCode } from '../types/core.generated.mjs';
|
|
3
|
-
|
|
3
|
+
import { RequestSigningErrorCodeMetadata } from '../types/enums.generated.mjs';
|
|
4
|
+
export { RequestSigningErrorCodeMetadata };
|
|
4
5
|
/** Canonical rc.6 request-signing transport vocabulary. */
|
|
5
6
|
export type RequestSignatureErrorCode = RequestSigningErrorCode;
|
|
6
7
|
export declare class RequestSignatureError extends ADCPError {
|
|
7
8
|
readonly code: RequestSignatureErrorCode;
|
|
8
9
|
readonly failedStep: number;
|
|
9
|
-
|
|
10
|
+
readonly recovery: 'transient' | 'correctable' | 'terminal';
|
|
11
|
+
constructor(code: RequestSignatureErrorCode, failedStep: number, message: string, details?: unknown, recovery?: 'transient' | 'correctable' | 'terminal');
|
|
10
12
|
}
|
|
11
13
|
/**
|
|
12
14
|
* Error codes surfaced by the RFC 9421 webhook-signing verifier. Maps onto
|
|
@@ -24,7 +26,9 @@ export type WebhookSignatureErrorCode = 'webhook_signature_header_malformed' | '
|
|
|
24
26
|
export declare class WebhookSignatureError extends ADCPError {
|
|
25
27
|
readonly code: WebhookSignatureErrorCode;
|
|
26
28
|
readonly failedStep: number;
|
|
27
|
-
|
|
29
|
+
/** Local retry signal; the wire error code stays in the webhook vocabulary. */
|
|
30
|
+
readonly retryable: boolean;
|
|
31
|
+
constructor(code: WebhookSignatureErrorCode, failedStep: number, message: string, details?: unknown, retryable?: boolean);
|
|
28
32
|
}
|
|
29
33
|
export type ResponseSignatureErrorCode = 'response_signature_key_purpose_invalid';
|
|
30
34
|
export declare class ResponseSignatureError extends ADCPError {
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { ADCPError } from '../errors';
|
|
2
2
|
import type { RequestSigningErrorCode } from '../types/core.generated';
|
|
3
|
-
|
|
3
|
+
import { RequestSigningErrorCodeMetadata } from '../types/enums.generated';
|
|
4
|
+
export { RequestSigningErrorCodeMetadata };
|
|
4
5
|
/** Canonical rc.6 request-signing transport vocabulary. */
|
|
5
6
|
export type RequestSignatureErrorCode = RequestSigningErrorCode;
|
|
6
7
|
export declare class RequestSignatureError extends ADCPError {
|
|
7
8
|
readonly code: RequestSignatureErrorCode;
|
|
8
9
|
readonly failedStep: number;
|
|
9
|
-
|
|
10
|
+
readonly recovery: 'transient' | 'correctable' | 'terminal';
|
|
11
|
+
constructor(code: RequestSignatureErrorCode, failedStep: number, message: string, details?: unknown, recovery?: 'transient' | 'correctable' | 'terminal');
|
|
10
12
|
}
|
|
11
13
|
/**
|
|
12
14
|
* Error codes surfaced by the RFC 9421 webhook-signing verifier. Maps onto
|
|
@@ -24,7 +26,9 @@ export type WebhookSignatureErrorCode = 'webhook_signature_header_malformed' | '
|
|
|
24
26
|
export declare class WebhookSignatureError extends ADCPError {
|
|
25
27
|
readonly code: WebhookSignatureErrorCode;
|
|
26
28
|
readonly failedStep: number;
|
|
27
|
-
|
|
29
|
+
/** Local retry signal; the wire error code stays in the webhook vocabulary. */
|
|
30
|
+
readonly retryable: boolean;
|
|
31
|
+
constructor(code: WebhookSignatureErrorCode, failedStep: number, message: string, details?: unknown, retryable?: boolean);
|
|
28
32
|
}
|
|
29
33
|
export type ResponseSignatureErrorCode = 'response_signature_key_purpose_invalid';
|
|
30
34
|
export declare class ResponseSignatureError extends ADCPError {
|
|
@@ -30,19 +30,24 @@ var import_enums = require('../types/enums.generated.js');
|
|
|
30
30
|
class RequestSignatureError extends import_errors.ADCPError {
|
|
31
31
|
code;
|
|
32
32
|
failedStep;
|
|
33
|
-
|
|
33
|
+
recovery;
|
|
34
|
+
constructor(code, failedStep, message, details, recovery) {
|
|
34
35
|
super(message, details);
|
|
35
36
|
this.code = code;
|
|
36
37
|
this.failedStep = failedStep;
|
|
38
|
+
this.recovery = recovery ?? import_enums.RequestSigningErrorCodeMetadata[code].recovery;
|
|
37
39
|
}
|
|
38
40
|
}
|
|
39
41
|
class WebhookSignatureError extends import_errors.ADCPError {
|
|
40
42
|
code;
|
|
41
43
|
failedStep;
|
|
42
|
-
|
|
44
|
+
/** Local retry signal; the wire error code stays in the webhook vocabulary. */
|
|
45
|
+
retryable;
|
|
46
|
+
constructor(code, failedStep, message, details, retryable = false) {
|
|
43
47
|
super(message, details);
|
|
44
48
|
this.code = code;
|
|
45
49
|
this.failedStep = failedStep;
|
|
50
|
+
this.retryable = retryable;
|
|
46
51
|
}
|
|
47
52
|
}
|
|
48
53
|
class ResponseSignatureError extends import_errors.ADCPError {
|
|
@@ -3,19 +3,24 @@ import { RequestSigningErrorCodeMetadata } from "../types/enums.generated.mjs";
|
|
|
3
3
|
class RequestSignatureError extends ADCPError {
|
|
4
4
|
code;
|
|
5
5
|
failedStep;
|
|
6
|
-
|
|
6
|
+
recovery;
|
|
7
|
+
constructor(code, failedStep, message, details, recovery) {
|
|
7
8
|
super(message, details);
|
|
8
9
|
this.code = code;
|
|
9
10
|
this.failedStep = failedStep;
|
|
11
|
+
this.recovery = recovery ?? RequestSigningErrorCodeMetadata[code].recovery;
|
|
10
12
|
}
|
|
11
13
|
}
|
|
12
14
|
class WebhookSignatureError extends ADCPError {
|
|
13
15
|
code;
|
|
14
16
|
failedStep;
|
|
15
|
-
|
|
17
|
+
/** Local retry signal; the wire error code stays in the webhook vocabulary. */
|
|
18
|
+
retryable;
|
|
19
|
+
constructor(code, failedStep, message, details, retryable = false) {
|
|
16
20
|
super(message, details);
|
|
17
21
|
this.code = code;
|
|
18
22
|
this.failedStep = failedStep;
|
|
23
|
+
this.retryable = retryable;
|
|
19
24
|
}
|
|
20
25
|
}
|
|
21
26
|
class ResponseSignatureError extends ADCPError {
|
|
@@ -15,6 +15,10 @@ export interface HttpsJwksResolverOptions {
|
|
|
15
15
|
* longer. Default 3600s (1 hour).
|
|
16
16
|
*/
|
|
17
17
|
maxAgeSeconds?: number;
|
|
18
|
+
/** Protocol cache floor. Defaults to 0; governance uses 60 seconds. */
|
|
19
|
+
minCacheAgeSeconds?: number;
|
|
20
|
+
/** Reject refresh failures instead of serving an expired snapshot. Default false. */
|
|
21
|
+
failClosed?: boolean;
|
|
18
22
|
/** Allow `http://` / private-IP JWKS URLs (dev loops only). Default false. */
|
|
19
23
|
allowPrivateIp?: boolean;
|
|
20
24
|
/** DNS resolver forwarded to the SSRF-safe fetch path. Defaults to `dns/promises.lookup`. */
|
|
@@ -45,11 +49,14 @@ export declare class HttpsJwksResolver implements JwksResolver {
|
|
|
45
49
|
private readonly url;
|
|
46
50
|
private readonly minCooldown;
|
|
47
51
|
private readonly maxAge;
|
|
52
|
+
private readonly minCacheAge;
|
|
53
|
+
private readonly failClosed;
|
|
48
54
|
private readonly allowPrivateIp;
|
|
49
55
|
private readonly lookup;
|
|
50
56
|
private readonly now;
|
|
51
57
|
private cache;
|
|
52
58
|
private inFlight;
|
|
59
|
+
private lastAttemptAt;
|
|
53
60
|
constructor(url: string, options?: HttpsJwksResolverOptions);
|
|
54
61
|
resolve(keyid: string): Promise<AdcpJsonWebKey | null>;
|
|
55
62
|
/**
|
|
@@ -15,6 +15,10 @@ export interface HttpsJwksResolverOptions {
|
|
|
15
15
|
* longer. Default 3600s (1 hour).
|
|
16
16
|
*/
|
|
17
17
|
maxAgeSeconds?: number;
|
|
18
|
+
/** Protocol cache floor. Defaults to 0; governance uses 60 seconds. */
|
|
19
|
+
minCacheAgeSeconds?: number;
|
|
20
|
+
/** Reject refresh failures instead of serving an expired snapshot. Default false. */
|
|
21
|
+
failClosed?: boolean;
|
|
18
22
|
/** Allow `http://` / private-IP JWKS URLs (dev loops only). Default false. */
|
|
19
23
|
allowPrivateIp?: boolean;
|
|
20
24
|
/** DNS resolver forwarded to the SSRF-safe fetch path. Defaults to `dns/promises.lookup`. */
|
|
@@ -45,11 +49,14 @@ export declare class HttpsJwksResolver implements JwksResolver {
|
|
|
45
49
|
private readonly url;
|
|
46
50
|
private readonly minCooldown;
|
|
47
51
|
private readonly maxAge;
|
|
52
|
+
private readonly minCacheAge;
|
|
53
|
+
private readonly failClosed;
|
|
48
54
|
private readonly allowPrivateIp;
|
|
49
55
|
private readonly lookup;
|
|
50
56
|
private readonly now;
|
|
51
57
|
private cache;
|
|
52
58
|
private inFlight;
|
|
59
|
+
private lastAttemptAt;
|
|
53
60
|
constructor(url: string, options?: HttpsJwksResolverOptions);
|
|
54
61
|
resolve(keyid: string): Promise<AdcpJsonWebKey | null>;
|
|
55
62
|
/**
|
|
@@ -22,40 +22,61 @@ __export(jwks_https_exports, {
|
|
|
22
22
|
});
|
|
23
23
|
module.exports = __toCommonJS(jwks_https_exports);
|
|
24
24
|
var import_net = require('../net/index.js');
|
|
25
|
+
var import_strict_json = require('./agent-resolver/strict-json.js');
|
|
25
26
|
const DEFAULT_MIN_COOLDOWN_SECONDS = 30;
|
|
26
27
|
const DEFAULT_MAX_AGE_SECONDS = 3600;
|
|
27
28
|
class HttpsJwksResolver {
|
|
28
29
|
url;
|
|
29
30
|
minCooldown;
|
|
30
31
|
maxAge;
|
|
32
|
+
minCacheAge;
|
|
33
|
+
failClosed;
|
|
31
34
|
allowPrivateIp;
|
|
32
35
|
lookup;
|
|
33
36
|
now;
|
|
34
37
|
cache;
|
|
35
38
|
inFlight;
|
|
39
|
+
lastAttemptAt = Number.NEGATIVE_INFINITY;
|
|
36
40
|
constructor(url, options = {}) {
|
|
37
41
|
this.url = url;
|
|
38
42
|
this.minCooldown = options.minCooldownSeconds ?? DEFAULT_MIN_COOLDOWN_SECONDS;
|
|
39
43
|
this.maxAge = options.maxAgeSeconds ?? DEFAULT_MAX_AGE_SECONDS;
|
|
44
|
+
this.minCacheAge = Math.min(options.minCacheAgeSeconds ?? 0, this.maxAge);
|
|
45
|
+
if (!Number.isFinite(this.maxAge) || this.maxAge < 0) {
|
|
46
|
+
throw new TypeError("maxAgeSeconds must be a finite non-negative number");
|
|
47
|
+
}
|
|
48
|
+
if (!Number.isFinite(this.minCooldown) || this.minCooldown < 0) {
|
|
49
|
+
throw new TypeError("minCooldownSeconds must be a finite non-negative number");
|
|
50
|
+
}
|
|
51
|
+
if (!Number.isFinite(options.minCacheAgeSeconds ?? 0) || (options.minCacheAgeSeconds ?? 0) < 0) {
|
|
52
|
+
throw new TypeError("minCacheAgeSeconds must be a finite non-negative number");
|
|
53
|
+
}
|
|
54
|
+
this.failClosed = options.failClosed === true;
|
|
40
55
|
this.allowPrivateIp = options.allowPrivateIp ?? false;
|
|
41
56
|
this.lookup = options.lookup;
|
|
42
57
|
this.now = options.now ?? (() => Math.floor(Date.now() / 1e3));
|
|
43
58
|
}
|
|
44
59
|
async resolve(keyid) {
|
|
45
60
|
if (!this.cache) {
|
|
61
|
+
if (!this.inFlight && this.now() - this.lastAttemptAt < this.minCooldown) {
|
|
62
|
+
throw new Error("JWKS lookup is in its retry cooldown");
|
|
63
|
+
}
|
|
46
64
|
await this.refresh();
|
|
47
65
|
} else if (this.cache.keys.has(keyid)) {
|
|
48
|
-
if (this.now()
|
|
66
|
+
if (this.now() < this.cache.expiresAt) {
|
|
49
67
|
return this.cache.keys.get(keyid) ?? null;
|
|
50
68
|
}
|
|
51
|
-
if (this.now() - this.
|
|
52
|
-
await this.refresh().catch(() => {
|
|
69
|
+
if (this.now() - this.lastAttemptAt >= this.minCooldown) {
|
|
70
|
+
await this.refresh().catch((error) => {
|
|
71
|
+
if (this.failClosed) throw error;
|
|
53
72
|
});
|
|
54
73
|
}
|
|
55
|
-
} else if (this.now() - this.
|
|
56
|
-
await this.refresh().catch(() => {
|
|
74
|
+
} else if (this.now() - this.lastAttemptAt >= this.minCooldown) {
|
|
75
|
+
await this.refresh().catch((error) => {
|
|
76
|
+
if (this.failClosed) throw error;
|
|
57
77
|
});
|
|
58
78
|
}
|
|
79
|
+
if (this.failClosed && this.cache && this.now() >= this.cache.expiresAt) throw new Error("JWKS snapshot expired");
|
|
59
80
|
return this.cache?.keys.get(keyid) ?? null;
|
|
60
81
|
}
|
|
61
82
|
/**
|
|
@@ -72,6 +93,7 @@ class HttpsJwksResolver {
|
|
|
72
93
|
await this.inFlight;
|
|
73
94
|
return;
|
|
74
95
|
}
|
|
96
|
+
this.lastAttemptAt = this.now();
|
|
75
97
|
this.inFlight = this.doRefresh().finally(() => {
|
|
76
98
|
this.inFlight = void 0;
|
|
77
99
|
});
|
|
@@ -103,7 +125,7 @@ class HttpsJwksResolver {
|
|
|
103
125
|
const text = Buffer.from(res.body).toString("utf8");
|
|
104
126
|
let parsed;
|
|
105
127
|
try {
|
|
106
|
-
parsed =
|
|
128
|
+
parsed = (0, import_strict_json.parseStrictJson)(text);
|
|
107
129
|
} catch {
|
|
108
130
|
throw new Error(`JWKS fetch ${this.url} returned non-JSON body`);
|
|
109
131
|
}
|
|
@@ -118,6 +140,7 @@ class HttpsJwksResolver {
|
|
|
118
140
|
for (const entry of keysField) {
|
|
119
141
|
if (entry && typeof entry === "object" && typeof entry.kid === "string") {
|
|
120
142
|
const jwk = entry;
|
|
143
|
+
if (byKid.has(jwk.kid)) throw new Error("JWKS has duplicate key identifiers");
|
|
121
144
|
byKid.set(jwk.kid, jwk);
|
|
122
145
|
}
|
|
123
146
|
}
|
|
@@ -130,11 +153,11 @@ class HttpsJwksResolver {
|
|
|
130
153
|
}
|
|
131
154
|
computeExpiry(headers) {
|
|
132
155
|
const cacheCtl = headers["cache-control"]?.toLowerCase() ?? "";
|
|
133
|
-
if (/\bno-store\b|\bno-cache\b/.test(cacheCtl)) return this.now();
|
|
156
|
+
if (/\bno-store\b|\bno-cache\b/.test(cacheCtl)) return this.now() + this.minCacheAge;
|
|
134
157
|
const match = /max-age\s*=\s*(\d+)/.exec(cacheCtl);
|
|
135
158
|
if (match) {
|
|
136
159
|
const serverMax = Number(match[1]);
|
|
137
|
-
if (Number.isFinite(serverMax)) return this.now() + Math.min(serverMax, this.maxAge);
|
|
160
|
+
if (Number.isFinite(serverMax)) return this.now() + Math.max(this.minCacheAge, Math.min(serverMax, this.maxAge));
|
|
138
161
|
}
|
|
139
162
|
return this.now() + this.maxAge;
|
|
140
163
|
}
|