@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
|
@@ -24,21 +24,28 @@ __export(web_bot_auth_exports, {
|
|
|
24
24
|
WEB_BOT_AUTH_DRAFT_REVISION: () => WEB_BOT_AUTH_DRAFT_REVISION,
|
|
25
25
|
WEB_BOT_AUTH_PROFILE: () => WEB_BOT_AUTH_PROFILE,
|
|
26
26
|
WebBotAuthError: () => WebBotAuthError,
|
|
27
|
+
WebBotAuthUnavailableError: () => WebBotAuthUnavailableError,
|
|
27
28
|
buildWebBotAuthSignatureBase: () => buildWebBotAuthSignatureBase,
|
|
28
29
|
createSignedSignatureDirectory: () => createSignedSignatureDirectory,
|
|
29
30
|
createWebBotAuthAuthorizationFailure: () => createWebBotAuthAuthorizationFailure,
|
|
30
31
|
createWebBotAuthFetch: () => createWebBotAuthFetch,
|
|
32
|
+
deferWebBotAuthPrincipalFailure: () => deferWebBotAuthPrincipalFailure,
|
|
31
33
|
getWebBotAuthAuthorizationFailure: () => getWebBotAuthAuthorizationFailure,
|
|
32
34
|
getWebBotAuthLegacyOriginGate: () => getWebBotAuthLegacyOriginGate,
|
|
33
35
|
isVerifiedWebBotSigner: () => isVerifiedWebBotSigner,
|
|
36
|
+
isWebBotAuthRevocationChecked: () => isWebBotAuthRevocationChecked,
|
|
34
37
|
jwkThumbprint: () => jwkThumbprint,
|
|
35
38
|
receivedAgentOrigin: () => receivedAgentOrigin,
|
|
36
39
|
registerWebBotAuthLegacyOriginGate: () => registerWebBotAuthLegacyOriginGate,
|
|
40
|
+
registerWebBotAuthRevocationCheck: () => registerWebBotAuthRevocationCheck,
|
|
37
41
|
selectSignatureProfile: () => selectSignatureProfile,
|
|
38
42
|
signWebBotAuth: () => signWebBotAuth,
|
|
43
|
+
verifyKeyDirectory: () => verifyKeyDirectory,
|
|
39
44
|
verifySignatureDirectory: () => verifySignatureDirectory,
|
|
40
45
|
verifyWebBotAuth: () => verifyWebBotAuth,
|
|
41
|
-
|
|
46
|
+
webBotAuthPrincipalFailureError: () => webBotAuthPrincipalFailureError,
|
|
47
|
+
webBotAuthToolFailure: () => webBotAuthToolFailure,
|
|
48
|
+
webBotAuthUnavailableResponse: () => webBotAuthUnavailableResponse
|
|
42
49
|
});
|
|
43
50
|
module.exports = __toCommonJS(web_bot_auth_exports);
|
|
44
51
|
var import_version = require('../version.js');
|
|
@@ -58,12 +65,14 @@ const WEB_BOT_AUTH_DRAFT_REVISION = "draft-ietf-webbotauth-httpsig-protocol-00";
|
|
|
58
65
|
const SIGNATURE_DIRECTORY_PATH = "/.well-known/http-message-signatures-directory";
|
|
59
66
|
const SIGNATURE_DIRECTORY_MEDIA_TYPE = "application/http-message-signatures-directory+json";
|
|
60
67
|
const DIRECTORY_TAG = "http-message-signatures-directory";
|
|
68
|
+
const DIRECTORY_CLOCK_SKEW_SECONDS = 60;
|
|
61
69
|
const TEST_KIDS = /* @__PURE__ */ new Set([
|
|
62
70
|
"6WsKMCObba49V3uN0vIENnk1BKAk7c9zjBJcWKyQ9_I",
|
|
63
71
|
"hHurKlVBnPJ4AGiPyl6HWMhxgkIcRBzaC8qWwEtnuVI",
|
|
64
72
|
"ZtaFf5jj-EzorJmZn6mX3YnmK_QyZKVLbbinzIQKOjQ",
|
|
65
73
|
"1XlJ3EIte7bPOEqKQtKrKNTNy5hywTyrwFXWrMbXKqE"
|
|
66
74
|
]);
|
|
75
|
+
const NO_CHALLENGE_REASONS = /* @__PURE__ */ new Set(["key_revoked", "identity_not_bound", "profile_disabled", "authorization_failed"]);
|
|
67
76
|
class WebBotAuthError extends Error {
|
|
68
77
|
constructor(status, reason) {
|
|
69
78
|
super(
|
|
@@ -72,13 +81,7 @@ class WebBotAuthError extends Error {
|
|
|
72
81
|
this.status = status;
|
|
73
82
|
this.reason = reason;
|
|
74
83
|
this.name = "WebBotAuthError";
|
|
75
|
-
this.headers = status === 403 && !
|
|
76
|
-
"key_revoked",
|
|
77
|
-
"identity_not_bound",
|
|
78
|
-
"revocation_unavailable",
|
|
79
|
-
"revocation_persistence_failed",
|
|
80
|
-
"profile_disabled"
|
|
81
|
-
].includes(reason) ? {
|
|
84
|
+
this.headers = status === 403 && !NO_CHALLENGE_REASONS.has(reason) ? {
|
|
82
85
|
"Accept-Signature": 'sig1=("@method" "@target-uri" "@authority" "content-type" "content-digest" "signature-agent";key="sig1");created;expires;nonce;keyid;tag="web-bot-auth"'
|
|
83
86
|
} : status === 429 ? { "Retry-After": "60" } : {};
|
|
84
87
|
}
|
|
@@ -86,18 +89,29 @@ class WebBotAuthError extends Error {
|
|
|
86
89
|
reason;
|
|
87
90
|
headers;
|
|
88
91
|
get body() {
|
|
89
|
-
if (
|
|
90
|
-
this.reason
|
|
91
|
-
))
|
|
92
|
+
if (this.reason === "authorization_failed" || this.reason === "identity_not_bound")
|
|
92
93
|
return {
|
|
93
|
-
adcp_error: {
|
|
94
|
-
code: this.reason.startsWith("revocation_") ? "SERVICE_UNAVAILABLE" : "PERMISSION_DENIED",
|
|
95
|
-
message: this.message,
|
|
96
|
-
recovery: this.reason.startsWith("revocation_") ? "transient" : "correctable"
|
|
97
|
-
}
|
|
94
|
+
adcp_error: { code: "PERMISSION_DENIED", message: "Agent authorization failed", recovery: "correctable" }
|
|
98
95
|
};
|
|
99
|
-
return {
|
|
96
|
+
return { adcp_error: { code: "AUTH_INVALID", message: this.message, recovery: "terminal" } };
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* The signer's identity could not be established because key discovery
|
|
100
|
+
* failed (WBA §6.10) or its revocation status is unknown. The request is
|
|
101
|
+
* unverified; servers apply their unverified-request policy.
|
|
102
|
+
*/
|
|
103
|
+
get unverified() {
|
|
104
|
+
return this.reason.startsWith("directory_") || this.reason === "identity_unverified";
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
class WebBotAuthUnavailableError extends Error {
|
|
108
|
+
constructor(reason, cause) {
|
|
109
|
+
super("Web Bot Auth verification dependency is unavailable");
|
|
110
|
+
this.reason = reason;
|
|
111
|
+
this.name = "WebBotAuthUnavailableError";
|
|
112
|
+
if (cause !== void 0) Object.defineProperty(this, "cause", { value: cause });
|
|
100
113
|
}
|
|
114
|
+
reason;
|
|
101
115
|
}
|
|
102
116
|
function bad(reason) {
|
|
103
117
|
throw new WebBotAuthError(400, reason);
|
|
@@ -106,7 +120,8 @@ function denied(reason) {
|
|
|
106
120
|
throw new WebBotAuthError(403, reason);
|
|
107
121
|
}
|
|
108
122
|
function dictionary(value, field) {
|
|
109
|
-
if (
|
|
123
|
+
if (value === void 0 || value === "") return denied("headers_missing");
|
|
124
|
+
if (value.length > 64 * 1024) return bad("headers");
|
|
110
125
|
try {
|
|
111
126
|
(0, import_parser.rejectDuplicateDictionaryKeys)(value, field);
|
|
112
127
|
return (0, import_structured_headers.parseDictionary)(value);
|
|
@@ -126,12 +141,18 @@ function jwkThumbprint(jwk) {
|
|
|
126
141
|
fields = { crv: jwk.crv, kty: jwk.kty, x: jwk.x };
|
|
127
142
|
else if (jwk.kty === "EC" && typeof jwk.crv === "string" && typeof jwk.x === "string" && typeof jwk.y === "string")
|
|
128
143
|
fields = { crv: jwk.crv, kty: jwk.kty, x: jwk.x, y: jwk.y };
|
|
144
|
+
else if (jwk.kty === "RSA" && typeof jwk.e === "string" && typeof jwk.n === "string")
|
|
145
|
+
fields = { e: jwk.e, kty: jwk.kty, n: jwk.n };
|
|
129
146
|
else return denied("unsupported_key");
|
|
130
147
|
return (0, import_crypto.createHash)("sha256").update(JSON.stringify(fields)).digest("base64url");
|
|
131
148
|
}
|
|
132
149
|
function algorithm(jwk) {
|
|
133
150
|
if (jwk.kty === "OKP" && jwk.crv === "Ed25519") return "ed25519";
|
|
134
151
|
if (jwk.kty === "EC" && jwk.crv === "P-256") return "ecdsa-p256-sha256";
|
|
152
|
+
if (jwk.kty === "RSA" && typeof jwk.n === "string" && jwk.e === "AQAB") {
|
|
153
|
+
const bytes = Buffer.from(jwk.n, "base64url").length;
|
|
154
|
+
if (bytes >= 256 && bytes <= 512) return "rsa-pss-sha512";
|
|
155
|
+
}
|
|
135
156
|
return denied("unsupported_key");
|
|
136
157
|
}
|
|
137
158
|
function inputs(headers) {
|
|
@@ -175,8 +196,8 @@ function buildWebBotAuthSignatureBase(input, request, response) {
|
|
|
175
196
|
if (params.has("key")) return bad("derived_dictionary");
|
|
176
197
|
if (response && bare !== "@status" && !params.has("req")) return bad("response_component");
|
|
177
198
|
if (bare === "@method") value = request.method.toUpperCase();
|
|
178
|
-
else if (bare === "@target-uri") value = (
|
|
179
|
-
else if (bare === "@authority") value = (
|
|
199
|
+
else if (bare === "@target-uri") value = receivedTargetUri(request.url);
|
|
200
|
+
else if (bare === "@authority") value = receivedAuthority(request.url);
|
|
180
201
|
else if (bare === "@status" && response && !params.has("req")) value = String(response.status);
|
|
181
202
|
else return bad("derived_component");
|
|
182
203
|
} else {
|
|
@@ -196,21 +217,49 @@ function buildWebBotAuthSignatureBase(input, request, response) {
|
|
|
196
217
|
});
|
|
197
218
|
return [...lines, `"@signature-params": ${input.signatureParamsValue}`].join("\n");
|
|
198
219
|
}
|
|
220
|
+
function receivedTargetUri(url) {
|
|
221
|
+
try {
|
|
222
|
+
new URL(url);
|
|
223
|
+
} catch {
|
|
224
|
+
return bad("target_uri");
|
|
225
|
+
}
|
|
226
|
+
return url;
|
|
227
|
+
}
|
|
228
|
+
function receivedAuthority(url) {
|
|
229
|
+
try {
|
|
230
|
+
return new URL(url).host.toLowerCase();
|
|
231
|
+
} catch {
|
|
232
|
+
return bad("target_uri");
|
|
233
|
+
}
|
|
234
|
+
}
|
|
199
235
|
function assertDigest(headers, body) {
|
|
200
236
|
const digest = (0, import_canonicalize2.getHeaderValue)(headers, "content-digest");
|
|
201
237
|
if (!digest || !(0, import_content_digest.contentDigestUsesEncoding)(digest, "rfc8941-base64") || !(0, import_content_digest.contentDigestMatches)(digest, body))
|
|
202
238
|
denied("content_digest");
|
|
203
239
|
}
|
|
204
|
-
function
|
|
240
|
+
function signatureBytes(input, request, jwk, response) {
|
|
205
241
|
const alg = algorithm(jwk);
|
|
206
242
|
if (input.params.alg !== void 0 && input.params.alg !== alg) denied("algorithm");
|
|
207
243
|
const signature = (0, import_canonicalize2.getHeaderValue)(response?.headers ?? request.headers, "signature");
|
|
208
|
-
|
|
244
|
+
if (signature === void 0 || signature === "") denied("headers_missing");
|
|
209
245
|
try {
|
|
210
|
-
bytes
|
|
246
|
+
return { alg, bytes: (0, import_parser.parseSignature)(signature ?? "", input.label, "rfc8941-base64").bytes };
|
|
211
247
|
} catch {
|
|
212
248
|
return bad("signature");
|
|
213
249
|
}
|
|
250
|
+
}
|
|
251
|
+
async function verifyBytesAsync(input, request, jwk) {
|
|
252
|
+
const { alg, bytes } = signatureBytes(input, request, jwk);
|
|
253
|
+
let valid = false;
|
|
254
|
+
try {
|
|
255
|
+
valid = await (0, import_crypto2.verifySignatureAsync)(alg, jwk, Buffer.from(buildWebBotAuthSignatureBase(input, request)), bytes);
|
|
256
|
+
} catch (error) {
|
|
257
|
+
if (error instanceof WebBotAuthError) throw error;
|
|
258
|
+
}
|
|
259
|
+
if (!valid) denied("signature_invalid");
|
|
260
|
+
}
|
|
261
|
+
function verifyBytes(input, request, jwk, response) {
|
|
262
|
+
const { alg, bytes } = signatureBytes(input, request, jwk, response);
|
|
214
263
|
let valid = false;
|
|
215
264
|
try {
|
|
216
265
|
valid = (0, import_crypto2.verifySignature)(
|
|
@@ -231,16 +280,63 @@ function registerWebBotAuthLegacyOriginGate(resolver, gate) {
|
|
|
231
280
|
function getWebBotAuthLegacyOriginGate(resolver) {
|
|
232
281
|
return resolver && legacyOriginGates.get(resolver);
|
|
233
282
|
}
|
|
283
|
+
const revocationCheckingResolvers = /* @__PURE__ */ new WeakSet();
|
|
284
|
+
function registerWebBotAuthRevocationCheck(resolver) {
|
|
285
|
+
revocationCheckingResolvers.add(resolver);
|
|
286
|
+
}
|
|
287
|
+
function isWebBotAuthRevocationChecked(resolver) {
|
|
288
|
+
return !!resolver && revocationCheckingResolvers.has(resolver);
|
|
289
|
+
}
|
|
290
|
+
function webBotAuthUnavailableResponse() {
|
|
291
|
+
return {
|
|
292
|
+
status: 503,
|
|
293
|
+
headers: { "Retry-After": "60" },
|
|
294
|
+
body: {
|
|
295
|
+
adcp_error: {
|
|
296
|
+
code: "SERVICE_UNAVAILABLE",
|
|
297
|
+
message: "Agent identity verification is temporarily unavailable",
|
|
298
|
+
recovery: "transient"
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
}
|
|
234
303
|
const authorizationFailures = /* @__PURE__ */ new WeakSet();
|
|
304
|
+
const AUTHORIZATION_FAILURE_MESSAGES = {
|
|
305
|
+
key_revoked: "Agent identity key is revoked",
|
|
306
|
+
identity_not_bound: "Agent identity is not onboarded for this operation",
|
|
307
|
+
revocation_unavailable: "Identity revocation information is unavailable",
|
|
308
|
+
authorization_failed: "Agent authorization failed",
|
|
309
|
+
authorization_unavailable: "Agent authorization is temporarily unavailable"
|
|
310
|
+
};
|
|
235
311
|
function createWebBotAuthAuthorizationFailure(reason) {
|
|
312
|
+
const transient = reason === "revocation_unavailable" || reason === "authorization_unavailable";
|
|
236
313
|
const failure = Object.freeze({
|
|
237
|
-
code:
|
|
238
|
-
message: reason
|
|
239
|
-
recovery:
|
|
314
|
+
code: transient ? "SERVICE_UNAVAILABLE" : "PERMISSION_DENIED",
|
|
315
|
+
message: AUTHORIZATION_FAILURE_MESSAGES[reason],
|
|
316
|
+
recovery: transient ? "transient" : "correctable"
|
|
240
317
|
});
|
|
241
318
|
authorizationFailures.add(failure);
|
|
242
319
|
return failure;
|
|
243
320
|
}
|
|
321
|
+
function principalFailureIsTransient(error) {
|
|
322
|
+
const value = error;
|
|
323
|
+
if (value?.transient === true || value?.recovery === "transient" || value?.code === "SERVICE_UNAVAILABLE")
|
|
324
|
+
return true;
|
|
325
|
+
return typeof value?.code !== "string";
|
|
326
|
+
}
|
|
327
|
+
function webBotAuthPrincipalFailureError(error) {
|
|
328
|
+
return principalFailureIsTransient(error) ? new WebBotAuthUnavailableError("authorization_unavailable", error) : new WebBotAuthError(403, "authorization_failed");
|
|
329
|
+
}
|
|
330
|
+
function deferWebBotAuthPrincipalFailure(signers, error) {
|
|
331
|
+
const signer = signers[signers.length - 1];
|
|
332
|
+
const authorizationFailure = createWebBotAuthAuthorizationFailure(
|
|
333
|
+
principalFailureIsTransient(error) ? "authorization_unavailable" : "authorization_failed"
|
|
334
|
+
);
|
|
335
|
+
return {
|
|
336
|
+
principal: `anonymous:${(0, import_crypto.createHash)("sha256").update(signer.origin).digest("hex")}`,
|
|
337
|
+
authorizationFailure
|
|
338
|
+
};
|
|
339
|
+
}
|
|
244
340
|
function getWebBotAuthAuthorizationFailure(extra) {
|
|
245
341
|
for (const value of [extra?.webBotAuthAuthorization, extra?.identity33]) {
|
|
246
342
|
const claim = value;
|
|
@@ -306,7 +402,8 @@ async function verifyWebBotAuth(request, options) {
|
|
|
306
402
|
const memberKey = component[1].get("key");
|
|
307
403
|
if (typeof memberKey !== "string" || memberKey !== input.label) bad("agent_member");
|
|
308
404
|
const member = agents.get(memberKey);
|
|
309
|
-
if (!member
|
|
405
|
+
if (!member) denied("agent_not_covered");
|
|
406
|
+
if (typeof member[0] !== "string") bad("signature_agent");
|
|
310
407
|
const type = member[1].get("type");
|
|
311
408
|
if (type !== void 0 && type !== "directory") denied("agent_type");
|
|
312
409
|
const origin = receivedAgentOrigin(member[0]);
|
|
@@ -326,27 +423,16 @@ async function verifyWebBotAuth(request, options) {
|
|
|
326
423
|
assertDigest(request.headers, request.body);
|
|
327
424
|
}
|
|
328
425
|
await options.directory.resolve(keyid, origin);
|
|
329
|
-
const
|
|
330
|
-
if (!
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
);
|
|
426
|
+
const checkedDirectory = options.directory.getVerifiedDirectory(origin);
|
|
427
|
+
if (!checkedDirectory || new URL(checkedDirectory.proof.request.url).origin !== origin)
|
|
428
|
+
denied("directory_origin");
|
|
429
|
+
if (checkedDirectory.expires <= now) denied("directory_expired");
|
|
430
|
+
if (!testKeysAllowed(options.allowTestKeys ?? options.directory.allowsTestKeys) && checkedDirectory.keys.some((key2) => TEST_KIDS.has(key2.kid)))
|
|
431
|
+
denied("directory_key");
|
|
336
432
|
const key = checkedDirectory.keys.find((key2) => key2.kid === keyid);
|
|
337
433
|
if (!key || jwkThumbprint(key) !== keyid) denied("key");
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
try {
|
|
341
|
-
if (await options.isRevoked(origin, keyid)) denied("key_revoked");
|
|
342
|
-
} catch (error) {
|
|
343
|
-
if (options.requireFreshRevocation === false && error instanceof WebBotAuthError && error.reason === "revocation_unavailable") {
|
|
344
|
-
} else if (options.deferAuthorizationErrors && error instanceof WebBotAuthError && ["key_revoked", "revocation_unavailable", "revocation_persistence_failed"].includes(error.reason))
|
|
345
|
-
authorizationFailure = createWebBotAuthAuthorizationFailure(
|
|
346
|
-
error.reason === "revocation_persistence_failed" ? "revocation_unavailable" : error.reason
|
|
347
|
-
);
|
|
348
|
-
else throw error;
|
|
349
|
-
}
|
|
434
|
+
await verifyBytesAsync(input, request, key);
|
|
435
|
+
if (options.isRevoked) await legacyRevocationCheck(options, origin, keyid);
|
|
350
436
|
const nonceTuple = JSON.stringify([origin, nonce]);
|
|
351
437
|
if (nonces.has(nonceTuple)) throw new WebBotAuthError(options.replayStatus ?? 429, "nonce_replayed");
|
|
352
438
|
nonces.add(nonceTuple);
|
|
@@ -355,20 +441,25 @@ async function verifyWebBotAuth(request, options) {
|
|
|
355
441
|
origin,
|
|
356
442
|
keyid,
|
|
357
443
|
label: input.label,
|
|
358
|
-
verified_at: now
|
|
359
|
-
...authorizationFailure && { authorizationFailure }
|
|
444
|
+
verified_at: now
|
|
360
445
|
});
|
|
361
446
|
verified.push(signer);
|
|
362
447
|
}
|
|
363
448
|
for (const signer of verified) {
|
|
364
449
|
const input = selected.find((i) => i.label === signer.label);
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
450
|
+
let result;
|
|
451
|
+
try {
|
|
452
|
+
result = await options.replayStore.insert(
|
|
453
|
+
signer.origin,
|
|
454
|
+
WEB_BOT_AUTH_PROFILE,
|
|
455
|
+
input.params.nonce,
|
|
456
|
+
// Acceptance is inclusive of expires + 60, so the nonce must outlive it.
|
|
457
|
+
input.params.expires + 60 - now + 1,
|
|
458
|
+
now
|
|
459
|
+
);
|
|
460
|
+
} catch (cause) {
|
|
461
|
+
throw new WebBotAuthUnavailableError("replay_unavailable", cause);
|
|
462
|
+
}
|
|
372
463
|
if (result !== "ok")
|
|
373
464
|
throw new WebBotAuthError(result === "replayed" ? options.replayStatus ?? 429 : 429, result);
|
|
374
465
|
}
|
|
@@ -377,48 +468,109 @@ async function verifyWebBotAuth(request, options) {
|
|
|
377
468
|
} catch (error) {
|
|
378
469
|
if (error instanceof WebBotAuthError) {
|
|
379
470
|
if (error.headers["Accept-Signature"]) {
|
|
380
|
-
|
|
471
|
+
let label = "sig1";
|
|
472
|
+
try {
|
|
473
|
+
label = inputs(request.headers)[0]?.label ?? "sig1";
|
|
474
|
+
} catch {
|
|
475
|
+
}
|
|
381
476
|
const bodyComponents = request.body ? '"content-type" "content-digest" ' : "";
|
|
382
477
|
error.headers["Accept-Signature"] = `${label}=("@method" "@target-uri" "@authority" ${bodyComponents}"signature-agent";key="${label}");created;expires;nonce;keyid;tag="web-bot-auth"`;
|
|
383
478
|
}
|
|
384
479
|
throw error;
|
|
385
480
|
}
|
|
481
|
+
if (error instanceof WebBotAuthUnavailableError) throw error;
|
|
386
482
|
return denied("verification_unavailable");
|
|
387
483
|
}
|
|
388
484
|
}
|
|
389
|
-
function
|
|
485
|
+
async function legacyRevocationCheck(options, origin, keyid) {
|
|
486
|
+
let revoked;
|
|
487
|
+
try {
|
|
488
|
+
revoked = await options.isRevoked(origin, keyid);
|
|
489
|
+
} catch (error) {
|
|
490
|
+
if (error instanceof WebBotAuthUnavailableError) throw error;
|
|
491
|
+
if (error instanceof WebBotAuthError && error.reason === "revocation_persistence_failed")
|
|
492
|
+
throw new WebBotAuthUnavailableError("revocation_persistence_failed", error);
|
|
493
|
+
if (error instanceof WebBotAuthError && error.reason === "key_revoked") return denied("key_revoked");
|
|
494
|
+
if (options.requireFreshRevocation === false && error instanceof WebBotAuthError && (error.reason === "revocation_unavailable" || error.reason === "revocation_absent"))
|
|
495
|
+
return;
|
|
496
|
+
return denied("identity_unverified");
|
|
497
|
+
}
|
|
498
|
+
if (revoked) denied("key_revoked");
|
|
499
|
+
}
|
|
500
|
+
function testKeysAllowed(allowTestKeys) {
|
|
501
|
+
return allowTestKeys === true && ["test", "development"].includes(process.env.NODE_ENV ?? "");
|
|
502
|
+
}
|
|
503
|
+
const UNSIGNED_DIRECTORY_MEDIA_TYPES = /* @__PURE__ */ new Set([
|
|
504
|
+
SIGNATURE_DIRECTORY_MEDIA_TYPE,
|
|
505
|
+
"application/jwk-set+json",
|
|
506
|
+
"application/json"
|
|
507
|
+
]);
|
|
508
|
+
function verifyKeyDirectory(response, now = Date.now() / 1e3, options = {}) {
|
|
390
509
|
try {
|
|
391
510
|
if (!Number.isFinite(now)) denied("directory_invalid");
|
|
392
511
|
const origin = (0, import_canonicalize.canonicalAgentOrigin)(response.request.url);
|
|
393
|
-
if (response.request.url !== origin + SIGNATURE_DIRECTORY_PATH || response.status !== 200
|
|
512
|
+
if (response.request.url !== origin + SIGNATURE_DIRECTORY_PATH || response.status !== 200)
|
|
513
|
+
denied("directory_response");
|
|
514
|
+
const signed = (0, import_canonicalize2.getHeaderValue)(response.headers, "signature-input") !== void 0;
|
|
515
|
+
if (!signed && options.requireSigned) denied("directory_unsigned");
|
|
516
|
+
const mediaType = (0, import_canonicalize2.getHeaderValue)(response.headers, "content-type")?.split(";")[0]?.trim().toLowerCase();
|
|
517
|
+
if (signed ? mediaType !== SIGNATURE_DIRECTORY_MEDIA_TYPE : !UNSIGNED_DIRECTORY_MEDIA_TYPES.has(mediaType ?? ""))
|
|
394
518
|
denied("directory_response");
|
|
395
519
|
const body = response.body ?? "";
|
|
396
520
|
if (Buffer.byteLength(body) > 256 * 1024) denied("directory_size");
|
|
397
|
-
|
|
521
|
+
if (signed || (0, import_canonicalize2.getHeaderValue)(response.headers, "content-digest") !== void 0)
|
|
522
|
+
assertDirectoryDigest(response.headers, body);
|
|
398
523
|
const document = (0, import_strict_json.parseStrictJson)(body, 32);
|
|
399
524
|
if (!Array.isArray(document.keys) || !document.keys.length || document.keys.length > 64) denied("directory_keys");
|
|
400
|
-
const signatures = inputs(response.headers);
|
|
401
|
-
const
|
|
525
|
+
const signatures = signed ? inputs(response.headers) : [];
|
|
526
|
+
const kids = /* @__PURE__ */ new Set();
|
|
527
|
+
const keys = [];
|
|
402
528
|
let expires = Infinity;
|
|
403
|
-
for (const
|
|
404
|
-
if (!
|
|
529
|
+
for (const raw of document.keys) {
|
|
530
|
+
if (!raw || typeof raw !== "object") denied("directory_key");
|
|
531
|
+
const thumbprint = jwkThumbprint(raw);
|
|
532
|
+
const key = { ...raw, kid: raw.kid ?? thumbprint };
|
|
533
|
+
if (key.kid !== thumbprint || kids.has(key.kid) || key.d !== void 0 || Object.keys(key).some((k) => k.startsWith("_private")) || key.adcp_use !== void 0 || !testKeysAllowed(options.allowTestKeys) && TEST_KIDS.has(key.kid))
|
|
405
534
|
denied("directory_key");
|
|
406
|
-
|
|
535
|
+
algorithm(key);
|
|
536
|
+
kids.add(key.kid);
|
|
537
|
+
keys.push(key);
|
|
538
|
+
if (!signed) continue;
|
|
407
539
|
const proof = signatures.find((input) => input.params.keyid === key.kid && input.params.tag === DIRECTORY_TAG);
|
|
408
|
-
if (!proof || !proof.components.includes("@authority;req") || !proof.components.includes("content-digest") || !Number.isInteger(proof.params.created) || !Number.isInteger(proof.params.expires) || proof.params.created > now || proof.params.expires <= now || proof.params.expires <= proof.params.created)
|
|
540
|
+
if (!proof || !proof.components.includes("@authority;req") || !proof.components.includes("content-digest") || !Number.isInteger(proof.params.created) || !Number.isInteger(proof.params.expires) || proof.params.created > now + DIRECTORY_CLOCK_SKEW_SECONDS || proof.params.expires <= now || proof.params.expires <= proof.params.created)
|
|
409
541
|
denied("directory_proof");
|
|
410
542
|
verifyBytes(proof, { ...response.request, headers: response.request.headers ?? {} }, key, response);
|
|
411
543
|
expires = Math.min(expires, proof.params.expires);
|
|
412
544
|
}
|
|
413
|
-
|
|
545
|
+
if (!signed) expires = now + unsignedAge(options.maxUnsignedAgeSeconds);
|
|
546
|
+
return { keys, expires, signed, proof: response };
|
|
414
547
|
} catch (error) {
|
|
415
|
-
if (error instanceof WebBotAuthError)
|
|
548
|
+
if (error instanceof WebBotAuthError) {
|
|
549
|
+
throw error.reason.startsWith("directory_") ? error : new WebBotAuthError(403, `directory_${error.reason}`);
|
|
550
|
+
}
|
|
416
551
|
return denied("directory_invalid");
|
|
417
552
|
}
|
|
418
553
|
}
|
|
554
|
+
function unsignedAge(seconds) {
|
|
555
|
+
const value = seconds ?? 86400;
|
|
556
|
+
if (!Number.isFinite(value) || value < 60)
|
|
557
|
+
throw new TypeError("Unsigned directory age must be a finite number of seconds, at least 60");
|
|
558
|
+
return value;
|
|
559
|
+
}
|
|
560
|
+
function verifySignatureDirectory(response, now = Date.now() / 1e3, allowTestKeys = false) {
|
|
561
|
+
return verifyKeyDirectory(response, now, { allowTestKeys, requireSigned: true });
|
|
562
|
+
}
|
|
563
|
+
function assertDirectoryDigest(headers, body) {
|
|
564
|
+
try {
|
|
565
|
+
assertDigest(headers, body);
|
|
566
|
+
} catch {
|
|
567
|
+
denied("directory_digest");
|
|
568
|
+
}
|
|
569
|
+
}
|
|
419
570
|
class SignatureDirectoryResolver {
|
|
420
571
|
constructor(options = {}) {
|
|
421
572
|
this.options = options;
|
|
573
|
+
unsignedAge(options.maxUnsignedDirectoryAgeSeconds);
|
|
422
574
|
}
|
|
423
575
|
options;
|
|
424
576
|
cache = /* @__PURE__ */ new Map();
|
|
@@ -430,19 +582,22 @@ class SignatureDirectoryResolver {
|
|
|
430
582
|
const origin = receivedAgentOrigin(signerOrigin);
|
|
431
583
|
const now = (this.options.now ?? (() => Date.now() / 1e3))();
|
|
432
584
|
const cached = this.cache.get(origin);
|
|
585
|
+
const usable = () => cached && cached.directory.expires > now ? cached.directory : void 0;
|
|
433
586
|
if (cached && cached.freshUntil > now && cached.directory.expires > now) {
|
|
434
587
|
const key = cached.directory.keys.find((k) => k.kid === keyid);
|
|
435
588
|
if (key || (this.keyMissCooldown.get(origin) ?? 0) > now) return key ?? null;
|
|
436
589
|
this.keyMissCooldown.set(origin, now + 60);
|
|
437
590
|
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
return denied("directory_unavailable");
|
|
441
|
-
}
|
|
591
|
+
const fromFallback = () => {
|
|
592
|
+
const key = usable()?.keys.find((k) => k.kid === keyid);
|
|
593
|
+
return key ?? denied("directory_unavailable");
|
|
594
|
+
};
|
|
595
|
+
if ((this.failures.get(origin) ?? 0) > now) return fromFallback();
|
|
442
596
|
let pending = this.pending.get(origin);
|
|
443
597
|
if (!pending) {
|
|
444
598
|
if ([...this.pending.keys()].filter((pendingOrigin) => (0, import_etld.eTldPlusOne)(pendingOrigin) === (0, import_etld.eTldPlusOne)(origin)).length >= 2 || this.pending.size >= (this.options.maxConcurrentFetches ?? 16)) {
|
|
445
|
-
|
|
599
|
+
const key = usable()?.keys.find((k) => k.kid === keyid);
|
|
600
|
+
if (key) return key;
|
|
446
601
|
throw new WebBotAuthError(429, "directory_fetch_limit");
|
|
447
602
|
}
|
|
448
603
|
pending = this.fetch(origin, now);
|
|
@@ -451,7 +606,7 @@ class SignatureDirectoryResolver {
|
|
|
451
606
|
try {
|
|
452
607
|
return (await pending).keys.find((k) => k.kid === keyid) ?? null;
|
|
453
608
|
} catch (error) {
|
|
454
|
-
if (
|
|
609
|
+
if (usable()) return fromFallback();
|
|
455
610
|
throw error;
|
|
456
611
|
} finally {
|
|
457
612
|
if (this.pending.get(origin) === pending) this.pending.delete(origin);
|
|
@@ -462,15 +617,22 @@ class SignatureDirectoryResolver {
|
|
|
462
617
|
const url = origin + SIGNATURE_DIRECTORY_PATH;
|
|
463
618
|
const result = await (0, import_net.ssrfSafeFetch)(url, {
|
|
464
619
|
...this.options.fetchOptions,
|
|
465
|
-
headers: {
|
|
620
|
+
headers: {
|
|
621
|
+
accept: `${SIGNATURE_DIRECTORY_MEDIA_TYPE}, application/jwk-set+json;q=0.9, application/json;q=0.8`
|
|
622
|
+
},
|
|
466
623
|
timeoutMs: 1e4,
|
|
467
624
|
maxBodyBytes: 256 * 1024
|
|
468
625
|
});
|
|
469
|
-
|
|
470
|
-
const
|
|
626
|
+
if (result.status !== 200) denied("directory_unavailable");
|
|
627
|
+
const body = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(result.body);
|
|
628
|
+
const directory = verifyKeyDirectory(
|
|
471
629
|
{ status: result.status, headers: result.headers, body, request: { method: "GET", url } },
|
|
472
630
|
now,
|
|
473
|
-
|
|
631
|
+
{
|
|
632
|
+
allowTestKeys: this.options.allowTestKeys,
|
|
633
|
+
requireSigned: this.options.requireSignedDirectories,
|
|
634
|
+
maxUnsignedAgeSeconds: this.options.maxUnsignedDirectoryAgeSeconds
|
|
635
|
+
}
|
|
474
636
|
);
|
|
475
637
|
const cacheControl = result.headers["cache-control"] ?? "";
|
|
476
638
|
const maxAge = /(?:^|,)\s*(?:no-cache|no-store)(?:,|$)/i.test(cacheControl) ? 0 : Number(cacheControl.match(/(?:^|,)\s*max-age=(\d+)/i)?.[1] ?? 300);
|
|
@@ -495,10 +657,14 @@ class SignatureDirectoryResolver {
|
|
|
495
657
|
}
|
|
496
658
|
}
|
|
497
659
|
get allowsTestKeys() {
|
|
498
|
-
return this.options.allowTestKeys
|
|
660
|
+
return testKeysAllowed(this.options.allowTestKeys);
|
|
661
|
+
}
|
|
662
|
+
/** The directory currently used to verify `origin`, including one kept through a failed refresh. */
|
|
663
|
+
getVerifiedDirectory(origin) {
|
|
664
|
+
return this.cache.get(receivedAgentOrigin(origin))?.directory;
|
|
499
665
|
}
|
|
500
666
|
getAuditProof(origin) {
|
|
501
|
-
return this.
|
|
667
|
+
return this.getVerifiedDirectory(origin)?.proof;
|
|
502
668
|
}
|
|
503
669
|
}
|
|
504
670
|
async function signBase(base, key) {
|
|
@@ -511,8 +677,15 @@ async function signBase(base, key) {
|
|
|
511
677
|
throw new TypeError("Web Bot Auth provider identity does not match its public key");
|
|
512
678
|
return key.provider.sign(Buffer.from(base));
|
|
513
679
|
}
|
|
514
|
-
|
|
515
|
-
|
|
680
|
+
if (alg === "rsa-pss-sha512") {
|
|
681
|
+
const privateKey = (0, import_crypto.createPrivateKey)({ key: jwk, format: "jwk" });
|
|
682
|
+
return (0, import_crypto.sign)("sha512", Buffer.from(base), {
|
|
683
|
+
key: privateKey,
|
|
684
|
+
padding: import_crypto.constants.RSA_PKCS1_PSS_PADDING,
|
|
685
|
+
saltLength: 64
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
return (0, import_crypto2.signWithPrivateJwkAsync)(alg, jwk, Buffer.from(base));
|
|
516
689
|
}
|
|
517
690
|
async function signWebBotAuth(request, key, options) {
|
|
518
691
|
const origin = (0, import_canonicalize.canonicalAgentOrigin)(options.origin);
|
|
@@ -521,9 +694,15 @@ async function signWebBotAuth(request, key, options) {
|
|
|
521
694
|
const jwk = "privateKey" in key ? key.privateKey : key.publicKey;
|
|
522
695
|
const now = Math.floor((options.now ?? (() => Date.now() / 1e3))());
|
|
523
696
|
const window = options.windowSeconds ?? 300;
|
|
524
|
-
const nonce = options.nonce ?? (0, import_crypto.randomBytes)(
|
|
697
|
+
const nonce = options.nonce ?? (0, import_crypto.randomBytes)(64).toString("base64url");
|
|
525
698
|
if (!Number.isInteger(window) || window < 1 || window > 300 || !/^[A-Za-z0-9_+\/-]{22,256}={0,2}$/.test(nonce))
|
|
526
699
|
throw new TypeError("Invalid Web Bot Auth lifetime or nonce");
|
|
700
|
+
if (!options.relay && ["signature", "signature-input", "signature-agent"].some(
|
|
701
|
+
(name) => (0, import_canonicalize2.getHeaderValue)(request.headers, name) !== void 0
|
|
702
|
+
))
|
|
703
|
+
throw new TypeError(
|
|
704
|
+
"Request already carries signature fields; pass relay: true to add a covering signature instead of replacing them"
|
|
705
|
+
);
|
|
527
706
|
const headers = {};
|
|
528
707
|
for (const [name, value] of Object.entries(request.headers))
|
|
529
708
|
if (value !== void 0 && !["signature", "signature-input", "signature-agent", "content-digest"].includes(name.toLowerCase()))
|
|
@@ -535,6 +714,8 @@ async function signWebBotAuth(request, key, options) {
|
|
|
535
714
|
headers["content-digest"] = (0, import_content_digest.computeContentDigest)(request.body, "rfc8941-base64");
|
|
536
715
|
components.push('"content-type"', '"content-digest"');
|
|
537
716
|
}
|
|
717
|
+
if (options.relay && ["signature", "signature-input", "signature-agent"].some((name) => !(0, import_canonicalize2.getHeaderValue)(request.headers, name)))
|
|
718
|
+
throw new TypeError("Relay requires the existing signature fields");
|
|
538
719
|
const earlier = options.relay ? inputs(request.headers) : [];
|
|
539
720
|
if (earlier.some((input2) => input2.label === label || input2.params.tag !== WEB_BOT_AUTH_PROFILE))
|
|
540
721
|
throw new TypeError("Relay requires existing Web Bot Auth signatures and a fresh label");
|
|
@@ -557,7 +738,10 @@ async function signWebBotAuth(request, key, options) {
|
|
|
557
738
|
headers["signature-input"] = `${priorInput ? priorInput + ", " : ""}${label}=(${components.join(" ")});created=${now};expires=${now + window};nonce="${nonce}";keyid="${jwkThumbprint(jwk)}";alg="${algorithm(jwk)}";tag="web-bot-auth"`;
|
|
558
739
|
const input = (0, import_parser.parseSignatureInput)(headers["signature-input"], label);
|
|
559
740
|
if (priorSignature) headers.signature = priorSignature;
|
|
560
|
-
const bytes = await signBase(
|
|
741
|
+
const bytes = await signBase(
|
|
742
|
+
buildWebBotAuthSignatureBase(input, { ...request, url: new URL(request.url).href, headers }),
|
|
743
|
+
key
|
|
744
|
+
);
|
|
561
745
|
headers.signature = `${priorSignature ? priorSignature + ", " : ""}${label}=:${Buffer.from(bytes).toString("base64")}:`;
|
|
562
746
|
return headers;
|
|
563
747
|
}
|
|
@@ -573,8 +757,7 @@ async function createSignedSignatureDirectory(originUrl, signingKeys, options =
|
|
|
573
757
|
const jwk = "privateKey" in key ? key.privateKey : key.publicKey;
|
|
574
758
|
return {
|
|
575
759
|
kty: jwk.kty,
|
|
576
|
-
crv: jwk.crv,
|
|
577
|
-
x: jwk.x,
|
|
760
|
+
...jwk.kty === "RSA" ? { n: jwk.n, e: jwk.e } : { crv: jwk.crv, x: jwk.x },
|
|
578
761
|
...jwk.y ? { y: jwk.y } : {},
|
|
579
762
|
kid: jwkThumbprint(jwk),
|
|
580
763
|
use: "sig"
|
|
@@ -595,7 +778,7 @@ async function createSignedSignatureDirectory(originUrl, signingKeys, options =
|
|
|
595
778
|
const signatureInputs = [], signatures = [];
|
|
596
779
|
for (let i = 0; i < keys.length; i++) {
|
|
597
780
|
const label = `key${i + 1}`;
|
|
598
|
-
const value = `${label}=("@authority";req "content-digest");created=${now};expires=${now + lifetime};keyid="${keys[i].kid}";alg="${algorithm(keys[i])}";tag="${DIRECTORY_TAG}"`;
|
|
781
|
+
const value = `${label}=("@authority";req "content-digest");created=${now - DIRECTORY_CLOCK_SKEW_SECONDS};expires=${now + lifetime};keyid="${keys[i].kid}";alg="${algorithm(keys[i])}";tag="${DIRECTORY_TAG}"`;
|
|
599
782
|
const input = (0, import_parser.parseSignatureInput)(value, label);
|
|
600
783
|
const bytes = await signBase(
|
|
601
784
|
buildWebBotAuthSignatureBase(input, { method: "GET", url: response.request.url, headers: {} }, response),
|
|
@@ -621,7 +804,7 @@ function createWebBotAuthFetch(upstream, key, options) {
|
|
|
621
804
|
if (init?.body != null) {
|
|
622
805
|
if (typeof init.body === "string") body = init.body;
|
|
623
806
|
else if (init.body instanceof Uint8Array || init.body instanceof ArrayBuffer)
|
|
624
|
-
body = new TextDecoder("utf-8", { fatal: true }).decode(init.body);
|
|
807
|
+
body = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(init.body);
|
|
625
808
|
else throw new TypeError("Web Bot Auth fetch requires a UTF-8 string or byte body");
|
|
626
809
|
}
|
|
627
810
|
const headers = {};
|
|
@@ -642,19 +825,26 @@ function createWebBotAuthFetch(upstream, key, options) {
|
|
|
642
825
|
WEB_BOT_AUTH_DRAFT_REVISION,
|
|
643
826
|
WEB_BOT_AUTH_PROFILE,
|
|
644
827
|
WebBotAuthError,
|
|
828
|
+
WebBotAuthUnavailableError,
|
|
645
829
|
buildWebBotAuthSignatureBase,
|
|
646
830
|
createSignedSignatureDirectory,
|
|
647
831
|
createWebBotAuthAuthorizationFailure,
|
|
648
832
|
createWebBotAuthFetch,
|
|
833
|
+
deferWebBotAuthPrincipalFailure,
|
|
649
834
|
getWebBotAuthAuthorizationFailure,
|
|
650
835
|
getWebBotAuthLegacyOriginGate,
|
|
651
836
|
isVerifiedWebBotSigner,
|
|
837
|
+
isWebBotAuthRevocationChecked,
|
|
652
838
|
jwkThumbprint,
|
|
653
839
|
receivedAgentOrigin,
|
|
654
840
|
registerWebBotAuthLegacyOriginGate,
|
|
841
|
+
registerWebBotAuthRevocationCheck,
|
|
655
842
|
selectSignatureProfile,
|
|
656
843
|
signWebBotAuth,
|
|
844
|
+
verifyKeyDirectory,
|
|
657
845
|
verifySignatureDirectory,
|
|
658
846
|
verifyWebBotAuth,
|
|
659
|
-
|
|
847
|
+
webBotAuthPrincipalFailureError,
|
|
848
|
+
webBotAuthToolFailure,
|
|
849
|
+
webBotAuthUnavailableResponse
|
|
660
850
|
});
|