@adcp/sdk 16.0.0-beta.1 → 16.0.0-beta.2
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/core/SingleAgentClient.d.mts +10 -1
- package/dist/lib/core/SingleAgentClient.d.ts +10 -1
- package/dist/lib/core/SingleAgentClient.js +29 -8
- package/dist/lib/core/SingleAgentClient.mjs +29 -8
- 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 +1 -1
- package/dist/lib/index.d.ts +1 -1
- package/dist/lib/index.js +2 -0
- package/dist/lib/index.mjs +2 -0
- package/dist/lib/protocols/index.d.mts +1 -1
- package/dist/lib/protocols/index.d.ts +1 -1
- package/dist/lib/protocols/index.js +2 -2
- package/dist/lib/protocols/index.mjs +2 -2
- 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.js +8 -0
- package/dist/lib/signing/crypto.mjs +9 -1
- 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/verifier.js +1 -1
- package/dist/lib/signing/verifier.mjs +1 -1
- 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 +259 -82
- package/dist/lib/signing/web-bot-auth.mjs +259 -87
- package/dist/lib/signing/webhook-verifier.js +1 -1
- package/dist/lib/signing/webhook-verifier.mjs +1 -1
- package/dist/lib/testing/storyboard/validations.d.mts +1 -1
- package/dist/lib/testing/storyboard/validations.d.ts +1 -1
- 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/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 +2 -2
- package/docs/guides/REPORTING-LEDGER.md +15 -5
- package/docs/guides/REPORTING-OPERATIONS.md +17 -11
- 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 +3 -1
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import { toReleasePrecisionVersion } from "../version.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
constants as cryptoConstants,
|
|
4
|
+
createHash,
|
|
5
|
+
createPrivateKey,
|
|
6
|
+
randomBytes,
|
|
7
|
+
sign as nodeSign
|
|
8
|
+
} from "crypto";
|
|
3
9
|
import { parseDictionary, parseItem, serializeItem } from "structured-headers";
|
|
4
10
|
import { ssrfSafeFetch } from "../net/index.mjs";
|
|
5
11
|
import { eTldPlusOne } from "./agent-resolver/etld.mjs";
|
|
6
12
|
import { canonicalAgentOrigin } from "./agent-resolver/canonicalize.mjs";
|
|
7
13
|
import { canonicalAgentUrl } from "./agent-resolver/select-agent.mjs";
|
|
8
14
|
import { parseStrictJson } from "./agent-resolver/strict-json.mjs";
|
|
9
|
-
import {
|
|
10
|
-
canonicalAuthority,
|
|
11
|
-
canonicalTargetUri,
|
|
12
|
-
getHeaderValue
|
|
13
|
-
} from "./canonicalize.mjs";
|
|
15
|
+
import { getHeaderValue } from "./canonicalize.mjs";
|
|
14
16
|
import { computeContentDigest, contentDigestMatches, contentDigestUsesEncoding } from "./content-digest.mjs";
|
|
15
17
|
import { jwkToPublicKey, verifySignature } from "./crypto.mjs";
|
|
16
18
|
import {
|
|
@@ -23,12 +25,14 @@ const WEB_BOT_AUTH_DRAFT_REVISION = "draft-ietf-webbotauth-httpsig-protocol-00";
|
|
|
23
25
|
const SIGNATURE_DIRECTORY_PATH = "/.well-known/http-message-signatures-directory";
|
|
24
26
|
const SIGNATURE_DIRECTORY_MEDIA_TYPE = "application/http-message-signatures-directory+json";
|
|
25
27
|
const DIRECTORY_TAG = "http-message-signatures-directory";
|
|
28
|
+
const DIRECTORY_CLOCK_SKEW_SECONDS = 60;
|
|
26
29
|
const TEST_KIDS = /* @__PURE__ */ new Set([
|
|
27
30
|
"6WsKMCObba49V3uN0vIENnk1BKAk7c9zjBJcWKyQ9_I",
|
|
28
31
|
"hHurKlVBnPJ4AGiPyl6HWMhxgkIcRBzaC8qWwEtnuVI",
|
|
29
32
|
"ZtaFf5jj-EzorJmZn6mX3YnmK_QyZKVLbbinzIQKOjQ",
|
|
30
33
|
"1XlJ3EIte7bPOEqKQtKrKNTNy5hywTyrwFXWrMbXKqE"
|
|
31
34
|
]);
|
|
35
|
+
const NO_CHALLENGE_REASONS = /* @__PURE__ */ new Set(["key_revoked", "identity_not_bound", "profile_disabled", "authorization_failed"]);
|
|
32
36
|
class WebBotAuthError extends Error {
|
|
33
37
|
constructor(status, reason) {
|
|
34
38
|
super(
|
|
@@ -37,13 +41,7 @@ class WebBotAuthError extends Error {
|
|
|
37
41
|
this.status = status;
|
|
38
42
|
this.reason = reason;
|
|
39
43
|
this.name = "WebBotAuthError";
|
|
40
|
-
this.headers = status === 403 && !
|
|
41
|
-
"key_revoked",
|
|
42
|
-
"identity_not_bound",
|
|
43
|
-
"revocation_unavailable",
|
|
44
|
-
"revocation_persistence_failed",
|
|
45
|
-
"profile_disabled"
|
|
46
|
-
].includes(reason) ? {
|
|
44
|
+
this.headers = status === 403 && !NO_CHALLENGE_REASONS.has(reason) ? {
|
|
47
45
|
"Accept-Signature": 'sig1=("@method" "@target-uri" "@authority" "content-type" "content-digest" "signature-agent";key="sig1");created;expires;nonce;keyid;tag="web-bot-auth"'
|
|
48
46
|
} : status === 429 ? { "Retry-After": "60" } : {};
|
|
49
47
|
}
|
|
@@ -51,19 +49,30 @@ class WebBotAuthError extends Error {
|
|
|
51
49
|
reason;
|
|
52
50
|
headers;
|
|
53
51
|
get body() {
|
|
54
|
-
if (
|
|
55
|
-
this.reason
|
|
56
|
-
))
|
|
52
|
+
if (this.reason === "authorization_failed" || this.reason === "identity_not_bound")
|
|
57
53
|
return {
|
|
58
|
-
adcp_error: {
|
|
59
|
-
code: this.reason.startsWith("revocation_") ? "SERVICE_UNAVAILABLE" : "PERMISSION_DENIED",
|
|
60
|
-
message: this.message,
|
|
61
|
-
recovery: this.reason.startsWith("revocation_") ? "transient" : "correctable"
|
|
62
|
-
}
|
|
54
|
+
adcp_error: { code: "PERMISSION_DENIED", message: "Agent authorization failed", recovery: "correctable" }
|
|
63
55
|
};
|
|
64
|
-
return {
|
|
56
|
+
return { adcp_error: { code: "AUTH_INVALID", message: this.message, recovery: "terminal" } };
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The signer's identity could not be established because key discovery
|
|
60
|
+
* failed (WBA §6.10) or its revocation status is unknown. The request is
|
|
61
|
+
* unverified; servers apply their unverified-request policy.
|
|
62
|
+
*/
|
|
63
|
+
get unverified() {
|
|
64
|
+
return this.reason.startsWith("directory_") || this.reason === "identity_unverified";
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
|
+
class WebBotAuthUnavailableError extends Error {
|
|
68
|
+
constructor(reason, cause) {
|
|
69
|
+
super("Web Bot Auth verification dependency is unavailable");
|
|
70
|
+
this.reason = reason;
|
|
71
|
+
this.name = "WebBotAuthUnavailableError";
|
|
72
|
+
if (cause !== void 0) Object.defineProperty(this, "cause", { value: cause });
|
|
73
|
+
}
|
|
74
|
+
reason;
|
|
75
|
+
}
|
|
67
76
|
function bad(reason) {
|
|
68
77
|
throw new WebBotAuthError(400, reason);
|
|
69
78
|
}
|
|
@@ -71,7 +80,8 @@ function denied(reason) {
|
|
|
71
80
|
throw new WebBotAuthError(403, reason);
|
|
72
81
|
}
|
|
73
82
|
function dictionary(value, field) {
|
|
74
|
-
if (
|
|
83
|
+
if (value === void 0 || value === "") return denied("headers_missing");
|
|
84
|
+
if (value.length > 64 * 1024) return bad("headers");
|
|
75
85
|
try {
|
|
76
86
|
rejectDuplicateDictionaryKeys(value, field);
|
|
77
87
|
return parseDictionary(value);
|
|
@@ -91,12 +101,18 @@ function jwkThumbprint(jwk) {
|
|
|
91
101
|
fields = { crv: jwk.crv, kty: jwk.kty, x: jwk.x };
|
|
92
102
|
else if (jwk.kty === "EC" && typeof jwk.crv === "string" && typeof jwk.x === "string" && typeof jwk.y === "string")
|
|
93
103
|
fields = { crv: jwk.crv, kty: jwk.kty, x: jwk.x, y: jwk.y };
|
|
104
|
+
else if (jwk.kty === "RSA" && typeof jwk.e === "string" && typeof jwk.n === "string")
|
|
105
|
+
fields = { e: jwk.e, kty: jwk.kty, n: jwk.n };
|
|
94
106
|
else return denied("unsupported_key");
|
|
95
107
|
return createHash("sha256").update(JSON.stringify(fields)).digest("base64url");
|
|
96
108
|
}
|
|
97
109
|
function algorithm(jwk) {
|
|
98
110
|
if (jwk.kty === "OKP" && jwk.crv === "Ed25519") return "ed25519";
|
|
99
111
|
if (jwk.kty === "EC" && jwk.crv === "P-256") return "ecdsa-p256-sha256";
|
|
112
|
+
if (jwk.kty === "RSA" && typeof jwk.n === "string" && jwk.e === "AQAB") {
|
|
113
|
+
const bytes = Buffer.from(jwk.n, "base64url").length;
|
|
114
|
+
if (bytes >= 256 && bytes <= 512) return "rsa-pss-sha512";
|
|
115
|
+
}
|
|
100
116
|
return denied("unsupported_key");
|
|
101
117
|
}
|
|
102
118
|
function inputs(headers) {
|
|
@@ -140,8 +156,8 @@ function buildWebBotAuthSignatureBase(input, request, response) {
|
|
|
140
156
|
if (params.has("key")) return bad("derived_dictionary");
|
|
141
157
|
if (response && bare !== "@status" && !params.has("req")) return bad("response_component");
|
|
142
158
|
if (bare === "@method") value = request.method.toUpperCase();
|
|
143
|
-
else if (bare === "@target-uri") value =
|
|
144
|
-
else if (bare === "@authority") value =
|
|
159
|
+
else if (bare === "@target-uri") value = receivedTargetUri(request.url);
|
|
160
|
+
else if (bare === "@authority") value = receivedAuthority(request.url);
|
|
145
161
|
else if (bare === "@status" && response && !params.has("req")) value = String(response.status);
|
|
146
162
|
else return bad("derived_component");
|
|
147
163
|
} else {
|
|
@@ -161,6 +177,21 @@ function buildWebBotAuthSignatureBase(input, request, response) {
|
|
|
161
177
|
});
|
|
162
178
|
return [...lines, `"@signature-params": ${input.signatureParamsValue}`].join("\n");
|
|
163
179
|
}
|
|
180
|
+
function receivedTargetUri(url) {
|
|
181
|
+
try {
|
|
182
|
+
new URL(url);
|
|
183
|
+
} catch {
|
|
184
|
+
return bad("target_uri");
|
|
185
|
+
}
|
|
186
|
+
return url;
|
|
187
|
+
}
|
|
188
|
+
function receivedAuthority(url) {
|
|
189
|
+
try {
|
|
190
|
+
return new URL(url).host.toLowerCase();
|
|
191
|
+
} catch {
|
|
192
|
+
return bad("target_uri");
|
|
193
|
+
}
|
|
194
|
+
}
|
|
164
195
|
function assertDigest(headers, body) {
|
|
165
196
|
const digest = getHeaderValue(headers, "content-digest");
|
|
166
197
|
if (!digest || !contentDigestUsesEncoding(digest, "rfc8941-base64") || !contentDigestMatches(digest, body))
|
|
@@ -170,6 +201,7 @@ function verifyBytes(input, request, jwk, response) {
|
|
|
170
201
|
const alg = algorithm(jwk);
|
|
171
202
|
if (input.params.alg !== void 0 && input.params.alg !== alg) denied("algorithm");
|
|
172
203
|
const signature = getHeaderValue(response?.headers ?? request.headers, "signature");
|
|
204
|
+
if (signature === void 0 || signature === "") denied("headers_missing");
|
|
173
205
|
let bytes;
|
|
174
206
|
try {
|
|
175
207
|
bytes = parseSignature(signature ?? "", input.label, "rfc8941-base64").bytes;
|
|
@@ -196,16 +228,63 @@ function registerWebBotAuthLegacyOriginGate(resolver, gate) {
|
|
|
196
228
|
function getWebBotAuthLegacyOriginGate(resolver) {
|
|
197
229
|
return resolver && legacyOriginGates.get(resolver);
|
|
198
230
|
}
|
|
231
|
+
const revocationCheckingResolvers = /* @__PURE__ */ new WeakSet();
|
|
232
|
+
function registerWebBotAuthRevocationCheck(resolver) {
|
|
233
|
+
revocationCheckingResolvers.add(resolver);
|
|
234
|
+
}
|
|
235
|
+
function isWebBotAuthRevocationChecked(resolver) {
|
|
236
|
+
return !!resolver && revocationCheckingResolvers.has(resolver);
|
|
237
|
+
}
|
|
238
|
+
function webBotAuthUnavailableResponse() {
|
|
239
|
+
return {
|
|
240
|
+
status: 503,
|
|
241
|
+
headers: { "Retry-After": "60" },
|
|
242
|
+
body: {
|
|
243
|
+
adcp_error: {
|
|
244
|
+
code: "SERVICE_UNAVAILABLE",
|
|
245
|
+
message: "Agent identity verification is temporarily unavailable",
|
|
246
|
+
recovery: "transient"
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
}
|
|
199
251
|
const authorizationFailures = /* @__PURE__ */ new WeakSet();
|
|
252
|
+
const AUTHORIZATION_FAILURE_MESSAGES = {
|
|
253
|
+
key_revoked: "Agent identity key is revoked",
|
|
254
|
+
identity_not_bound: "Agent identity is not onboarded for this operation",
|
|
255
|
+
revocation_unavailable: "Identity revocation information is unavailable",
|
|
256
|
+
authorization_failed: "Agent authorization failed",
|
|
257
|
+
authorization_unavailable: "Agent authorization is temporarily unavailable"
|
|
258
|
+
};
|
|
200
259
|
function createWebBotAuthAuthorizationFailure(reason) {
|
|
260
|
+
const transient = reason === "revocation_unavailable" || reason === "authorization_unavailable";
|
|
201
261
|
const failure = Object.freeze({
|
|
202
|
-
code:
|
|
203
|
-
message: reason
|
|
204
|
-
recovery:
|
|
262
|
+
code: transient ? "SERVICE_UNAVAILABLE" : "PERMISSION_DENIED",
|
|
263
|
+
message: AUTHORIZATION_FAILURE_MESSAGES[reason],
|
|
264
|
+
recovery: transient ? "transient" : "correctable"
|
|
205
265
|
});
|
|
206
266
|
authorizationFailures.add(failure);
|
|
207
267
|
return failure;
|
|
208
268
|
}
|
|
269
|
+
function principalFailureIsTransient(error) {
|
|
270
|
+
const value = error;
|
|
271
|
+
if (value?.transient === true || value?.recovery === "transient" || value?.code === "SERVICE_UNAVAILABLE")
|
|
272
|
+
return true;
|
|
273
|
+
return typeof value?.code !== "string";
|
|
274
|
+
}
|
|
275
|
+
function webBotAuthPrincipalFailureError(error) {
|
|
276
|
+
return principalFailureIsTransient(error) ? new WebBotAuthUnavailableError("authorization_unavailable", error) : new WebBotAuthError(403, "authorization_failed");
|
|
277
|
+
}
|
|
278
|
+
function deferWebBotAuthPrincipalFailure(signers, error) {
|
|
279
|
+
const signer = signers[signers.length - 1];
|
|
280
|
+
const authorizationFailure = createWebBotAuthAuthorizationFailure(
|
|
281
|
+
principalFailureIsTransient(error) ? "authorization_unavailable" : "authorization_failed"
|
|
282
|
+
);
|
|
283
|
+
return {
|
|
284
|
+
principal: `anonymous:${createHash("sha256").update(signer.origin).digest("hex")}`,
|
|
285
|
+
authorizationFailure
|
|
286
|
+
};
|
|
287
|
+
}
|
|
209
288
|
function getWebBotAuthAuthorizationFailure(extra) {
|
|
210
289
|
for (const value of [extra?.webBotAuthAuthorization, extra?.identity33]) {
|
|
211
290
|
const claim = value;
|
|
@@ -271,7 +350,8 @@ async function verifyWebBotAuth(request, options) {
|
|
|
271
350
|
const memberKey = component[1].get("key");
|
|
272
351
|
if (typeof memberKey !== "string" || memberKey !== input.label) bad("agent_member");
|
|
273
352
|
const member = agents.get(memberKey);
|
|
274
|
-
if (!member
|
|
353
|
+
if (!member) denied("agent_not_covered");
|
|
354
|
+
if (typeof member[0] !== "string") bad("signature_agent");
|
|
275
355
|
const type = member[1].get("type");
|
|
276
356
|
if (type !== void 0 && type !== "directory") denied("agent_type");
|
|
277
357
|
const origin = receivedAgentOrigin(member[0]);
|
|
@@ -291,27 +371,16 @@ async function verifyWebBotAuth(request, options) {
|
|
|
291
371
|
assertDigest(request.headers, request.body);
|
|
292
372
|
}
|
|
293
373
|
await options.directory.resolve(keyid, origin);
|
|
294
|
-
const
|
|
295
|
-
if (!
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
);
|
|
374
|
+
const checkedDirectory = options.directory.getVerifiedDirectory(origin);
|
|
375
|
+
if (!checkedDirectory || new URL(checkedDirectory.proof.request.url).origin !== origin)
|
|
376
|
+
denied("directory_origin");
|
|
377
|
+
if (checkedDirectory.expires <= now) denied("directory_expired");
|
|
378
|
+
if (!testKeysAllowed(options.allowTestKeys ?? options.directory.allowsTestKeys) && checkedDirectory.keys.some((key2) => TEST_KIDS.has(key2.kid)))
|
|
379
|
+
denied("directory_key");
|
|
301
380
|
const key = checkedDirectory.keys.find((key2) => key2.kid === keyid);
|
|
302
381
|
if (!key || jwkThumbprint(key) !== keyid) denied("key");
|
|
303
382
|
verifyBytes(input, request, key);
|
|
304
|
-
|
|
305
|
-
try {
|
|
306
|
-
if (await options.isRevoked(origin, keyid)) denied("key_revoked");
|
|
307
|
-
} catch (error) {
|
|
308
|
-
if (options.requireFreshRevocation === false && error instanceof WebBotAuthError && error.reason === "revocation_unavailable") {
|
|
309
|
-
} else if (options.deferAuthorizationErrors && error instanceof WebBotAuthError && ["key_revoked", "revocation_unavailable", "revocation_persistence_failed"].includes(error.reason))
|
|
310
|
-
authorizationFailure = createWebBotAuthAuthorizationFailure(
|
|
311
|
-
error.reason === "revocation_persistence_failed" ? "revocation_unavailable" : error.reason
|
|
312
|
-
);
|
|
313
|
-
else throw error;
|
|
314
|
-
}
|
|
383
|
+
if (options.isRevoked) await legacyRevocationCheck(options, origin, keyid);
|
|
315
384
|
const nonceTuple = JSON.stringify([origin, nonce]);
|
|
316
385
|
if (nonces.has(nonceTuple)) throw new WebBotAuthError(options.replayStatus ?? 429, "nonce_replayed");
|
|
317
386
|
nonces.add(nonceTuple);
|
|
@@ -320,20 +389,25 @@ async function verifyWebBotAuth(request, options) {
|
|
|
320
389
|
origin,
|
|
321
390
|
keyid,
|
|
322
391
|
label: input.label,
|
|
323
|
-
verified_at: now
|
|
324
|
-
...authorizationFailure && { authorizationFailure }
|
|
392
|
+
verified_at: now
|
|
325
393
|
});
|
|
326
394
|
verified.push(signer);
|
|
327
395
|
}
|
|
328
396
|
for (const signer of verified) {
|
|
329
397
|
const input = selected.find((i) => i.label === signer.label);
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
398
|
+
let result;
|
|
399
|
+
try {
|
|
400
|
+
result = await options.replayStore.insert(
|
|
401
|
+
signer.origin,
|
|
402
|
+
WEB_BOT_AUTH_PROFILE,
|
|
403
|
+
input.params.nonce,
|
|
404
|
+
// Acceptance is inclusive of expires + 60, so the nonce must outlive it.
|
|
405
|
+
input.params.expires + 60 - now + 1,
|
|
406
|
+
now
|
|
407
|
+
);
|
|
408
|
+
} catch (cause) {
|
|
409
|
+
throw new WebBotAuthUnavailableError("replay_unavailable", cause);
|
|
410
|
+
}
|
|
337
411
|
if (result !== "ok")
|
|
338
412
|
throw new WebBotAuthError(result === "replayed" ? options.replayStatus ?? 429 : 429, result);
|
|
339
413
|
}
|
|
@@ -342,48 +416,109 @@ async function verifyWebBotAuth(request, options) {
|
|
|
342
416
|
} catch (error) {
|
|
343
417
|
if (error instanceof WebBotAuthError) {
|
|
344
418
|
if (error.headers["Accept-Signature"]) {
|
|
345
|
-
|
|
419
|
+
let label = "sig1";
|
|
420
|
+
try {
|
|
421
|
+
label = inputs(request.headers)[0]?.label ?? "sig1";
|
|
422
|
+
} catch {
|
|
423
|
+
}
|
|
346
424
|
const bodyComponents = request.body ? '"content-type" "content-digest" ' : "";
|
|
347
425
|
error.headers["Accept-Signature"] = `${label}=("@method" "@target-uri" "@authority" ${bodyComponents}"signature-agent";key="${label}");created;expires;nonce;keyid;tag="web-bot-auth"`;
|
|
348
426
|
}
|
|
349
427
|
throw error;
|
|
350
428
|
}
|
|
429
|
+
if (error instanceof WebBotAuthUnavailableError) throw error;
|
|
351
430
|
return denied("verification_unavailable");
|
|
352
431
|
}
|
|
353
432
|
}
|
|
354
|
-
function
|
|
433
|
+
async function legacyRevocationCheck(options, origin, keyid) {
|
|
434
|
+
let revoked;
|
|
435
|
+
try {
|
|
436
|
+
revoked = await options.isRevoked(origin, keyid);
|
|
437
|
+
} catch (error) {
|
|
438
|
+
if (error instanceof WebBotAuthUnavailableError) throw error;
|
|
439
|
+
if (error instanceof WebBotAuthError && error.reason === "revocation_persistence_failed")
|
|
440
|
+
throw new WebBotAuthUnavailableError("revocation_persistence_failed", error);
|
|
441
|
+
if (error instanceof WebBotAuthError && error.reason === "key_revoked") return denied("key_revoked");
|
|
442
|
+
if (options.requireFreshRevocation === false && error instanceof WebBotAuthError && (error.reason === "revocation_unavailable" || error.reason === "revocation_absent"))
|
|
443
|
+
return;
|
|
444
|
+
return denied("identity_unverified");
|
|
445
|
+
}
|
|
446
|
+
if (revoked) denied("key_revoked");
|
|
447
|
+
}
|
|
448
|
+
function testKeysAllowed(allowTestKeys) {
|
|
449
|
+
return allowTestKeys === true && ["test", "development"].includes(process.env.NODE_ENV ?? "");
|
|
450
|
+
}
|
|
451
|
+
const UNSIGNED_DIRECTORY_MEDIA_TYPES = /* @__PURE__ */ new Set([
|
|
452
|
+
SIGNATURE_DIRECTORY_MEDIA_TYPE,
|
|
453
|
+
"application/jwk-set+json",
|
|
454
|
+
"application/json"
|
|
455
|
+
]);
|
|
456
|
+
function verifyKeyDirectory(response, now = Date.now() / 1e3, options = {}) {
|
|
355
457
|
try {
|
|
356
458
|
if (!Number.isFinite(now)) denied("directory_invalid");
|
|
357
459
|
const origin = canonicalAgentOrigin(response.request.url);
|
|
358
|
-
if (response.request.url !== origin + SIGNATURE_DIRECTORY_PATH || response.status !== 200
|
|
460
|
+
if (response.request.url !== origin + SIGNATURE_DIRECTORY_PATH || response.status !== 200)
|
|
461
|
+
denied("directory_response");
|
|
462
|
+
const signed = getHeaderValue(response.headers, "signature-input") !== void 0;
|
|
463
|
+
if (!signed && options.requireSigned) denied("directory_unsigned");
|
|
464
|
+
const mediaType = getHeaderValue(response.headers, "content-type")?.split(";")[0]?.trim().toLowerCase();
|
|
465
|
+
if (signed ? mediaType !== SIGNATURE_DIRECTORY_MEDIA_TYPE : !UNSIGNED_DIRECTORY_MEDIA_TYPES.has(mediaType ?? ""))
|
|
359
466
|
denied("directory_response");
|
|
360
467
|
const body = response.body ?? "";
|
|
361
468
|
if (Buffer.byteLength(body) > 256 * 1024) denied("directory_size");
|
|
362
|
-
|
|
469
|
+
if (signed || getHeaderValue(response.headers, "content-digest") !== void 0)
|
|
470
|
+
assertDirectoryDigest(response.headers, body);
|
|
363
471
|
const document = parseStrictJson(body, 32);
|
|
364
472
|
if (!Array.isArray(document.keys) || !document.keys.length || document.keys.length > 64) denied("directory_keys");
|
|
365
|
-
const signatures = inputs(response.headers);
|
|
366
|
-
const
|
|
473
|
+
const signatures = signed ? inputs(response.headers) : [];
|
|
474
|
+
const kids = /* @__PURE__ */ new Set();
|
|
475
|
+
const keys = [];
|
|
367
476
|
let expires = Infinity;
|
|
368
|
-
for (const
|
|
369
|
-
if (!
|
|
477
|
+
for (const raw of document.keys) {
|
|
478
|
+
if (!raw || typeof raw !== "object") denied("directory_key");
|
|
479
|
+
const thumbprint = jwkThumbprint(raw);
|
|
480
|
+
const key = { ...raw, kid: raw.kid ?? thumbprint };
|
|
481
|
+
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))
|
|
370
482
|
denied("directory_key");
|
|
371
|
-
|
|
483
|
+
algorithm(key);
|
|
484
|
+
kids.add(key.kid);
|
|
485
|
+
keys.push(key);
|
|
486
|
+
if (!signed) continue;
|
|
372
487
|
const proof = signatures.find((input) => input.params.keyid === key.kid && input.params.tag === DIRECTORY_TAG);
|
|
373
|
-
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)
|
|
488
|
+
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)
|
|
374
489
|
denied("directory_proof");
|
|
375
490
|
verifyBytes(proof, { ...response.request, headers: response.request.headers ?? {} }, key, response);
|
|
376
491
|
expires = Math.min(expires, proof.params.expires);
|
|
377
492
|
}
|
|
378
|
-
|
|
493
|
+
if (!signed) expires = now + unsignedAge(options.maxUnsignedAgeSeconds);
|
|
494
|
+
return { keys, expires, signed, proof: response };
|
|
379
495
|
} catch (error) {
|
|
380
|
-
if (error instanceof WebBotAuthError)
|
|
496
|
+
if (error instanceof WebBotAuthError) {
|
|
497
|
+
throw error.reason.startsWith("directory_") ? error : new WebBotAuthError(403, `directory_${error.reason}`);
|
|
498
|
+
}
|
|
381
499
|
return denied("directory_invalid");
|
|
382
500
|
}
|
|
383
501
|
}
|
|
502
|
+
function unsignedAge(seconds) {
|
|
503
|
+
const value = seconds ?? 86400;
|
|
504
|
+
if (!Number.isFinite(value) || value < 60)
|
|
505
|
+
throw new TypeError("Unsigned directory age must be a finite number of seconds, at least 60");
|
|
506
|
+
return value;
|
|
507
|
+
}
|
|
508
|
+
function verifySignatureDirectory(response, now = Date.now() / 1e3, allowTestKeys = false) {
|
|
509
|
+
return verifyKeyDirectory(response, now, { allowTestKeys, requireSigned: true });
|
|
510
|
+
}
|
|
511
|
+
function assertDirectoryDigest(headers, body) {
|
|
512
|
+
try {
|
|
513
|
+
assertDigest(headers, body);
|
|
514
|
+
} catch {
|
|
515
|
+
denied("directory_digest");
|
|
516
|
+
}
|
|
517
|
+
}
|
|
384
518
|
class SignatureDirectoryResolver {
|
|
385
519
|
constructor(options = {}) {
|
|
386
520
|
this.options = options;
|
|
521
|
+
unsignedAge(options.maxUnsignedDirectoryAgeSeconds);
|
|
387
522
|
}
|
|
388
523
|
options;
|
|
389
524
|
cache = /* @__PURE__ */ new Map();
|
|
@@ -395,19 +530,22 @@ class SignatureDirectoryResolver {
|
|
|
395
530
|
const origin = receivedAgentOrigin(signerOrigin);
|
|
396
531
|
const now = (this.options.now ?? (() => Date.now() / 1e3))();
|
|
397
532
|
const cached = this.cache.get(origin);
|
|
533
|
+
const usable = () => cached && cached.directory.expires > now ? cached.directory : void 0;
|
|
398
534
|
if (cached && cached.freshUntil > now && cached.directory.expires > now) {
|
|
399
535
|
const key = cached.directory.keys.find((k) => k.kid === keyid);
|
|
400
536
|
if (key || (this.keyMissCooldown.get(origin) ?? 0) > now) return key ?? null;
|
|
401
537
|
this.keyMissCooldown.set(origin, now + 60);
|
|
402
538
|
}
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
return denied("directory_unavailable");
|
|
406
|
-
}
|
|
539
|
+
const fromFallback = () => {
|
|
540
|
+
const key = usable()?.keys.find((k) => k.kid === keyid);
|
|
541
|
+
return key ?? denied("directory_unavailable");
|
|
542
|
+
};
|
|
543
|
+
if ((this.failures.get(origin) ?? 0) > now) return fromFallback();
|
|
407
544
|
let pending = this.pending.get(origin);
|
|
408
545
|
if (!pending) {
|
|
409
546
|
if ([...this.pending.keys()].filter((pendingOrigin) => eTldPlusOne(pendingOrigin) === eTldPlusOne(origin)).length >= 2 || this.pending.size >= (this.options.maxConcurrentFetches ?? 16)) {
|
|
410
|
-
|
|
547
|
+
const key = usable()?.keys.find((k) => k.kid === keyid);
|
|
548
|
+
if (key) return key;
|
|
411
549
|
throw new WebBotAuthError(429, "directory_fetch_limit");
|
|
412
550
|
}
|
|
413
551
|
pending = this.fetch(origin, now);
|
|
@@ -416,7 +554,7 @@ class SignatureDirectoryResolver {
|
|
|
416
554
|
try {
|
|
417
555
|
return (await pending).keys.find((k) => k.kid === keyid) ?? null;
|
|
418
556
|
} catch (error) {
|
|
419
|
-
if (
|
|
557
|
+
if (usable()) return fromFallback();
|
|
420
558
|
throw error;
|
|
421
559
|
} finally {
|
|
422
560
|
if (this.pending.get(origin) === pending) this.pending.delete(origin);
|
|
@@ -427,15 +565,22 @@ class SignatureDirectoryResolver {
|
|
|
427
565
|
const url = origin + SIGNATURE_DIRECTORY_PATH;
|
|
428
566
|
const result = await ssrfSafeFetch(url, {
|
|
429
567
|
...this.options.fetchOptions,
|
|
430
|
-
headers: {
|
|
568
|
+
headers: {
|
|
569
|
+
accept: `${SIGNATURE_DIRECTORY_MEDIA_TYPE}, application/jwk-set+json;q=0.9, application/json;q=0.8`
|
|
570
|
+
},
|
|
431
571
|
timeoutMs: 1e4,
|
|
432
572
|
maxBodyBytes: 256 * 1024
|
|
433
573
|
});
|
|
434
|
-
|
|
435
|
-
const
|
|
574
|
+
if (result.status !== 200) denied("directory_unavailable");
|
|
575
|
+
const body = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(result.body);
|
|
576
|
+
const directory = verifyKeyDirectory(
|
|
436
577
|
{ status: result.status, headers: result.headers, body, request: { method: "GET", url } },
|
|
437
578
|
now,
|
|
438
|
-
|
|
579
|
+
{
|
|
580
|
+
allowTestKeys: this.options.allowTestKeys,
|
|
581
|
+
requireSigned: this.options.requireSignedDirectories,
|
|
582
|
+
maxUnsignedAgeSeconds: this.options.maxUnsignedDirectoryAgeSeconds
|
|
583
|
+
}
|
|
439
584
|
);
|
|
440
585
|
const cacheControl = result.headers["cache-control"] ?? "";
|
|
441
586
|
const maxAge = /(?:^|,)\s*(?:no-cache|no-store)(?:,|$)/i.test(cacheControl) ? 0 : Number(cacheControl.match(/(?:^|,)\s*max-age=(\d+)/i)?.[1] ?? 300);
|
|
@@ -460,10 +605,14 @@ class SignatureDirectoryResolver {
|
|
|
460
605
|
}
|
|
461
606
|
}
|
|
462
607
|
get allowsTestKeys() {
|
|
463
|
-
return this.options.allowTestKeys
|
|
608
|
+
return testKeysAllowed(this.options.allowTestKeys);
|
|
609
|
+
}
|
|
610
|
+
/** The directory currently used to verify `origin`, including one kept through a failed refresh. */
|
|
611
|
+
getVerifiedDirectory(origin) {
|
|
612
|
+
return this.cache.get(receivedAgentOrigin(origin))?.directory;
|
|
464
613
|
}
|
|
465
614
|
getAuditProof(origin) {
|
|
466
|
-
return this.
|
|
615
|
+
return this.getVerifiedDirectory(origin)?.proof;
|
|
467
616
|
}
|
|
468
617
|
}
|
|
469
618
|
async function signBase(base, key) {
|
|
@@ -477,6 +626,12 @@ async function signBase(base, key) {
|
|
|
477
626
|
return key.provider.sign(Buffer.from(base));
|
|
478
627
|
}
|
|
479
628
|
const privateKey = createPrivateKey({ key: jwk, format: "jwk" });
|
|
629
|
+
if (alg === "rsa-pss-sha512")
|
|
630
|
+
return nodeSign("sha512", Buffer.from(base), {
|
|
631
|
+
key: privateKey,
|
|
632
|
+
padding: cryptoConstants.RSA_PKCS1_PSS_PADDING,
|
|
633
|
+
saltLength: 64
|
|
634
|
+
});
|
|
480
635
|
return alg === "ed25519" ? nodeSign(null, Buffer.from(base), privateKey) : nodeSign("sha256", Buffer.from(base), { key: privateKey, dsaEncoding: "ieee-p1363" });
|
|
481
636
|
}
|
|
482
637
|
async function signWebBotAuth(request, key, options) {
|
|
@@ -486,9 +641,15 @@ async function signWebBotAuth(request, key, options) {
|
|
|
486
641
|
const jwk = "privateKey" in key ? key.privateKey : key.publicKey;
|
|
487
642
|
const now = Math.floor((options.now ?? (() => Date.now() / 1e3))());
|
|
488
643
|
const window = options.windowSeconds ?? 300;
|
|
489
|
-
const nonce = options.nonce ?? randomBytes(
|
|
644
|
+
const nonce = options.nonce ?? randomBytes(64).toString("base64url");
|
|
490
645
|
if (!Number.isInteger(window) || window < 1 || window > 300 || !/^[A-Za-z0-9_+\/-]{22,256}={0,2}$/.test(nonce))
|
|
491
646
|
throw new TypeError("Invalid Web Bot Auth lifetime or nonce");
|
|
647
|
+
if (!options.relay && ["signature", "signature-input", "signature-agent"].some(
|
|
648
|
+
(name) => getHeaderValue(request.headers, name) !== void 0
|
|
649
|
+
))
|
|
650
|
+
throw new TypeError(
|
|
651
|
+
"Request already carries signature fields; pass relay: true to add a covering signature instead of replacing them"
|
|
652
|
+
);
|
|
492
653
|
const headers = {};
|
|
493
654
|
for (const [name, value] of Object.entries(request.headers))
|
|
494
655
|
if (value !== void 0 && !["signature", "signature-input", "signature-agent", "content-digest"].includes(name.toLowerCase()))
|
|
@@ -500,6 +661,8 @@ async function signWebBotAuth(request, key, options) {
|
|
|
500
661
|
headers["content-digest"] = computeContentDigest(request.body, "rfc8941-base64");
|
|
501
662
|
components.push('"content-type"', '"content-digest"');
|
|
502
663
|
}
|
|
664
|
+
if (options.relay && ["signature", "signature-input", "signature-agent"].some((name) => !getHeaderValue(request.headers, name)))
|
|
665
|
+
throw new TypeError("Relay requires the existing signature fields");
|
|
503
666
|
const earlier = options.relay ? inputs(request.headers) : [];
|
|
504
667
|
if (earlier.some((input2) => input2.label === label || input2.params.tag !== WEB_BOT_AUTH_PROFILE))
|
|
505
668
|
throw new TypeError("Relay requires existing Web Bot Auth signatures and a fresh label");
|
|
@@ -522,7 +685,10 @@ async function signWebBotAuth(request, key, options) {
|
|
|
522
685
|
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"`;
|
|
523
686
|
const input = parseSignatureInput(headers["signature-input"], label);
|
|
524
687
|
if (priorSignature) headers.signature = priorSignature;
|
|
525
|
-
const bytes = await signBase(
|
|
688
|
+
const bytes = await signBase(
|
|
689
|
+
buildWebBotAuthSignatureBase(input, { ...request, url: new URL(request.url).href, headers }),
|
|
690
|
+
key
|
|
691
|
+
);
|
|
526
692
|
headers.signature = `${priorSignature ? priorSignature + ", " : ""}${label}=:${Buffer.from(bytes).toString("base64")}:`;
|
|
527
693
|
return headers;
|
|
528
694
|
}
|
|
@@ -538,8 +704,7 @@ async function createSignedSignatureDirectory(originUrl, signingKeys, options =
|
|
|
538
704
|
const jwk = "privateKey" in key ? key.privateKey : key.publicKey;
|
|
539
705
|
return {
|
|
540
706
|
kty: jwk.kty,
|
|
541
|
-
crv: jwk.crv,
|
|
542
|
-
x: jwk.x,
|
|
707
|
+
...jwk.kty === "RSA" ? { n: jwk.n, e: jwk.e } : { crv: jwk.crv, x: jwk.x },
|
|
543
708
|
...jwk.y ? { y: jwk.y } : {},
|
|
544
709
|
kid: jwkThumbprint(jwk),
|
|
545
710
|
use: "sig"
|
|
@@ -560,7 +725,7 @@ async function createSignedSignatureDirectory(originUrl, signingKeys, options =
|
|
|
560
725
|
const signatureInputs = [], signatures = [];
|
|
561
726
|
for (let i = 0; i < keys.length; i++) {
|
|
562
727
|
const label = `key${i + 1}`;
|
|
563
|
-
const value = `${label}=("@authority";req "content-digest");created=${now};expires=${now + lifetime};keyid="${keys[i].kid}";alg="${algorithm(keys[i])}";tag="${DIRECTORY_TAG}"`;
|
|
728
|
+
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}"`;
|
|
564
729
|
const input = parseSignatureInput(value, label);
|
|
565
730
|
const bytes = await signBase(
|
|
566
731
|
buildWebBotAuthSignatureBase(input, { method: "GET", url: response.request.url, headers: {} }, response),
|
|
@@ -586,7 +751,7 @@ function createWebBotAuthFetch(upstream, key, options) {
|
|
|
586
751
|
if (init?.body != null) {
|
|
587
752
|
if (typeof init.body === "string") body = init.body;
|
|
588
753
|
else if (init.body instanceof Uint8Array || init.body instanceof ArrayBuffer)
|
|
589
|
-
body = new TextDecoder("utf-8", { fatal: true }).decode(init.body);
|
|
754
|
+
body = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(init.body);
|
|
590
755
|
else throw new TypeError("Web Bot Auth fetch requires a UTF-8 string or byte body");
|
|
591
756
|
}
|
|
592
757
|
const headers = {};
|
|
@@ -606,19 +771,26 @@ export {
|
|
|
606
771
|
WEB_BOT_AUTH_DRAFT_REVISION,
|
|
607
772
|
WEB_BOT_AUTH_PROFILE,
|
|
608
773
|
WebBotAuthError,
|
|
774
|
+
WebBotAuthUnavailableError,
|
|
609
775
|
buildWebBotAuthSignatureBase,
|
|
610
776
|
createSignedSignatureDirectory,
|
|
611
777
|
createWebBotAuthAuthorizationFailure,
|
|
612
778
|
createWebBotAuthFetch,
|
|
779
|
+
deferWebBotAuthPrincipalFailure,
|
|
613
780
|
getWebBotAuthAuthorizationFailure,
|
|
614
781
|
getWebBotAuthLegacyOriginGate,
|
|
615
782
|
isVerifiedWebBotSigner,
|
|
783
|
+
isWebBotAuthRevocationChecked,
|
|
616
784
|
jwkThumbprint,
|
|
617
785
|
receivedAgentOrigin,
|
|
618
786
|
registerWebBotAuthLegacyOriginGate,
|
|
787
|
+
registerWebBotAuthRevocationCheck,
|
|
619
788
|
selectSignatureProfile,
|
|
620
789
|
signWebBotAuth,
|
|
790
|
+
verifyKeyDirectory,
|
|
621
791
|
verifySignatureDirectory,
|
|
622
792
|
verifyWebBotAuth,
|
|
623
|
-
|
|
793
|
+
webBotAuthPrincipalFailureError,
|
|
794
|
+
webBotAuthToolFailure,
|
|
795
|
+
webBotAuthUnavailableResponse
|
|
624
796
|
};
|
|
@@ -209,7 +209,7 @@ async function verifyWebhookSignature(request, options) {
|
|
|
209
209
|
if (keyResolution.operatorAuthorizationValidUntil !== void 0) {
|
|
210
210
|
assertDelegatedOperatorAuthorizationActive(keyResolution.operatorAuthorizationValidUntil, currentTime());
|
|
211
211
|
}
|
|
212
|
-
const remaining = parsedInput.params.expires - now + import_types.CLOCK_SKEW_TOLERANCE_SECONDS;
|
|
212
|
+
const remaining = parsedInput.params.expires - now + import_types.CLOCK_SKEW_TOLERANCE_SECONDS + 1;
|
|
213
213
|
const ttl = Math.max(remaining, import_types.MAX_SIGNATURE_WINDOW_SECONDS + import_types.CLOCK_SKEW_TOLERANCE_SECONDS);
|
|
214
214
|
const insertResult = await options.replayStore.insert(jwk.kid, replayScope, parsedInput.params.nonce, ttl, now);
|
|
215
215
|
if (insertResult === "replayed") {
|
|
@@ -182,7 +182,7 @@ async function verifyWebhookSignature(request, options) {
|
|
|
182
182
|
if (keyResolution.operatorAuthorizationValidUntil !== void 0) {
|
|
183
183
|
assertDelegatedOperatorAuthorizationActive(keyResolution.operatorAuthorizationValidUntil, currentTime());
|
|
184
184
|
}
|
|
185
|
-
const remaining = parsedInput.params.expires - now + CLOCK_SKEW_TOLERANCE_SECONDS;
|
|
185
|
+
const remaining = parsedInput.params.expires - now + CLOCK_SKEW_TOLERANCE_SECONDS + 1;
|
|
186
186
|
const ttl = Math.max(remaining, MAX_SIGNATURE_WINDOW_SECONDS + CLOCK_SKEW_TOLERANCE_SECONDS);
|
|
187
187
|
const insertResult = await options.replayStore.insert(jwk.kid, replayScope, parsedInput.params.nonce, ttl, now);
|
|
188
188
|
if (insertResult === "replayed") {
|
|
@@ -197,7 +197,7 @@ export interface UpstreamTrafficQueryResult {
|
|
|
197
197
|
*/
|
|
198
198
|
export declare function runValidations(validations: StoryboardValidation[], context: ValidationContext): ValidationResult[];
|
|
199
199
|
/** Capability semver emitted in run summaries and used for advisory expiry. */
|
|
200
|
-
export declare const RUNNER_CAPABILITY_VERSION = "16.0.0-beta.
|
|
200
|
+
export declare const RUNNER_CAPABILITY_VERSION = "16.0.0-beta.2";
|
|
201
201
|
/** True when a failed validation contributes to the owning step's grade. */
|
|
202
202
|
export declare function validationFailsStep(result: ValidationResult): boolean;
|
|
203
203
|
/**
|
|
@@ -197,7 +197,7 @@ export interface UpstreamTrafficQueryResult {
|
|
|
197
197
|
*/
|
|
198
198
|
export declare function runValidations(validations: StoryboardValidation[], context: ValidationContext): ValidationResult[];
|
|
199
199
|
/** Capability semver emitted in run summaries and used for advisory expiry. */
|
|
200
|
-
export declare const RUNNER_CAPABILITY_VERSION = "16.0.0-beta.
|
|
200
|
+
export declare const RUNNER_CAPABILITY_VERSION = "16.0.0-beta.2";
|
|
201
201
|
/** True when a failed validation contributes to the owning step's grade. */
|
|
202
202
|
export declare function validationFailsStep(result: ValidationResult): boolean;
|
|
203
203
|
/**
|