@adcp/sdk 16.0.0-beta.1 → 16.0.0-beta.3
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/README.md +54 -0
- package/dist/lib/core/ConversationTypes.d.mts +8 -3
- package/dist/lib/core/ConversationTypes.d.ts +8 -3
- package/dist/lib/core/SingleAgentClient.d.mts +15 -4
- package/dist/lib/core/SingleAgentClient.d.ts +15 -4
- package/dist/lib/core/SingleAgentClient.js +29 -8
- package/dist/lib/core/SingleAgentClient.mjs +29 -8
- package/dist/lib/core/TaskExecutor.js +5 -1
- package/dist/lib/core/TaskExecutor.mjs +5 -1
- package/dist/lib/errors/index.d.mts +9 -4
- package/dist/lib/errors/index.d.ts +9 -4
- package/dist/lib/identity-3.3/generated-compat.d.mts +34 -0
- package/dist/lib/identity-3.3/generated-compat.d.ts +34 -0
- package/dist/lib/identity-3.3/generated-compat.js +16 -0
- package/dist/lib/identity-3.3/generated-compat.mjs +0 -0
- package/dist/lib/identity-3.3/index.d.mts +2 -2
- package/dist/lib/identity-3.3/index.d.ts +2 -2
- package/dist/lib/identity-3.3/index.js +4 -0
- package/dist/lib/identity-3.3/index.mjs +5 -1
- package/dist/lib/identity-3.3/lifecycle.d.mts +17 -5
- package/dist/lib/identity-3.3/lifecycle.d.ts +17 -5
- package/dist/lib/identity-3.3/lifecycle.js +32 -5
- package/dist/lib/identity-3.3/lifecycle.mjs +38 -6
- package/dist/lib/identity-3.3/revocation.d.mts +39 -3
- package/dist/lib/identity-3.3/revocation.d.ts +39 -3
- package/dist/lib/identity-3.3/revocation.js +88 -18
- package/dist/lib/identity-3.3/revocation.mjs +89 -19
- package/dist/lib/identity-3.3/server.d.mts +12 -0
- package/dist/lib/identity-3.3/server.d.ts +12 -0
- package/dist/lib/identity-3.3/server.js +103 -43
- package/dist/lib/identity-3.3/server.mjs +111 -45
- package/dist/lib/identity-3.3/trust.d.mts +62 -1
- package/dist/lib/identity-3.3/trust.d.ts +62 -1
- package/dist/lib/identity-3.3/trust.js +126 -21
- package/dist/lib/identity-3.3/trust.mjs +124 -21
- package/dist/lib/identity-3.3/types.d.mts +39 -11
- package/dist/lib/identity-3.3/types.d.ts +39 -11
- package/dist/lib/index.d.mts +3 -3
- package/dist/lib/index.d.ts +3 -3
- package/dist/lib/index.js +8 -0
- package/dist/lib/index.mjs +9 -1
- package/dist/lib/negotiation/types.d.mts +20 -39
- package/dist/lib/negotiation/types.d.ts +20 -39
- package/dist/lib/protocols/index.d.mts +61 -25
- package/dist/lib/protocols/index.d.ts +61 -25
- package/dist/lib/protocols/index.js +11 -6
- package/dist/lib/protocols/index.mjs +5 -2
- package/dist/lib/protocols/mcp-modern.js +1 -0
- package/dist/lib/protocols/mcp-modern.mjs +2 -1
- package/dist/lib/protocols/mcp.js +1 -0
- package/dist/lib/protocols/mcp.mjs +2 -1
- package/dist/lib/protocols/responseSizeLimit.d.mts +91 -5
- package/dist/lib/protocols/responseSizeLimit.d.ts +91 -5
- package/dist/lib/protocols/responseSizeLimit.js +195 -31
- package/dist/lib/protocols/responseSizeLimit.mjs +192 -31
- package/dist/lib/reporting/bigquery/index.js +8 -0
- package/dist/lib/reporting/bigquery/index.mjs +8 -0
- package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
- package/dist/lib/server/auth-signature.d.mts +6 -1
- package/dist/lib/server/auth-signature.d.ts +6 -1
- package/dist/lib/server/auth-signature.js +24 -13
- package/dist/lib/server/auth-signature.mjs +29 -13
- package/dist/lib/server/auth.d.mts +2 -2
- package/dist/lib/server/auth.d.ts +2 -2
- package/dist/lib/server/create-adcp-server.js +31 -10
- package/dist/lib/server/create-adcp-server.mjs +37 -11
- package/dist/lib/signing/crypto.d.mts +15 -0
- package/dist/lib/signing/crypto.d.ts +15 -0
- package/dist/lib/signing/crypto.js +83 -3
- package/dist/lib/signing/crypto.mjs +86 -3
- package/dist/lib/signing/fetch-async.js +1 -1
- package/dist/lib/signing/fetch-async.mjs +1 -1
- package/dist/lib/signing/fetch.js +1 -1
- package/dist/lib/signing/fetch.mjs +1 -1
- package/dist/lib/signing/middleware.js +9 -0
- package/dist/lib/signing/middleware.mjs +14 -0
- package/dist/lib/signing/parser.d.mts +8 -5
- package/dist/lib/signing/parser.d.ts +8 -5
- package/dist/lib/signing/parser.js +77 -61
- package/dist/lib/signing/parser.mjs +77 -61
- package/dist/lib/signing/revocation.js +3 -0
- package/dist/lib/signing/revocation.mjs +3 -0
- package/dist/lib/signing/server.d.mts +1 -1
- package/dist/lib/signing/server.d.ts +1 -1
- package/dist/lib/signing/server.js +2 -0
- package/dist/lib/signing/server.mjs +2 -1
- package/dist/lib/signing/signer.js +2 -8
- package/dist/lib/signing/signer.mjs +3 -9
- package/dist/lib/signing/testing.js +2 -6
- package/dist/lib/signing/testing.mjs +3 -7
- package/dist/lib/signing/verifier.js +3 -3
- package/dist/lib/signing/verifier.mjs +4 -4
- package/dist/lib/signing/web-bot-auth.d.mts +84 -10
- package/dist/lib/signing/web-bot-auth.d.ts +84 -10
- package/dist/lib/signing/web-bot-auth.js +278 -88
- package/dist/lib/signing/web-bot-auth.mjs +279 -94
- package/dist/lib/signing/webhook-verifier.js +3 -3
- package/dist/lib/signing/webhook-verifier.mjs +4 -4
- package/dist/lib/substitution/compile.js +2 -1
- package/dist/lib/substitution/compile.mjs +2 -1
- package/dist/lib/substitution/consent-macros.d.mts +4 -0
- package/dist/lib/substitution/consent-macros.d.ts +4 -0
- package/dist/lib/substitution/consent-macros.js +45 -0
- package/dist/lib/substitution/consent-macros.mjs +20 -0
- package/dist/lib/substitution/index.d.mts +1 -1
- package/dist/lib/substitution/index.d.ts +1 -1
- package/dist/lib/substitution/index.js +2 -0
- package/dist/lib/substitution/index.mjs +2 -1
- package/dist/lib/substitution/translate.d.mts +15 -2
- package/dist/lib/substitution/translate.d.ts +15 -2
- package/dist/lib/substitution/translate.js +148 -26
- package/dist/lib/substitution/translate.mjs +147 -26
- package/dist/lib/testing/client.js +2 -1
- package/dist/lib/testing/client.mjs +2 -1
- package/dist/lib/testing/scenarios/capabilities.js +1 -1
- package/dist/lib/testing/scenarios/capabilities.mjs +1 -1
- package/dist/lib/testing/storyboard/request-signing/builder.js +2 -5
- package/dist/lib/testing/storyboard/request-signing/builder.mjs +3 -6
- package/dist/lib/testing/storyboard/signer-grader/grader.js +2 -7
- package/dist/lib/testing/storyboard/signer-grader/grader.mjs +2 -7
- package/dist/lib/testing/storyboard/validations.d.mts +1 -1
- package/dist/lib/testing/storyboard/validations.d.ts +1 -1
- package/dist/lib/types/enums.generated.d.mts +3 -0
- package/dist/lib/types/enums.generated.d.ts +3 -0
- package/dist/lib/types/enums.generated.js +9 -0
- package/dist/lib/types/enums.generated.mjs +6 -0
- package/dist/lib/v2/projection/catalog-snapshot.js +3 -1
- package/dist/lib/v2/projection/catalog-snapshot.mjs +3 -1
- package/dist/lib/v2/projection/creative-delivery.d.mts +31 -9
- package/dist/lib/v2/projection/creative-delivery.d.ts +31 -9
- package/dist/lib/v2/projection/index.d.mts +1 -1
- package/dist/lib/v2/projection/index.d.ts +1 -1
- package/dist/lib/v2/projection/index.js +2 -0
- package/dist/lib/v2/projection/index.mjs +2 -0
- package/dist/lib/v2/projection/legacy-routes.d.mts +10 -0
- package/dist/lib/v2/projection/legacy-routes.d.ts +10 -0
- package/dist/lib/v2/projection/legacy-routes.js +7 -0
- package/dist/lib/v2/projection/legacy-routes.mjs +6 -0
- package/dist/lib/validation/schema-loader.js +25 -0
- package/dist/lib/validation/schema-loader.mjs +24 -0
- 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/docs/TYPE-SUMMARY.md +7 -2
- package/docs/guides/DISCOVERY-CACHE-AND-SESSION-REUSE.md +3 -2
- package/docs/guides/REPORTING-LEDGER.md +15 -5
- package/docs/guides/REPORTING-OPERATIONS.md +17 -11
- package/docs/guides/UNIVERSAL-MACRO-COMPILATION.md +38 -0
- package/docs/guides/VALIDATE-YOUR-AGENT.md +12 -0
- package/docs/llms.txt +2 -2
- package/docs/migration-12-to-13.md +8 -0
- package/docs/migration-14.x-rc-worksheet.md +4 -4
- package/docs/migration-agent-identity-3.3.md +150 -11
- package/package.json +4 -1
|
@@ -6,7 +6,7 @@ import { parseStrictJson } from "../signing/agent-resolver/strict-json.mjs";
|
|
|
6
6
|
import {
|
|
7
7
|
SignatureDirectoryResolver,
|
|
8
8
|
WebBotAuthError,
|
|
9
|
-
|
|
9
|
+
WebBotAuthUnavailableError,
|
|
10
10
|
jwkThumbprint
|
|
11
11
|
} from "../signing/web-bot-auth.mjs";
|
|
12
12
|
const REASONS = [
|
|
@@ -24,28 +24,51 @@ const REASONS = [
|
|
|
24
24
|
import { mergeRevocationDetails } from "../signing/revocation.mjs";
|
|
25
25
|
import { mergeRevocationDetails as mergeRevocationDetail } from "../signing/revocation.mjs";
|
|
26
26
|
class InMemoryOriginRevocationStore {
|
|
27
|
-
constructor(limits = { perOrigin: 16384, total: 1e5 }) {
|
|
28
|
-
this.limits = limits;
|
|
29
|
-
}
|
|
30
|
-
limits;
|
|
31
27
|
durability = "process-local";
|
|
32
28
|
revoked = /* @__PURE__ */ new Map();
|
|
29
|
+
originKeys = /* @__PURE__ */ new Map();
|
|
30
|
+
operatorKeys = /* @__PURE__ */ new Map();
|
|
31
|
+
limits;
|
|
32
|
+
constructor(limits = {}) {
|
|
33
|
+
this.limits = {
|
|
34
|
+
perOrigin: limits.perOrigin ?? 16384,
|
|
35
|
+
perOperator: limits.perOperator ?? 16384,
|
|
36
|
+
total: limits.total ?? 1e5
|
|
37
|
+
};
|
|
38
|
+
if (Object.values(this.limits).some((value) => !Number.isSafeInteger(value) || value < 1))
|
|
39
|
+
throw new TypeError("Revocation limits must be positive safe integers");
|
|
40
|
+
}
|
|
33
41
|
async merge(origin, snapshot) {
|
|
34
42
|
origin = canonicalAgentOrigin(origin);
|
|
43
|
+
const operator = operatorOf(origin);
|
|
35
44
|
const additions = new Set(snapshot.revoked_kids.filter((kid) => !this.revoked.has(JSON.stringify([origin, kid]))));
|
|
36
|
-
const
|
|
37
|
-
if (
|
|
45
|
+
const originCount = this.originKeys.get(origin) ?? 0, operatorCount = this.operatorKeys.get(operator) ?? 0;
|
|
46
|
+
if (originCount + additions.size > this.limits.perOrigin || operatorCount + additions.size > this.limits.perOperator || this.revoked.size + additions.size > this.limits.total)
|
|
38
47
|
throw new WebBotAuthError(403, "revocation_storage_limit");
|
|
39
48
|
for (const kid of snapshot.revoked_kids) {
|
|
40
49
|
const key = JSON.stringify([origin, kid]), prior = this.revoked.get(key), detail = snapshot.revocation_details?.[kid] ?? {};
|
|
41
50
|
this.revoked.set(key, mergeRevocationDetail(prior, detail));
|
|
42
51
|
}
|
|
52
|
+
if (additions.size) {
|
|
53
|
+
this.originKeys.set(origin, originCount + additions.size);
|
|
54
|
+
this.operatorKeys.set(operator, operatorCount + additions.size);
|
|
55
|
+
}
|
|
43
56
|
}
|
|
44
57
|
async get(origin, keyid) {
|
|
45
58
|
const entry = this.revoked.get(JSON.stringify([canonicalAgentOrigin(origin), keyid]));
|
|
46
59
|
return entry && { ...entry };
|
|
47
60
|
}
|
|
48
61
|
}
|
|
62
|
+
function operatorOf(origin) {
|
|
63
|
+
try {
|
|
64
|
+
return eTldPlusOne(origin);
|
|
65
|
+
} catch {
|
|
66
|
+
return new URL(origin).hostname;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const MAX_POLLING_SECONDS = 1800;
|
|
70
|
+
class RevocationListAbsentError extends Error {
|
|
71
|
+
}
|
|
49
72
|
class OriginRevocationResolver {
|
|
50
73
|
constructor(options) {
|
|
51
74
|
this.options = options;
|
|
@@ -61,31 +84,73 @@ class OriginRevocationResolver {
|
|
|
61
84
|
pending = /* @__PURE__ */ new Map();
|
|
62
85
|
failures = /* @__PURE__ */ new Map();
|
|
63
86
|
persistenceFailures = /* @__PURE__ */ new Set();
|
|
87
|
+
absentUntil = /* @__PURE__ */ new Map();
|
|
88
|
+
/**
|
|
89
|
+
* Compatibility wrapper over {@link status}: `true` when revoked, `false`
|
|
90
|
+
* when the list is current, and `revocation_unavailable` when the identity
|
|
91
|
+
* publishes no list (`absent`) or its status is not known (`unknown`).
|
|
92
|
+
*/
|
|
64
93
|
async isRevoked(originUrl, keyid) {
|
|
94
|
+
const state = await this.status(originUrl, keyid);
|
|
95
|
+
if (state === "revoked") return true;
|
|
96
|
+
if (state === "current") return false;
|
|
97
|
+
throw new WebBotAuthError(403, state === "absent" ? "revocation_absent" : "revocation_unavailable");
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Revocation status of `keyid` at `origin`:
|
|
101
|
+
* - `revoked`: a stored (permanent) revocation covers the key.
|
|
102
|
+
* - `current`: a signed list is current, or within `next_update` + grace.
|
|
103
|
+
* - `absent`: the origin serves no list (404/410). The identity is still
|
|
104
|
+
* established but cannot be bound for spend.
|
|
105
|
+
* - `unknown`: no list could be verified and grace has passed (or there was
|
|
106
|
+
* never a list). The identity is not established.
|
|
107
|
+
* Throws {@link WebBotAuthUnavailableError} (`revocation_persistence_failed`)
|
|
108
|
+
* when the history store fails: an infrastructure outage, never a
|
|
109
|
+
* revocation outcome.
|
|
110
|
+
*/
|
|
111
|
+
async status(originUrl, keyid) {
|
|
112
|
+
try {
|
|
113
|
+
return await this.currentStatus(originUrl, keyid);
|
|
114
|
+
} catch (error) {
|
|
115
|
+
if (error instanceof WebBotAuthError && error.reason === "revocation_persistence_failed")
|
|
116
|
+
throw new WebBotAuthUnavailableError("revocation_persistence_failed", error);
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
async currentStatus(originUrl, keyid) {
|
|
65
121
|
const origin = canonicalAgentOrigin(originUrl), now = this.now();
|
|
66
|
-
if (await this.readStored(origin, keyid)) return
|
|
122
|
+
if (await this.readStored(origin, keyid)) return "revoked";
|
|
123
|
+
const settled = async () => {
|
|
124
|
+
if (await this.readStored(origin, keyid)) return "revoked";
|
|
125
|
+
if ((this.absentUntil.get(origin) ?? 0) > now) return "absent";
|
|
126
|
+
return (this.freshness.get(origin) ?? 0) > now || (this.graceUntil.get(origin) ?? 0) > now ? "current" : "unknown";
|
|
127
|
+
};
|
|
67
128
|
if ((this.freshness.get(origin) ?? 0) <= now) {
|
|
68
129
|
if ((this.failures.get(origin) ?? 0) > now) {
|
|
69
130
|
if (this.persistenceFailures.has(origin)) throw new WebBotAuthError(403, "revocation_persistence_failed");
|
|
70
|
-
|
|
71
|
-
throw new WebBotAuthError(403, "revocation_unavailable");
|
|
131
|
+
return settled();
|
|
72
132
|
}
|
|
73
133
|
let promise = this.pending.get(origin);
|
|
74
134
|
if (!promise) {
|
|
75
|
-
if (this.pending.size >= 16 || [...this.pending.keys()].filter((pendingOrigin) =>
|
|
76
|
-
|
|
135
|
+
if (this.pending.size >= 16 || [...this.pending.keys()].filter((pendingOrigin) => operatorOf(pendingOrigin) === operatorOf(origin)).length >= 2) {
|
|
136
|
+
return settled();
|
|
77
137
|
}
|
|
78
138
|
promise = this.refresh(origin).catch((error) => {
|
|
79
139
|
if (this.failures.size >= (this.options.maxOrigins ?? 1e3)) {
|
|
80
140
|
const oldest = this.failures.keys().next().value;
|
|
81
141
|
this.failures.delete(oldest);
|
|
82
142
|
this.persistenceFailures.delete(oldest);
|
|
143
|
+
this.absentUntil.delete(oldest);
|
|
83
144
|
}
|
|
84
145
|
this.failures.set(origin, now + 60);
|
|
85
146
|
if (error instanceof WebBotAuthError && error.reason === "revocation_persistence_failed") {
|
|
86
147
|
this.persistenceFailures.add(origin);
|
|
87
148
|
this.graceUntil.delete(origin);
|
|
88
149
|
}
|
|
150
|
+
if (error.cause instanceof RevocationListAbsentError) {
|
|
151
|
+
this.absentUntil.set(origin, now + 60);
|
|
152
|
+
this.graceUntil.delete(origin);
|
|
153
|
+
}
|
|
89
154
|
throw error;
|
|
90
155
|
});
|
|
91
156
|
this.pending.set(origin, promise);
|
|
@@ -94,12 +159,11 @@ class OriginRevocationResolver {
|
|
|
94
159
|
await promise;
|
|
95
160
|
} catch (error) {
|
|
96
161
|
if (error instanceof WebBotAuthError && error.reason === "revocation_persistence_failed") throw error;
|
|
97
|
-
if ((this.graceUntil.get(origin) ?? 0) <= now) throw error;
|
|
98
162
|
} finally {
|
|
99
163
|
if (this.pending.get(origin) === promise) this.pending.delete(origin);
|
|
100
164
|
}
|
|
101
165
|
}
|
|
102
|
-
return
|
|
166
|
+
return settled();
|
|
103
167
|
}
|
|
104
168
|
async readStored(origin, keyid) {
|
|
105
169
|
try {
|
|
@@ -117,6 +181,7 @@ class OriginRevocationResolver {
|
|
|
117
181
|
maxBodyBytes: 256 * 1024,
|
|
118
182
|
timeoutMs: 1e4
|
|
119
183
|
});
|
|
184
|
+
if (response.status === 404 || response.status === 410) throw new RevocationListAbsentError();
|
|
120
185
|
if (response.status !== 200) throw new Error("Unavailable revocation list");
|
|
121
186
|
const body = new TextDecoder("utf-8", { fatal: true }).decode(response.body);
|
|
122
187
|
let tokens;
|
|
@@ -142,9 +207,9 @@ class OriginRevocationResolver {
|
|
|
142
207
|
if (header.typ !== "adcp-gov-revocation+jws" || typeof header.kid !== "string" || typeof header.alg !== "string" || !["EdDSA", "ES256"].includes(header.alg))
|
|
143
208
|
continue;
|
|
144
209
|
await this.options.directory.resolve(header.kid, origin);
|
|
145
|
-
const
|
|
146
|
-
if (!
|
|
147
|
-
|
|
210
|
+
const checked = this.options.directory.getVerifiedDirectory(origin);
|
|
211
|
+
if (!checked || new URL(checked.proof.request.url).origin !== origin || checked.expires <= this.now())
|
|
212
|
+
continue;
|
|
148
213
|
const jwk = checked.keys.find((key) => key.kid === header.kid);
|
|
149
214
|
if (!jwk) continue;
|
|
150
215
|
const result = await compactVerify(token, await importJWK(jwk, header.alg), {
|
|
@@ -161,7 +226,8 @@ class OriginRevocationResolver {
|
|
|
161
226
|
32
|
|
162
227
|
);
|
|
163
228
|
const updated = Date.parse(snapshot.updated) / 1e3, next = Date.parse(snapshot.next_update) / 1e3, now = this.now();
|
|
164
|
-
if (snapshot.issuer !== origin || !Number.isFinite(updated) || !Number.isFinite(next) ||
|
|
229
|
+
if (snapshot.issuer !== origin || !Number.isFinite(updated) || !Number.isFinite(next) || // Tolerate a publisher clock slightly ahead, as for request `created`.
|
|
230
|
+
updated > now + 60 || next <= now || next <= updated || !Array.isArray(snapshot.revoked_kids) || snapshot.revoked_kids.length > 4096 || snapshot.revoked_kids.some((k) => typeof k !== "string" || k.length > 256) || updated < (this.updated.get(origin) ?? 0))
|
|
165
231
|
throw new Error("Invalid revocation snapshot");
|
|
166
232
|
for (const [kid, detail] of Object.entries(snapshot.revocation_details ?? {}))
|
|
167
233
|
if (!snapshot.revoked_kids.includes(kid) || !detail || detail.reason && !REASONS.includes(detail.reason) || detail.invalidity_date && !Number.isFinite(Date.parse(detail.invalidity_date)))
|
|
@@ -179,11 +245,15 @@ class OriginRevocationResolver {
|
|
|
179
245
|
this.updated.delete(oldest);
|
|
180
246
|
this.graceUntil.delete(oldest);
|
|
181
247
|
}
|
|
182
|
-
this.graceUntil.set(
|
|
248
|
+
this.graceUntil.set(
|
|
249
|
+
origin,
|
|
250
|
+
Math.min(next, now + MAX_POLLING_SECONDS) + 4 * (this.options.pollingIntervalSeconds ?? 300)
|
|
251
|
+
);
|
|
183
252
|
this.updated.set(origin, updated);
|
|
184
253
|
this.freshness.set(origin, Math.min(next, now + (this.options.pollingIntervalSeconds ?? 300)));
|
|
185
254
|
this.failures.delete(origin);
|
|
186
255
|
this.persistenceFailures.delete(origin);
|
|
256
|
+
this.absentUntil.delete(origin);
|
|
187
257
|
} catch (cause) {
|
|
188
258
|
if (cause instanceof WebBotAuthError && cause.reason === "revocation_persistence_failed") throw cause;
|
|
189
259
|
const error = new WebBotAuthError(403, "revocation_unavailable");
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { OriginRevocationStatus } from './revocation.mjs';
|
|
1
2
|
import type { AuthPrincipal } from '../server/auth.mjs';
|
|
2
3
|
import type { HandlerContext, ProtocolHandlers } from '../server/create-adcp-server.mjs';
|
|
3
4
|
import { type VerifiedWebBotSigner } from '../signing/web-bot-auth.mjs';
|
|
@@ -10,6 +11,17 @@ export declare function createIdentity33PrincipalResolver(options: {
|
|
|
10
11
|
lifecycle: OriginIdentityLifecycle;
|
|
11
12
|
anonymousPrincipal?: string;
|
|
12
13
|
anonymousTools?: readonly string[];
|
|
14
|
+
/**
|
|
15
|
+
* Origin revocation, checked here (the identity layer), not during signature
|
|
16
|
+
* verification. A revoked key is refused without a challenge; unknown
|
|
17
|
+
* status leaves the identity unverified; an origin serving no list stays
|
|
18
|
+
* identity-established and cannot be bound or spend. Usually an
|
|
19
|
+
* `OriginRevocationResolver`. Without it, the server must supply the
|
|
20
|
+
* deprecated `webBotAuth.isRevoked`.
|
|
21
|
+
*/
|
|
22
|
+
revocation?: {
|
|
23
|
+
status(origin: string, keyid: string): Promise<OriginRevocationStatus>;
|
|
24
|
+
};
|
|
13
25
|
}): (signers: readonly VerifiedWebBotSigner[], operation: ResolvedOperation) => Promise<AuthPrincipal>;
|
|
14
26
|
/** Pass to createAdcpServer.identity33.authorize; framework runs it before its replay cache. */
|
|
15
27
|
export declare function createIdentity33Authorization<TAccount>(options: {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { OriginRevocationStatus } from './revocation';
|
|
1
2
|
import type { AuthPrincipal } from '../server/auth';
|
|
2
3
|
import type { HandlerContext, ProtocolHandlers } from '../server/create-adcp-server';
|
|
3
4
|
import { type VerifiedWebBotSigner } from '../signing/web-bot-auth';
|
|
@@ -10,6 +11,17 @@ export declare function createIdentity33PrincipalResolver(options: {
|
|
|
10
11
|
lifecycle: OriginIdentityLifecycle;
|
|
11
12
|
anonymousPrincipal?: string;
|
|
12
13
|
anonymousTools?: readonly string[];
|
|
14
|
+
/**
|
|
15
|
+
* Origin revocation, checked here (the identity layer), not during signature
|
|
16
|
+
* verification. A revoked key is refused without a challenge; unknown
|
|
17
|
+
* status leaves the identity unverified; an origin serving no list stays
|
|
18
|
+
* identity-established and cannot be bound or spend. Usually an
|
|
19
|
+
* `OriginRevocationResolver`. Without it, the server must supply the
|
|
20
|
+
* deprecated `webBotAuth.isRevoked`.
|
|
21
|
+
*/
|
|
22
|
+
revocation?: {
|
|
23
|
+
status(origin: string, keyid: string): Promise<OriginRevocationStatus>;
|
|
24
|
+
};
|
|
13
25
|
}): (signers: readonly VerifiedWebBotSigner[], operation: ResolvedOperation) => Promise<AuthPrincipal>;
|
|
14
26
|
/** Pass to createAdcpServer.identity33.authorize; framework runs it before its replay cache. */
|
|
15
27
|
export declare function createIdentity33Authorization<TAccount>(options: {
|
|
@@ -50,6 +50,9 @@ function claimFromContext(ctx) {
|
|
|
50
50
|
const claim = ctx.authInfo?.extra?.identity33;
|
|
51
51
|
return claim && (0, import_web_bot_auth3.isVerifiedWebBotSigner)(claim.signer) ? claim : void 0;
|
|
52
52
|
}
|
|
53
|
+
function anonymousPrincipalFor(origin, prefix = "anonymous") {
|
|
54
|
+
return `${prefix}:${(0, import_crypto.createHash)("sha256").update(origin).digest("hex")}`;
|
|
55
|
+
}
|
|
53
56
|
function isIdentity33UnboundContext(ctx) {
|
|
54
57
|
return claimFromContext(ctx)?.state === "identity-established";
|
|
55
58
|
}
|
|
@@ -67,17 +70,44 @@ function createIdentity33PrincipalResolver(options) {
|
|
|
67
70
|
if (!signers.length || signers.some((s) => !(0, import_web_bot_auth3.isVerifiedWebBotSigner)(s)))
|
|
68
71
|
throw new import_web_bot_auth3.WebBotAuthError(403, "unverified_identity");
|
|
69
72
|
const signer = signers[signers.length - 1];
|
|
73
|
+
let revocation;
|
|
74
|
+
if (options.revocation) {
|
|
75
|
+
revocation = "current";
|
|
76
|
+
for (const each of signers) {
|
|
77
|
+
let status;
|
|
78
|
+
try {
|
|
79
|
+
status = await options.revocation.status(each.origin, each.keyid);
|
|
80
|
+
} catch (error) {
|
|
81
|
+
throw new import_web_bot_auth.WebBotAuthUnavailableError("revocation_persistence_failed", error);
|
|
82
|
+
}
|
|
83
|
+
if (status === "revoked") throw new import_web_bot_auth3.WebBotAuthError(403, "key_revoked");
|
|
84
|
+
if (status === "unknown") throw new import_web_bot_auth3.WebBotAuthError(403, "identity_unverified");
|
|
85
|
+
if (status === "absent") revocation = "absent";
|
|
86
|
+
}
|
|
87
|
+
}
|
|
70
88
|
const revoked = signers.find((s) => s.authorizationFailure);
|
|
71
|
-
|
|
89
|
+
let { binding } = revoked || revocation === "absent" ? { binding: void 0 } : await options.lifecycle.getPrincipal(signer.origin);
|
|
90
|
+
if (binding) {
|
|
91
|
+
try {
|
|
92
|
+
await options.lifecycle.authorizeBound(signer.origin, { readOnly: true });
|
|
93
|
+
} catch (error) {
|
|
94
|
+
if (error instanceof import_trust.TrustMembershipError) binding = void 0;
|
|
95
|
+
else if (error instanceof import_trust.TrustRecordUnavailableError)
|
|
96
|
+
throw new import_web_bot_auth.WebBotAuthUnavailableError("trust_record_unavailable", error);
|
|
97
|
+
else throw error;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
72
100
|
if (!binding && operation !== void 0 && typeof operation !== "string")
|
|
73
101
|
throw new import_web_bot_auth3.WebBotAuthError(400, "operation");
|
|
74
102
|
const authorizationFailure = revoked?.authorizationFailure ?? (!binding && typeof operation === "string" && !anonymousTools.has(operation) ? (0, import_web_bot_auth2.createWebBotAuthAuthorizationFailure)("identity_not_bound") : void 0);
|
|
75
103
|
return {
|
|
76
|
-
principal: binding?.principalId ??
|
|
104
|
+
principal: binding?.principalId ?? anonymousPrincipalFor(signer.origin, options.anonymousPrincipal),
|
|
77
105
|
extra: {
|
|
78
106
|
identity33: {
|
|
79
107
|
state: binding ? "bound" : "identity-established",
|
|
80
108
|
signer: revoked ?? signer,
|
|
109
|
+
...revocation && { revocation },
|
|
110
|
+
anonymousPrincipal: anonymousPrincipalFor(signer.origin, options.anonymousPrincipal),
|
|
81
111
|
...authorizationFailure && { authorizationFailure }
|
|
82
112
|
}
|
|
83
113
|
},
|
|
@@ -91,6 +121,7 @@ function createIdentity33PrincipalResolver(options) {
|
|
|
91
121
|
} : {}
|
|
92
122
|
};
|
|
93
123
|
};
|
|
124
|
+
if (options.revocation) (0, import_web_bot_auth.registerWebBotAuthRevocationCheck)(resolver);
|
|
94
125
|
(0, import_web_bot_auth.registerWebBotAuthLegacyOriginGate)(resolver, async (origin) => {
|
|
95
126
|
if ((await options.lifecycle.getPrincipal(origin)).binding)
|
|
96
127
|
throw new import_errors.RequestSignatureError(
|
|
@@ -102,6 +133,37 @@ function createIdentity33PrincipalResolver(options) {
|
|
|
102
133
|
return resolver;
|
|
103
134
|
}
|
|
104
135
|
function createIdentity33Authorization(options) {
|
|
136
|
+
const anonymousTools = options.anonymousTools ?? [
|
|
137
|
+
"get_principal",
|
|
138
|
+
"get_adcp_capabilities",
|
|
139
|
+
"list_creative_formats",
|
|
140
|
+
"get_products"
|
|
141
|
+
];
|
|
142
|
+
const authorizeUnbound = async (ctx, identity, tool, params) => {
|
|
143
|
+
if (tool === "sync_principal") {
|
|
144
|
+
if (identity.revocation === "absent")
|
|
145
|
+
throw new import_async_outcome.AdcpError("PERMISSION_DENIED", { message: "Agent identity cannot be onboarded" });
|
|
146
|
+
const dryRun = !!params && typeof params === "object" && params.dry_run === true;
|
|
147
|
+
const binding = await options.lifecycle.syncOpen(identity.signer.origin, { dryRun });
|
|
148
|
+
if (dryRun) return;
|
|
149
|
+
if (!ctx.authInfo) throw new import_trust.IdentityAuthorizationError();
|
|
150
|
+
ctx.authInfo.clientId = binding.principalId;
|
|
151
|
+
ctx.authInfo.credential = (0, import_buyer_agent.markVerifiedHttpSig)({
|
|
152
|
+
kind: "http_sig",
|
|
153
|
+
keyid: identity.signer.keyid,
|
|
154
|
+
agent_url: identity.signer.origin,
|
|
155
|
+
verified_at: identity.signer.verified_at
|
|
156
|
+
});
|
|
157
|
+
ctx.authInfo.extra = {
|
|
158
|
+
...ctx.authInfo.extra,
|
|
159
|
+
credential: ctx.authInfo.credential,
|
|
160
|
+
identity33: { ...identity, state: "bound" }
|
|
161
|
+
};
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
if (anonymousTools.includes(tool) && !needsSpendAuthorization(tool, params)) return;
|
|
165
|
+
throw new import_async_outcome.AdcpError("PERMISSION_DENIED", { message: "Agent identity is not onboarded for this operation" });
|
|
166
|
+
};
|
|
105
167
|
return async (ctx, tool, params) => {
|
|
106
168
|
const identity = claimFromContext(ctx);
|
|
107
169
|
const failure = (0, import_web_bot_auth2.getWebBotAuthAuthorizationFailure)(ctx.authInfo?.extra);
|
|
@@ -114,48 +176,43 @@ function createIdentity33Authorization(options) {
|
|
|
114
176
|
});
|
|
115
177
|
return;
|
|
116
178
|
}
|
|
117
|
-
if (identity.state === "identity-established")
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
ctx.authInfo.clientId = binding.principalId;
|
|
124
|
-
ctx.authInfo.credential = (0, import_buyer_agent.markVerifiedHttpSig)({
|
|
125
|
-
kind: "http_sig",
|
|
126
|
-
keyid: identity.signer.keyid,
|
|
127
|
-
agent_url: identity.signer.origin,
|
|
128
|
-
verified_at: identity.signer.verified_at
|
|
129
|
-
});
|
|
130
|
-
ctx.authInfo.extra = {
|
|
131
|
-
...ctx.authInfo.extra,
|
|
132
|
-
credential: ctx.authInfo.credential,
|
|
133
|
-
identity33: { ...identity, state: "bound" }
|
|
134
|
-
};
|
|
179
|
+
if (identity.state === "identity-established") return authorizeUnbound(ctx, identity, tool, params);
|
|
180
|
+
const origin = identity.signer.origin;
|
|
181
|
+
const spend = !IDENTITY_ADMIN_TOOLS.has(tool) && needsSpendAuthorization(tool, params) && !(!KNOWN_TOOLS.has(tool) && options.readOnlyTools?.includes(tool));
|
|
182
|
+
try {
|
|
183
|
+
if (IDENTITY_ADMIN_TOOLS.has(tool)) {
|
|
184
|
+
await options.lifecycle.authorizeBound(origin, { identityConfiguration: true });
|
|
135
185
|
return;
|
|
136
186
|
}
|
|
137
|
-
if (
|
|
187
|
+
if (!spend) {
|
|
188
|
+
await options.lifecycle.authorizeBound(origin, { readOnly: true });
|
|
138
189
|
return;
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
190
|
+
}
|
|
191
|
+
const claims = await options.resolveClaims(ctx, tool, params);
|
|
192
|
+
if (!claims.length) throw new import_trust.IdentityAuthorizationError();
|
|
193
|
+
for (const claim of claims) {
|
|
194
|
+
await options.lifecycle.authorizeBound(origin, {
|
|
195
|
+
sandbox: claim.sandbox,
|
|
196
|
+
billing: claim.billing,
|
|
197
|
+
brandDomain: claim.brand.domain
|
|
198
|
+
});
|
|
199
|
+
await (0, import_trust.authorizeDemandGrant)({ ...claim, signerOrigin: origin }, options.grants);
|
|
200
|
+
}
|
|
201
|
+
} catch (error) {
|
|
202
|
+
if (error instanceof import_trust.TrustRecordUnavailableError)
|
|
203
|
+
throw new import_async_outcome.AdcpError("SERVICE_UNAVAILABLE", { message: error.message, recovery: "transient" });
|
|
204
|
+
if (error instanceof import_trust.TrustMembershipError && !spend) {
|
|
205
|
+
const demoted = { ...identity, state: "identity-established" };
|
|
206
|
+
if (ctx.authInfo) {
|
|
207
|
+
const { credential: _credential, ...extra } = ctx.authInfo.extra ?? {};
|
|
208
|
+
ctx.authInfo.clientId = identity.anonymousPrincipal ?? anonymousPrincipalFor(origin);
|
|
209
|
+
delete ctx.authInfo.credential;
|
|
210
|
+
ctx.authInfo.extra = { ...extra, identity33: demoted };
|
|
211
|
+
}
|
|
212
|
+
delete ctx.agent;
|
|
213
|
+
return authorizeUnbound(ctx, demoted, tool, params);
|
|
214
|
+
}
|
|
215
|
+
throw error;
|
|
159
216
|
}
|
|
160
217
|
};
|
|
161
218
|
}
|
|
@@ -176,7 +233,10 @@ function withIdentity33PrincipalHandlers(base, lifecycle) {
|
|
|
176
233
|
return base.getPrincipal(params, ctx);
|
|
177
234
|
}
|
|
178
235
|
const read = await lifecycle.getPrincipal(identity.signer.origin);
|
|
179
|
-
if (!read.binding
|
|
236
|
+
if (!read.binding || identity.state === "identity-established")
|
|
237
|
+
return {
|
|
238
|
+
result: { kind: "unconfigured", ...read.result.relationship && { relationship: read.result.relationship } }
|
|
239
|
+
};
|
|
180
240
|
if (!base.getPrincipal) return { result: read.result };
|
|
181
241
|
const response = await base.getPrincipal(params, ctx);
|
|
182
242
|
if (read.result.relationship && response && typeof response === "object") {
|
|
@@ -200,7 +260,7 @@ function createIdentity33PrincipalScopeResolver(options) {
|
|
|
200
260
|
return options.legacy(ctx);
|
|
201
261
|
}
|
|
202
262
|
const { binding } = await options.lifecycle.getPrincipal(identity.signer.origin);
|
|
203
|
-
if (!binding) throw new import_trust.IdentityAuthorizationError();
|
|
263
|
+
if (!binding || identity.state !== "bound") throw new import_trust.IdentityAuthorizationError();
|
|
204
264
|
return {
|
|
205
265
|
tenant_id: options.tenantId,
|
|
206
266
|
principal_id: binding.principalId,
|