@adcp/sdk 16.0.0-beta.3 → 16.0.0-beta.4
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/auth/oauth/diagnose.js +3 -11
- package/dist/lib/auth/oauth/diagnose.mjs +4 -12
- package/dist/lib/auth/oauth/diagnostics.d.mts +2 -1
- package/dist/lib/auth/oauth/diagnostics.d.ts +2 -1
- package/dist/lib/auth/oauth/diagnostics.js +8 -10
- package/dist/lib/auth/oauth/diagnostics.mjs +7 -10
- 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 +7 -1
- package/dist/lib/reporting/consumer-runtime.js +2 -1
- package/dist/lib/reporting/consumer-runtime.mjs +2 -1
- package/dist/lib/reporting/ledger/handler.js +50 -11
- package/dist/lib/reporting/ledger/handler.mjs +50 -12
- package/dist/lib/reporting/ledger/index.d.mts +17 -14
- package/dist/lib/reporting/ledger/index.d.ts +17 -14
- package/dist/lib/reporting/ledger/index.js +32 -20
- package/dist/lib/reporting/ledger/index.mjs +21 -10
- package/dist/lib/reporting/ledger/offering-validation.d.mts +9 -0
- package/dist/lib/reporting/ledger/offering-validation.d.ts +9 -0
- package/dist/lib/reporting/{gcs/bucket-policy.js → ledger/offering-validation.js} +9 -8
- package/dist/lib/reporting/ledger/offering-validation.mjs +8 -0
- package/dist/lib/reporting/ledger/receipts.d.mts +48 -0
- package/dist/lib/reporting/ledger/receipts.d.ts +48 -0
- package/dist/lib/reporting/ledger/receipts.js +186 -0
- package/dist/lib/reporting/ledger/receipts.mjs +161 -0
- package/dist/lib/reporting/ledger/status-ingest.js +3 -3
- package/dist/lib/reporting/ledger/status-ingest.mjs +4 -5
- package/dist/lib/reporting/ledger/types.d.mts +43 -0
- package/dist/lib/reporting/ledger/types.d.ts +43 -0
- package/dist/lib/reporting/ledger/types.js +31 -2
- package/dist/lib/reporting/ledger/types.mjs +26 -1
- package/dist/lib/reporting/pagination.d.mts +6 -0
- package/dist/lib/reporting/pagination.d.ts +6 -0
- package/dist/lib/reporting/{gcs/errors.js → pagination.js} +12 -17
- package/dist/lib/reporting/pagination.mjs +12 -0
- package/dist/lib/reporting/reconciliation.d.mts +1 -0
- package/dist/lib/reporting/reconciliation.d.ts +1 -0
- package/dist/lib/reporting/reconciliation.js +3 -2
- package/dist/lib/reporting/reconciliation.mjs +3 -2
- package/dist/lib/reporting/service/index.d.mts +79 -153
- package/dist/lib/reporting/service/index.d.ts +79 -153
- package/dist/lib/reporting/service/index.js +34 -491
- package/dist/lib/reporting/service/index.mjs +36 -510
- package/dist/lib/reporting/service/scheduler-support.d.mts +43 -0
- package/dist/lib/reporting/service/scheduler-support.d.ts +43 -0
- package/dist/lib/reporting/service/scheduler-support.js +125 -0
- package/dist/lib/reporting/service/scheduler-support.mjs +96 -0
- package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
- package/dist/lib/signing/canonicalize.d.mts +4 -0
- package/dist/lib/signing/canonicalize.d.ts +4 -0
- package/dist/lib/signing/canonicalize.js +14 -8
- package/dist/lib/signing/canonicalize.mjs +14 -8
- package/dist/lib/signing/content-digest.d.mts +8 -0
- package/dist/lib/signing/content-digest.d.ts +8 -0
- package/dist/lib/signing/content-digest.js +11 -5
- package/dist/lib/signing/content-digest.mjs +10 -5
- package/dist/lib/signing/signer.js +9 -6
- package/dist/lib/signing/signer.mjs +10 -7
- package/dist/lib/signing/web-bot-auth.js +103 -37
- package/dist/lib/signing/web-bot-auth.mjs +108 -37
- package/dist/lib/testing/storyboard/junit.js +16 -3
- package/dist/lib/testing/storyboard/junit.mjs +16 -3
- package/dist/lib/testing/storyboard/request-signing/builder.js +10 -9
- package/dist/lib/testing/storyboard/request-signing/builder.mjs +10 -9
- package/dist/lib/testing/storyboard/runner.js +94 -25
- package/dist/lib/testing/storyboard/runner.mjs +94 -25
- package/dist/lib/testing/storyboard/types.d.mts +6 -0
- package/dist/lib/testing/storyboard/types.d.ts +6 -0
- package/dist/lib/testing/storyboard/validations.d.mts +1 -1
- package/dist/lib/testing/storyboard/validations.d.ts +1 -1
- package/dist/lib/version.d.mts +3 -3
- package/dist/lib/version.d.ts +3 -3
- package/dist/lib/version.js +3 -3
- package/dist/lib/version.mjs +3 -3
- package/docs/TYPE-SUMMARY.md +23 -5
- package/docs/guides/REPORTING-GCS-FENCE.md +3 -3
- package/docs/guides/REPORTING-GCS-MANAGED.md +4 -4
- package/docs/guides/REPORTING-LEDGER-CUTOVER.md +7 -7
- package/docs/guides/REPORTING-LEDGER.md +107 -51
- package/docs/guides/REPORTING-OPERATIONS.md +66 -5
- package/docs/guides/REPORTING-STABILITY.md +36 -32
- package/docs/llms.txt +4 -3
- package/docs/migration-14.x-rc-worksheet.md +4 -7
- package/docs/migration-reporting-storage-package.md +186 -0
- package/examples/README.md +1 -1
- package/package.json +13 -78
- package/dist/lib/reporting/azure/index.d.mts +0 -31
- package/dist/lib/reporting/azure/index.d.ts +0 -31
- package/dist/lib/reporting/azure/index.js +0 -266
- package/dist/lib/reporting/azure/index.mjs +0 -249
- package/dist/lib/reporting/bigquery/index.d.mts +0 -246
- package/dist/lib/reporting/bigquery/index.d.ts +0 -246
- package/dist/lib/reporting/bigquery/index.js +0 -633
- package/dist/lib/reporting/bigquery/index.mjs +0 -594
- package/dist/lib/reporting/gcs/adapter.d.mts +0 -38
- package/dist/lib/reporting/gcs/adapter.d.ts +0 -38
- package/dist/lib/reporting/gcs/adapter.js +0 -342
- package/dist/lib/reporting/gcs/adapter.mjs +0 -325
- package/dist/lib/reporting/gcs/bucket-policy.d.mts +0 -3
- package/dist/lib/reporting/gcs/bucket-policy.d.ts +0 -3
- package/dist/lib/reporting/gcs/bucket-policy.mjs +0 -7
- package/dist/lib/reporting/gcs/errors.d.mts +0 -11
- package/dist/lib/reporting/gcs/errors.d.ts +0 -11
- package/dist/lib/reporting/gcs/errors.mjs +0 -16
- package/dist/lib/reporting/gcs/fence.d.mts +0 -41
- package/dist/lib/reporting/gcs/fence.d.ts +0 -41
- package/dist/lib/reporting/gcs/fence.js +0 -250
- package/dist/lib/reporting/gcs/fence.mjs +0 -230
- package/dist/lib/reporting/gcs/index.d.mts +0 -18
- package/dist/lib/reporting/gcs/index.d.ts +0 -18
- package/dist/lib/reporting/gcs/index.js +0 -44
- package/dist/lib/reporting/gcs/index.mjs +0 -14
- package/dist/lib/reporting/gcs/io.d.mts +0 -7
- package/dist/lib/reporting/gcs/io.d.ts +0 -7
- package/dist/lib/reporting/gcs/io.js +0 -95
- package/dist/lib/reporting/gcs/io.mjs +0 -69
- package/dist/lib/reporting/gcs/reader.d.mts +0 -67
- package/dist/lib/reporting/gcs/reader.d.ts +0 -67
- package/dist/lib/reporting/gcs/reader.js +0 -193
- package/dist/lib/reporting/gcs/reader.mjs +0 -169
- package/dist/lib/reporting/gcs/row-provider.d.mts +0 -39
- package/dist/lib/reporting/gcs/row-provider.d.ts +0 -39
- package/dist/lib/reporting/gcs/row-provider.js +0 -225
- package/dist/lib/reporting/gcs/row-provider.mjs +0 -208
- package/dist/lib/reporting/gcs/validation.d.mts +0 -5
- package/dist/lib/reporting/gcs/validation.d.ts +0 -5
- package/dist/lib/reporting/gcs/validation.js +0 -49
- package/dist/lib/reporting/gcs/validation.mjs +0 -25
- package/dist/lib/reporting/ledger/change-feed.d.mts +0 -69
- package/dist/lib/reporting/ledger/change-feed.d.ts +0 -69
- package/dist/lib/reporting/ledger/change-feed.js +0 -107
- package/dist/lib/reporting/ledger/change-feed.mjs +0 -80
- package/dist/lib/reporting/ledger/managed-postgres.d.mts +0 -293
- package/dist/lib/reporting/ledger/managed-postgres.d.ts +0 -293
- package/dist/lib/reporting/ledger/managed-postgres.js +0 -2346
- package/dist/lib/reporting/ledger/managed-postgres.mjs +0 -2332
- package/dist/lib/reporting/ledger/managed-router.d.mts +0 -148
- package/dist/lib/reporting/ledger/managed-router.d.ts +0 -148
- package/dist/lib/reporting/ledger/managed-router.js +0 -280
- package/dist/lib/reporting/ledger/managed-router.mjs +0 -258
- package/dist/lib/reporting/ledger/managed.d.mts +0 -535
- package/dist/lib/reporting/ledger/managed.d.ts +0 -535
- package/dist/lib/reporting/ledger/managed.js +0 -761
- package/dist/lib/reporting/ledger/managed.mjs +0 -729
- package/dist/lib/reporting/ledger/notification-activity.d.mts +0 -332
- package/dist/lib/reporting/ledger/notification-activity.d.ts +0 -332
- package/dist/lib/reporting/ledger/notification-activity.js +0 -1366
- package/dist/lib/reporting/ledger/notification-activity.mjs +0 -1337
- package/dist/lib/reporting/ledger/object-writes.d.mts +0 -128
- package/dist/lib/reporting/ledger/object-writes.d.ts +0 -128
- package/dist/lib/reporting/ledger/object-writes.js +0 -147
- package/dist/lib/reporting/ledger/object-writes.mjs +0 -114
- package/dist/lib/reporting/ledger/postgres.d.mts +0 -689
- package/dist/lib/reporting/ledger/postgres.d.ts +0 -689
- package/dist/lib/reporting/ledger/postgres.js +0 -4462
- package/dist/lib/reporting/ledger/postgres.mjs +0 -4462
- package/dist/lib/reporting/ledger/row-storage-migration.d.mts +0 -59
- package/dist/lib/reporting/ledger/row-storage-migration.d.ts +0 -59
- package/dist/lib/reporting/ledger/row-storage-migration.js +0 -365
- package/dist/lib/reporting/ledger/row-storage-migration.mjs +0 -339
- package/dist/lib/reporting/ledger/row-storage-object.d.mts +0 -203
- package/dist/lib/reporting/ledger/row-storage-object.d.ts +0 -203
- package/dist/lib/reporting/ledger/row-storage-object.js +0 -379
- package/dist/lib/reporting/ledger/row-storage-object.mjs +0 -335
- package/dist/lib/reporting/ledger/row-storage-provider-io.d.mts +0 -44
- package/dist/lib/reporting/ledger/row-storage-provider-io.d.ts +0 -44
- package/dist/lib/reporting/ledger/row-storage-provider-io.js +0 -142
- package/dist/lib/reporting/ledger/row-storage-provider-io.mjs +0 -111
- package/dist/lib/reporting/ledger/row-storage.d.mts +0 -316
- package/dist/lib/reporting/ledger/row-storage.d.ts +0 -316
- package/dist/lib/reporting/ledger/row-storage.js +0 -812
- package/dist/lib/reporting/ledger/row-storage.mjs +0 -802
- package/dist/lib/reporting/s3/index.d.mts +0 -34
- package/dist/lib/reporting/s3/index.d.ts +0 -34
- package/dist/lib/reporting/s3/index.js +0 -347
- package/dist/lib/reporting/s3/index.mjs +0 -340
- package/dist/lib/reporting/service/maintenance.d.mts +0 -148
- package/dist/lib/reporting/service/maintenance.d.ts +0 -148
- package/dist/lib/reporting/service/maintenance.js +0 -185
- package/dist/lib/reporting/service/maintenance.mjs +0 -159
- package/examples/reliable-reporting-service/README.md +0 -169
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
buildSignatureBase,
|
|
6
6
|
formatSignatureParams
|
|
7
7
|
} from "./canonicalize.mjs";
|
|
8
|
-
import { computeContentDigest } from "./content-digest.mjs";
|
|
8
|
+
import { computeContentDigest, serializeByteSequenceMember } from "./content-digest.mjs";
|
|
9
9
|
import { RequestSignatureError, ResponseSignatureError, WebhookSignatureError } from "./errors.mjs";
|
|
10
10
|
import {
|
|
11
11
|
MANDATORY_COMPONENTS,
|
|
@@ -59,6 +59,7 @@ function prepareRequestSignature(request, identity, options = {}) {
|
|
|
59
59
|
const windowSeconds = Math.min(options.windowSeconds ?? 300, MAX_SIGNATURE_WINDOW_SECONDS);
|
|
60
60
|
const nonce = options.nonce ?? base64UrlRandom(16);
|
|
61
61
|
const label = options.label ?? "sig1";
|
|
62
|
+
assertLabelAndClock(label, now);
|
|
62
63
|
const hasBody = (request.body ?? "").length > 0;
|
|
63
64
|
const binaryEncoding = options.binaryEncoding ?? "legacy-base64url";
|
|
64
65
|
const coverDigest = options.coverContentDigest ?? binaryEncoding === "rfc8941-base64";
|
|
@@ -89,11 +90,8 @@ function prepareRequestSignature(request, identity, options = {}) {
|
|
|
89
90
|
}
|
|
90
91
|
function finalizeRequestSignature(prepared, signature) {
|
|
91
92
|
const headers = { ...prepared.headers };
|
|
92
|
-
const sigB64 = Buffer.from(signature).toString(
|
|
93
|
-
prepared.binaryEncoding === "legacy-base64url" ? "base64url" : "base64"
|
|
94
|
-
);
|
|
95
93
|
headers["Signature-Input"] = `${prepared.label}=${formatSignatureParams(prepared.components, prepared.params)}`;
|
|
96
|
-
headers["Signature"] =
|
|
94
|
+
headers["Signature"] = serializeByteSequenceMember(prepared.label, signature, prepared.binaryEncoding);
|
|
97
95
|
return { headers, signatureBase: prepared.base, params: prepared.params };
|
|
98
96
|
}
|
|
99
97
|
function signRequest(request, key, options = {}) {
|
|
@@ -115,6 +113,7 @@ function prepareWebhookSignature(request, identity, options = {}) {
|
|
|
115
113
|
const windowSeconds = Math.min(options.windowSeconds ?? 300, MAX_SIGNATURE_WINDOW_SECONDS);
|
|
116
114
|
const nonce = options.nonce ?? base64UrlRandom(16);
|
|
117
115
|
const label = options.label ?? "sig1";
|
|
116
|
+
assertLabelAndClock(label, now);
|
|
118
117
|
const headers = { ...flattenHeaders(request.headers) };
|
|
119
118
|
headers["Content-Digest"] = computeContentDigest(request.body ?? "", "legacy-base64url");
|
|
120
119
|
const components = [...WEBHOOK_MANDATORY_COMPONENTS];
|
|
@@ -141,6 +140,7 @@ function prepareResponseSignature(response, identity, options = {}) {
|
|
|
141
140
|
const windowSeconds = Math.min(options.windowSeconds ?? 300, MAX_SIGNATURE_WINDOW_SECONDS);
|
|
142
141
|
const nonce = options.nonce ?? base64UrlRandom(16);
|
|
143
142
|
const label = options.label ?? "sig1";
|
|
143
|
+
assertLabelAndClock(label, now);
|
|
144
144
|
const hasBody = (response.body ?? "").length > 0;
|
|
145
145
|
const coverDigest = (options.coverContentDigest ?? true) && hasBody;
|
|
146
146
|
const headers = { ...flattenHeaders(response.headers) };
|
|
@@ -169,9 +169,8 @@ function prepareResponseSignature(response, identity, options = {}) {
|
|
|
169
169
|
}
|
|
170
170
|
function finalizeResponseSignature(prepared, signature) {
|
|
171
171
|
const headers = { ...prepared.headers };
|
|
172
|
-
const sigB64 = Buffer.from(signature).toString("base64url");
|
|
173
172
|
headers["Signature-Input"] = `${prepared.label}=${formatSignatureParams(prepared.components, prepared.params)}`;
|
|
174
|
-
headers["Signature"] =
|
|
173
|
+
headers["Signature"] = serializeByteSequenceMember(prepared.label, signature, "legacy-base64url");
|
|
175
174
|
return { status: prepared.status, headers, signatureBase: prepared.base, params: prepared.params };
|
|
176
175
|
}
|
|
177
176
|
function signResponse(response, key, options = {}) {
|
|
@@ -191,6 +190,10 @@ function flattenHeaders(headers) {
|
|
|
191
190
|
}
|
|
192
191
|
return out;
|
|
193
192
|
}
|
|
193
|
+
function assertLabelAndClock(label, now) {
|
|
194
|
+
if (!/^[a-z*][a-z0-9_.*-]*$/.test(label)) throw new TypeError(`Signature label "${label}" is not an RFC 8941 key`);
|
|
195
|
+
if (!Number.isSafeInteger(now)) throw new TypeError("Signature clock must return integer epoch seconds");
|
|
196
|
+
}
|
|
194
197
|
function base64UrlRandom(byteLength) {
|
|
195
198
|
return randomBytes(byteLength).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
|
|
196
199
|
}
|
|
@@ -51,6 +51,7 @@ module.exports = __toCommonJS(web_bot_auth_exports);
|
|
|
51
51
|
var import_version = require('../version.js');
|
|
52
52
|
var import_crypto = require("crypto");
|
|
53
53
|
var import_structured_headers = require("structured-headers");
|
|
54
|
+
var import_http_message_sig = require("http-message-sig");
|
|
54
55
|
var import_net = require('../net/index.js');
|
|
55
56
|
var import_etld = require('./agent-resolver/etld.js');
|
|
56
57
|
var import_canonicalize = require('./agent-resolver/canonicalize.js');
|
|
@@ -176,11 +177,11 @@ function receivedAgentOrigin(value) {
|
|
|
176
177
|
}
|
|
177
178
|
}
|
|
178
179
|
function buildWebBotAuthSignatureBase(input, request, response) {
|
|
179
|
-
const lines = input.components.map((
|
|
180
|
-
const bare =
|
|
180
|
+
const lines = input.components.map((component2) => {
|
|
181
|
+
const bare = component2.split(";", 1)[0];
|
|
181
182
|
let identifier;
|
|
182
183
|
try {
|
|
183
|
-
identifier = (0, import_structured_headers.parseItem)(`"${bare}"${
|
|
184
|
+
identifier = (0, import_structured_headers.parseItem)(`"${bare}"${component2.slice(bare.length)}`);
|
|
184
185
|
} catch {
|
|
185
186
|
return bad("component");
|
|
186
187
|
}
|
|
@@ -398,8 +399,8 @@ async function verifyWebBotAuth(request, options) {
|
|
|
398
399
|
(c) => c.startsWith("signature-agent;key=") && !verified.some((s) => selected.find((i) => i.label === s.label).components.includes(c))
|
|
399
400
|
);
|
|
400
401
|
if (agentComponents.length !== 1) denied("agent_not_covered");
|
|
401
|
-
const
|
|
402
|
-
const memberKey =
|
|
402
|
+
const component2 = (0, import_structured_headers.parseItem)(`"${agentComponents[0].replace("signature-agent;", 'signature-agent";')}`);
|
|
403
|
+
const memberKey = component2[1].get("key");
|
|
403
404
|
if (typeof memberKey !== "string" || memberKey !== input.label) bad("agent_member");
|
|
404
405
|
const member = agents.get(memberKey);
|
|
405
406
|
if (!member) denied("agent_not_covered");
|
|
@@ -667,7 +668,7 @@ class SignatureDirectoryResolver {
|
|
|
667
668
|
return this.getVerifiedDirectory(origin)?.proof;
|
|
668
669
|
}
|
|
669
670
|
}
|
|
670
|
-
|
|
671
|
+
function webBotSigner(key) {
|
|
671
672
|
const jwk = "privateKey" in key ? key.privateKey : key.publicKey;
|
|
672
673
|
const alg = algorithm(jwk);
|
|
673
674
|
if (!["test", "development"].includes(process.env.NODE_ENV ?? "") && TEST_KIDS.has(jwkThumbprint(jwk)))
|
|
@@ -675,17 +676,40 @@ async function signBase(base, key) {
|
|
|
675
676
|
if ("provider" in key) {
|
|
676
677
|
if (key.provider.keyid !== jwkThumbprint(jwk) || key.provider.algorithm !== alg)
|
|
677
678
|
throw new TypeError("Web Bot Auth provider identity does not match its public key");
|
|
678
|
-
return key.provider.sign(Buffer.from(
|
|
679
|
+
return { algorithm: alg, sign: async (data) => Buffer.from(await key.provider.sign(Buffer.from(data))) };
|
|
679
680
|
}
|
|
680
681
|
if (alg === "rsa-pss-sha512") {
|
|
681
682
|
const privateKey = (0, import_crypto.createPrivateKey)({ key: jwk, format: "jwk" });
|
|
682
|
-
return
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
683
|
+
return {
|
|
684
|
+
algorithm: alg,
|
|
685
|
+
sign: (data) => (0, import_crypto.sign)("sha512", Buffer.from(data), {
|
|
686
|
+
key: privateKey,
|
|
687
|
+
padding: import_crypto.constants.RSA_PKCS1_PSS_PADDING,
|
|
688
|
+
saltLength: 64
|
|
689
|
+
})
|
|
690
|
+
};
|
|
687
691
|
}
|
|
688
|
-
return (0, import_crypto2.signWithPrivateJwkAsync)(alg, jwk, Buffer.from(
|
|
692
|
+
return { algorithm: alg, sign: (data) => (0, import_crypto2.signWithPrivateJwkAsync)(alg, jwk, Buffer.from(data)) };
|
|
693
|
+
}
|
|
694
|
+
async function createWebBotSignature(...args) {
|
|
695
|
+
try {
|
|
696
|
+
return await (0, import_http_message_sig.createSignature)(...args);
|
|
697
|
+
} catch (error) {
|
|
698
|
+
if ((0, import_http_message_sig.isSignatureError)(error)) throw new WebBotAuthError(400, "component_value");
|
|
699
|
+
throw error;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
function componentDescriptor(identifier) {
|
|
703
|
+
const bare = identifier.split(";", 1)[0];
|
|
704
|
+
const [, params] = (0, import_structured_headers.parseItem)(`"${bare}"${identifier.slice(bare.length)}`);
|
|
705
|
+
return (0, import_http_message_sig.component)(bare, Object.fromEntries(params));
|
|
706
|
+
}
|
|
707
|
+
function coveredFields(components, headers) {
|
|
708
|
+
const names = new Set(components.filter((c) => !c.name.startsWith("@")).map((c) => c.name));
|
|
709
|
+
return [...names].flatMap((name) => {
|
|
710
|
+
const value = (0, import_canonicalize2.getHeaderValue)(headers, name);
|
|
711
|
+
return value === void 0 ? [] : [{ name, value }];
|
|
712
|
+
});
|
|
689
713
|
}
|
|
690
714
|
async function signWebBotAuth(request, key, options) {
|
|
691
715
|
const origin = (0, import_canonicalize.canonicalAgentOrigin)(options.origin);
|
|
@@ -695,6 +719,7 @@ async function signWebBotAuth(request, key, options) {
|
|
|
695
719
|
const now = Math.floor((options.now ?? (() => Date.now() / 1e3))());
|
|
696
720
|
const window = options.windowSeconds ?? 300;
|
|
697
721
|
const nonce = options.nonce ?? (0, import_crypto.randomBytes)(64).toString("base64url");
|
|
722
|
+
if (!Number.isSafeInteger(now)) throw new TypeError("Invalid Web Bot Auth clock");
|
|
698
723
|
if (!Number.isInteger(window) || window < 1 || window > 300 || !/^[A-Za-z0-9_+\/-]{22,256}={0,2}$/.test(nonce))
|
|
699
724
|
throw new TypeError("Invalid Web Bot Auth lifetime or nonce");
|
|
700
725
|
if (!options.relay && ["signature", "signature-input", "signature-agent"].some(
|
|
@@ -707,42 +732,69 @@ async function signWebBotAuth(request, key, options) {
|
|
|
707
732
|
for (const [name, value] of Object.entries(request.headers))
|
|
708
733
|
if (value !== void 0 && !["signature", "signature-input", "signature-agent", "content-digest"].includes(name.toLowerCase()))
|
|
709
734
|
headers[name.toLowerCase()] = Array.isArray(value) ? value.join(", ") : value;
|
|
710
|
-
const components = [
|
|
735
|
+
const components = [(0, import_http_message_sig.component)("@method"), (0, import_http_message_sig.component)("@target-uri"), (0, import_http_message_sig.component)("@authority")];
|
|
711
736
|
if (request.body !== void 0 && request.body !== "") {
|
|
712
737
|
if (!(0, import_canonicalize2.getHeaderValue)(headers, "content-type"))
|
|
713
738
|
throw new TypeError("Body-bearing Web Bot Auth requests need Content-Type");
|
|
714
739
|
headers["content-digest"] = (0, import_content_digest.computeContentDigest)(request.body, "rfc8941-base64");
|
|
715
|
-
components.push(
|
|
740
|
+
components.push((0, import_http_message_sig.component)("content-type"), (0, import_http_message_sig.component)("content-digest"));
|
|
716
741
|
}
|
|
717
742
|
if (options.relay && ["signature", "signature-input", "signature-agent"].some((name) => !(0, import_canonicalize2.getHeaderValue)(request.headers, name)))
|
|
718
743
|
throw new TypeError("Relay requires the existing signature fields");
|
|
719
744
|
const earlier = options.relay ? inputs(request.headers) : [];
|
|
720
|
-
if (earlier.some((
|
|
745
|
+
if (earlier.some((input) => input.label === label || input.params.tag !== WEB_BOT_AUTH_PROFILE))
|
|
721
746
|
throw new TypeError("Relay requires existing Web Bot Auth signatures and a fresh label");
|
|
722
747
|
const priorAgent = options.relay ? (0, import_canonicalize2.getHeaderValue)(request.headers, "signature-agent") : void 0;
|
|
723
748
|
if (priorAgent) dictionary(priorAgent, "Signature-Agent");
|
|
724
749
|
headers["signature-agent"] = `${priorAgent ? priorAgent + ", " : ""}${label}="${origin}"`;
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
750
|
+
if (priorAgent) dictionary(headers["signature-agent"], "Signature-Agent");
|
|
751
|
+
components.push((0, import_http_message_sig.component)("signature-agent", { key: label }));
|
|
752
|
+
const covered = new Set(components.map(import_http_message_sig.componentIdentity));
|
|
753
|
+
const cover = (descriptor) => {
|
|
754
|
+
const identity = (0, import_http_message_sig.componentIdentity)(descriptor);
|
|
755
|
+
if (!covered.has(identity)) {
|
|
756
|
+
covered.add(identity);
|
|
757
|
+
components.push(descriptor);
|
|
731
758
|
}
|
|
732
|
-
|
|
759
|
+
};
|
|
760
|
+
for (const inner of earlier) {
|
|
761
|
+
for (const identifier of inner.components) cover(componentDescriptor(identifier));
|
|
762
|
+
cover((0, import_http_message_sig.component)("signature-input", { key: inner.label }));
|
|
763
|
+
cover((0, import_http_message_sig.component)("signature", { key: inner.label }));
|
|
733
764
|
}
|
|
734
765
|
const priorInput = options.relay ? (0, import_canonicalize2.getHeaderValue)(request.headers, "signature-input") : void 0;
|
|
735
766
|
const priorSignature = options.relay ? (0, import_canonicalize2.getHeaderValue)(request.headers, "signature") : void 0;
|
|
736
767
|
if (options.relay && (!priorInput || !priorSignature || !priorAgent))
|
|
737
768
|
throw new TypeError("Relay requires the existing signature fields");
|
|
738
|
-
headers["signature-input"] =
|
|
739
|
-
const input = (0, import_parser.parseSignatureInput)(headers["signature-input"], label);
|
|
769
|
+
if (priorInput) headers["signature-input"] = priorInput;
|
|
740
770
|
if (priorSignature) headers.signature = priorSignature;
|
|
741
|
-
const
|
|
742
|
-
buildWebBotAuthSignatureBase(
|
|
743
|
-
|
|
771
|
+
for (const inner of earlier)
|
|
772
|
+
buildWebBotAuthSignatureBase(inner, { ...request, url: new URL(request.url).href, headers });
|
|
773
|
+
const targetUri = new URL(request.url);
|
|
774
|
+
targetUri.hash = "";
|
|
775
|
+
const signed = await createWebBotSignature(
|
|
776
|
+
{
|
|
777
|
+
kind: "request",
|
|
778
|
+
method: request.method.toUpperCase(),
|
|
779
|
+
targetUri: targetUri.href,
|
|
780
|
+
fields: coveredFields(components, headers)
|
|
781
|
+
},
|
|
782
|
+
{
|
|
783
|
+
label,
|
|
784
|
+
components,
|
|
785
|
+
parameters: {
|
|
786
|
+
created: now,
|
|
787
|
+
expires: now + window,
|
|
788
|
+
nonce,
|
|
789
|
+
keyid: jwkThumbprint(jwk),
|
|
790
|
+
alg: algorithm(jwk),
|
|
791
|
+
tag: WEB_BOT_AUTH_PROFILE
|
|
792
|
+
},
|
|
793
|
+
signer: webBotSigner(key)
|
|
794
|
+
}
|
|
744
795
|
);
|
|
745
|
-
headers
|
|
796
|
+
headers["signature-input"] = `${priorInput ? priorInput + ", " : ""}${signed.signatureInput}`;
|
|
797
|
+
headers.signature = `${priorSignature ? priorSignature + ", " : ""}${signed.signature}`;
|
|
746
798
|
return headers;
|
|
747
799
|
}
|
|
748
800
|
async function createSignedSignatureDirectory(originUrl, signingKeys, options = {}) {
|
|
@@ -777,15 +829,29 @@ async function createSignedSignatureDirectory(originUrl, signingKeys, options =
|
|
|
777
829
|
};
|
|
778
830
|
const signatureInputs = [], signatures = [];
|
|
779
831
|
for (let i = 0; i < keys.length; i++) {
|
|
780
|
-
const
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
832
|
+
const signed = await createWebBotSignature(
|
|
833
|
+
{
|
|
834
|
+
kind: "response",
|
|
835
|
+
status: response.status,
|
|
836
|
+
fields: coveredFields([(0, import_http_message_sig.component)("content-digest")], response.headers),
|
|
837
|
+
request: { kind: "request", method: "GET", targetUri: response.request.url, fields: [] }
|
|
838
|
+
},
|
|
839
|
+
{
|
|
840
|
+
label: `key${i + 1}`,
|
|
841
|
+
components: [(0, import_http_message_sig.component)("@authority", { req: true }), (0, import_http_message_sig.component)("content-digest")],
|
|
842
|
+
parameters: {
|
|
843
|
+
// Backdate created so verifiers whose clocks run slightly behind accept a fresh proof.
|
|
844
|
+
created: now - DIRECTORY_CLOCK_SKEW_SECONDS,
|
|
845
|
+
expires: now + lifetime,
|
|
846
|
+
keyid: keys[i].kid,
|
|
847
|
+
alg: algorithm(keys[i]),
|
|
848
|
+
tag: DIRECTORY_TAG
|
|
849
|
+
},
|
|
850
|
+
signer: webBotSigner(signingKeys[i])
|
|
851
|
+
}
|
|
786
852
|
);
|
|
787
|
-
signatureInputs.push(
|
|
788
|
-
signatures.push(
|
|
853
|
+
signatureInputs.push(signed.signatureInput);
|
|
854
|
+
signatures.push(signed.signature);
|
|
789
855
|
}
|
|
790
856
|
response.headers["signature-input"] = signatureInputs.join(", ");
|
|
791
857
|
response.headers.signature = signatures.join(", ");
|
|
@@ -7,6 +7,12 @@ import {
|
|
|
7
7
|
sign as nodeSign
|
|
8
8
|
} from "crypto";
|
|
9
9
|
import { parseDictionary, parseItem, serializeItem } from "structured-headers";
|
|
10
|
+
import {
|
|
11
|
+
component,
|
|
12
|
+
componentIdentity,
|
|
13
|
+
createSignature,
|
|
14
|
+
isSignatureError
|
|
15
|
+
} from "http-message-sig";
|
|
10
16
|
import { ssrfSafeFetch } from "../net/index.mjs";
|
|
11
17
|
import { eTldPlusOne } from "./agent-resolver/etld.mjs";
|
|
12
18
|
import { canonicalAgentOrigin } from "./agent-resolver/canonicalize.mjs";
|
|
@@ -136,11 +142,11 @@ function receivedAgentOrigin(value) {
|
|
|
136
142
|
}
|
|
137
143
|
}
|
|
138
144
|
function buildWebBotAuthSignatureBase(input, request, response) {
|
|
139
|
-
const lines = input.components.map((
|
|
140
|
-
const bare =
|
|
145
|
+
const lines = input.components.map((component2) => {
|
|
146
|
+
const bare = component2.split(";", 1)[0];
|
|
141
147
|
let identifier;
|
|
142
148
|
try {
|
|
143
|
-
identifier = parseItem(`"${bare}"${
|
|
149
|
+
identifier = parseItem(`"${bare}"${component2.slice(bare.length)}`);
|
|
144
150
|
} catch {
|
|
145
151
|
return bad("component");
|
|
146
152
|
}
|
|
@@ -358,8 +364,8 @@ async function verifyWebBotAuth(request, options) {
|
|
|
358
364
|
(c) => c.startsWith("signature-agent;key=") && !verified.some((s) => selected.find((i) => i.label === s.label).components.includes(c))
|
|
359
365
|
);
|
|
360
366
|
if (agentComponents.length !== 1) denied("agent_not_covered");
|
|
361
|
-
const
|
|
362
|
-
const memberKey =
|
|
367
|
+
const component2 = parseItem(`"${agentComponents[0].replace("signature-agent;", 'signature-agent";')}`);
|
|
368
|
+
const memberKey = component2[1].get("key");
|
|
363
369
|
if (typeof memberKey !== "string" || memberKey !== input.label) bad("agent_member");
|
|
364
370
|
const member = agents.get(memberKey);
|
|
365
371
|
if (!member) denied("agent_not_covered");
|
|
@@ -627,7 +633,7 @@ class SignatureDirectoryResolver {
|
|
|
627
633
|
return this.getVerifiedDirectory(origin)?.proof;
|
|
628
634
|
}
|
|
629
635
|
}
|
|
630
|
-
|
|
636
|
+
function webBotSigner(key) {
|
|
631
637
|
const jwk = "privateKey" in key ? key.privateKey : key.publicKey;
|
|
632
638
|
const alg = algorithm(jwk);
|
|
633
639
|
if (!["test", "development"].includes(process.env.NODE_ENV ?? "") && TEST_KIDS.has(jwkThumbprint(jwk)))
|
|
@@ -635,17 +641,40 @@ async function signBase(base, key) {
|
|
|
635
641
|
if ("provider" in key) {
|
|
636
642
|
if (key.provider.keyid !== jwkThumbprint(jwk) || key.provider.algorithm !== alg)
|
|
637
643
|
throw new TypeError("Web Bot Auth provider identity does not match its public key");
|
|
638
|
-
return key.provider.sign(Buffer.from(
|
|
644
|
+
return { algorithm: alg, sign: async (data) => Buffer.from(await key.provider.sign(Buffer.from(data))) };
|
|
639
645
|
}
|
|
640
646
|
if (alg === "rsa-pss-sha512") {
|
|
641
647
|
const privateKey = createPrivateKey({ key: jwk, format: "jwk" });
|
|
642
|
-
return
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
648
|
+
return {
|
|
649
|
+
algorithm: alg,
|
|
650
|
+
sign: (data) => nodeSign("sha512", Buffer.from(data), {
|
|
651
|
+
key: privateKey,
|
|
652
|
+
padding: cryptoConstants.RSA_PKCS1_PSS_PADDING,
|
|
653
|
+
saltLength: 64
|
|
654
|
+
})
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
return { algorithm: alg, sign: (data) => signWithPrivateJwkAsync(alg, jwk, Buffer.from(data)) };
|
|
658
|
+
}
|
|
659
|
+
async function createWebBotSignature(...args) {
|
|
660
|
+
try {
|
|
661
|
+
return await createSignature(...args);
|
|
662
|
+
} catch (error) {
|
|
663
|
+
if (isSignatureError(error)) throw new WebBotAuthError(400, "component_value");
|
|
664
|
+
throw error;
|
|
647
665
|
}
|
|
648
|
-
|
|
666
|
+
}
|
|
667
|
+
function componentDescriptor(identifier) {
|
|
668
|
+
const bare = identifier.split(";", 1)[0];
|
|
669
|
+
const [, params] = parseItem(`"${bare}"${identifier.slice(bare.length)}`);
|
|
670
|
+
return component(bare, Object.fromEntries(params));
|
|
671
|
+
}
|
|
672
|
+
function coveredFields(components, headers) {
|
|
673
|
+
const names = new Set(components.filter((c) => !c.name.startsWith("@")).map((c) => c.name));
|
|
674
|
+
return [...names].flatMap((name) => {
|
|
675
|
+
const value = getHeaderValue(headers, name);
|
|
676
|
+
return value === void 0 ? [] : [{ name, value }];
|
|
677
|
+
});
|
|
649
678
|
}
|
|
650
679
|
async function signWebBotAuth(request, key, options) {
|
|
651
680
|
const origin = canonicalAgentOrigin(options.origin);
|
|
@@ -655,6 +684,7 @@ async function signWebBotAuth(request, key, options) {
|
|
|
655
684
|
const now = Math.floor((options.now ?? (() => Date.now() / 1e3))());
|
|
656
685
|
const window = options.windowSeconds ?? 300;
|
|
657
686
|
const nonce = options.nonce ?? randomBytes(64).toString("base64url");
|
|
687
|
+
if (!Number.isSafeInteger(now)) throw new TypeError("Invalid Web Bot Auth clock");
|
|
658
688
|
if (!Number.isInteger(window) || window < 1 || window > 300 || !/^[A-Za-z0-9_+\/-]{22,256}={0,2}$/.test(nonce))
|
|
659
689
|
throw new TypeError("Invalid Web Bot Auth lifetime or nonce");
|
|
660
690
|
if (!options.relay && ["signature", "signature-input", "signature-agent"].some(
|
|
@@ -667,42 +697,69 @@ async function signWebBotAuth(request, key, options) {
|
|
|
667
697
|
for (const [name, value] of Object.entries(request.headers))
|
|
668
698
|
if (value !== void 0 && !["signature", "signature-input", "signature-agent", "content-digest"].includes(name.toLowerCase()))
|
|
669
699
|
headers[name.toLowerCase()] = Array.isArray(value) ? value.join(", ") : value;
|
|
670
|
-
const components = [
|
|
700
|
+
const components = [component("@method"), component("@target-uri"), component("@authority")];
|
|
671
701
|
if (request.body !== void 0 && request.body !== "") {
|
|
672
702
|
if (!getHeaderValue(headers, "content-type"))
|
|
673
703
|
throw new TypeError("Body-bearing Web Bot Auth requests need Content-Type");
|
|
674
704
|
headers["content-digest"] = computeContentDigest(request.body, "rfc8941-base64");
|
|
675
|
-
components.push(
|
|
705
|
+
components.push(component("content-type"), component("content-digest"));
|
|
676
706
|
}
|
|
677
707
|
if (options.relay && ["signature", "signature-input", "signature-agent"].some((name) => !getHeaderValue(request.headers, name)))
|
|
678
708
|
throw new TypeError("Relay requires the existing signature fields");
|
|
679
709
|
const earlier = options.relay ? inputs(request.headers) : [];
|
|
680
|
-
if (earlier.some((
|
|
710
|
+
if (earlier.some((input) => input.label === label || input.params.tag !== WEB_BOT_AUTH_PROFILE))
|
|
681
711
|
throw new TypeError("Relay requires existing Web Bot Auth signatures and a fresh label");
|
|
682
712
|
const priorAgent = options.relay ? getHeaderValue(request.headers, "signature-agent") : void 0;
|
|
683
713
|
if (priorAgent) dictionary(priorAgent, "Signature-Agent");
|
|
684
714
|
headers["signature-agent"] = `${priorAgent ? priorAgent + ", " : ""}${label}="${origin}"`;
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
715
|
+
if (priorAgent) dictionary(headers["signature-agent"], "Signature-Agent");
|
|
716
|
+
components.push(component("signature-agent", { key: label }));
|
|
717
|
+
const covered = new Set(components.map(componentIdentity));
|
|
718
|
+
const cover = (descriptor) => {
|
|
719
|
+
const identity = componentIdentity(descriptor);
|
|
720
|
+
if (!covered.has(identity)) {
|
|
721
|
+
covered.add(identity);
|
|
722
|
+
components.push(descriptor);
|
|
691
723
|
}
|
|
692
|
-
|
|
724
|
+
};
|
|
725
|
+
for (const inner of earlier) {
|
|
726
|
+
for (const identifier of inner.components) cover(componentDescriptor(identifier));
|
|
727
|
+
cover(component("signature-input", { key: inner.label }));
|
|
728
|
+
cover(component("signature", { key: inner.label }));
|
|
693
729
|
}
|
|
694
730
|
const priorInput = options.relay ? getHeaderValue(request.headers, "signature-input") : void 0;
|
|
695
731
|
const priorSignature = options.relay ? getHeaderValue(request.headers, "signature") : void 0;
|
|
696
732
|
if (options.relay && (!priorInput || !priorSignature || !priorAgent))
|
|
697
733
|
throw new TypeError("Relay requires the existing signature fields");
|
|
698
|
-
headers["signature-input"] =
|
|
699
|
-
const input = parseSignatureInput(headers["signature-input"], label);
|
|
734
|
+
if (priorInput) headers["signature-input"] = priorInput;
|
|
700
735
|
if (priorSignature) headers.signature = priorSignature;
|
|
701
|
-
const
|
|
702
|
-
buildWebBotAuthSignatureBase(
|
|
703
|
-
|
|
736
|
+
for (const inner of earlier)
|
|
737
|
+
buildWebBotAuthSignatureBase(inner, { ...request, url: new URL(request.url).href, headers });
|
|
738
|
+
const targetUri = new URL(request.url);
|
|
739
|
+
targetUri.hash = "";
|
|
740
|
+
const signed = await createWebBotSignature(
|
|
741
|
+
{
|
|
742
|
+
kind: "request",
|
|
743
|
+
method: request.method.toUpperCase(),
|
|
744
|
+
targetUri: targetUri.href,
|
|
745
|
+
fields: coveredFields(components, headers)
|
|
746
|
+
},
|
|
747
|
+
{
|
|
748
|
+
label,
|
|
749
|
+
components,
|
|
750
|
+
parameters: {
|
|
751
|
+
created: now,
|
|
752
|
+
expires: now + window,
|
|
753
|
+
nonce,
|
|
754
|
+
keyid: jwkThumbprint(jwk),
|
|
755
|
+
alg: algorithm(jwk),
|
|
756
|
+
tag: WEB_BOT_AUTH_PROFILE
|
|
757
|
+
},
|
|
758
|
+
signer: webBotSigner(key)
|
|
759
|
+
}
|
|
704
760
|
);
|
|
705
|
-
headers
|
|
761
|
+
headers["signature-input"] = `${priorInput ? priorInput + ", " : ""}${signed.signatureInput}`;
|
|
762
|
+
headers.signature = `${priorSignature ? priorSignature + ", " : ""}${signed.signature}`;
|
|
706
763
|
return headers;
|
|
707
764
|
}
|
|
708
765
|
async function createSignedSignatureDirectory(originUrl, signingKeys, options = {}) {
|
|
@@ -737,15 +794,29 @@ async function createSignedSignatureDirectory(originUrl, signingKeys, options =
|
|
|
737
794
|
};
|
|
738
795
|
const signatureInputs = [], signatures = [];
|
|
739
796
|
for (let i = 0; i < keys.length; i++) {
|
|
740
|
-
const
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
797
|
+
const signed = await createWebBotSignature(
|
|
798
|
+
{
|
|
799
|
+
kind: "response",
|
|
800
|
+
status: response.status,
|
|
801
|
+
fields: coveredFields([component("content-digest")], response.headers),
|
|
802
|
+
request: { kind: "request", method: "GET", targetUri: response.request.url, fields: [] }
|
|
803
|
+
},
|
|
804
|
+
{
|
|
805
|
+
label: `key${i + 1}`,
|
|
806
|
+
components: [component("@authority", { req: true }), component("content-digest")],
|
|
807
|
+
parameters: {
|
|
808
|
+
// Backdate created so verifiers whose clocks run slightly behind accept a fresh proof.
|
|
809
|
+
created: now - DIRECTORY_CLOCK_SKEW_SECONDS,
|
|
810
|
+
expires: now + lifetime,
|
|
811
|
+
keyid: keys[i].kid,
|
|
812
|
+
alg: algorithm(keys[i]),
|
|
813
|
+
tag: DIRECTORY_TAG
|
|
814
|
+
},
|
|
815
|
+
signer: webBotSigner(signingKeys[i])
|
|
816
|
+
}
|
|
746
817
|
);
|
|
747
|
-
signatureInputs.push(
|
|
748
|
-
signatures.push(
|
|
818
|
+
signatureInputs.push(signed.signatureInput);
|
|
819
|
+
signatures.push(signed.signature);
|
|
749
820
|
}
|
|
750
821
|
response.headers["signature-input"] = signatureInputs.join(", ");
|
|
751
822
|
response.headers.signature = signatures.join(", ");
|
|
@@ -82,8 +82,9 @@ function formatStoryboardResultsAsJUnit(results, options = {}) {
|
|
|
82
82
|
const suiteGroup = options.suite_groups?.[sb.storyboard_id];
|
|
83
83
|
const suiteClassname = suiteGroup ? `${suiteGroup}.${sb.storyboard_id}` : sb.storyboard_id;
|
|
84
84
|
const suiteCases = [];
|
|
85
|
-
let suiteFailures =
|
|
85
|
+
let suiteFailures = 0;
|
|
86
86
|
let representedSkipped = 0;
|
|
87
|
+
let suiteSkipped = 0;
|
|
87
88
|
const passPhases = sb.passes?.length ? sb.passes.map((pass) => ({ label: `Pass ${pass.pass_index} \u203A `, phases: pass.phases })) : [{ label: "", phases: sb.phases }];
|
|
88
89
|
for (const pass of passPhases) {
|
|
89
90
|
for (const phase of pass.phases) {
|
|
@@ -93,6 +94,7 @@ function formatStoryboardResultsAsJUnit(results, options = {}) {
|
|
|
93
94
|
const time = ((step.duration_ms || 0) / 1e3).toFixed(3);
|
|
94
95
|
if (step.skipped) {
|
|
95
96
|
representedSkipped += 1;
|
|
97
|
+
suiteSkipped += 1;
|
|
96
98
|
totalSkipped += 1;
|
|
97
99
|
suiteCases.push(
|
|
98
100
|
` <testcase classname="${xmlEscape(suiteClassname)}" name="${xmlEscape(name)}" time="${time}">
|
|
@@ -102,7 +104,6 @@ function formatStoryboardResultsAsJUnit(results, options = {}) {
|
|
|
102
104
|
continue;
|
|
103
105
|
}
|
|
104
106
|
if (!step.passed) {
|
|
105
|
-
totalFailures += 1;
|
|
106
107
|
const failureDetails = [
|
|
107
108
|
step.error,
|
|
108
109
|
...step.validations.filter((v) => !v.passed).map(
|
|
@@ -114,6 +115,17 @@ function formatStoryboardResultsAsJUnit(results, options = {}) {
|
|
|
114
115
|
// report body.
|
|
115
116
|
...hintLines(step.hints)
|
|
116
117
|
].filter(Boolean).join("\n");
|
|
118
|
+
if (step.counts_toward_failure === false) {
|
|
119
|
+
suiteCases.push(
|
|
120
|
+
` <testcase classname="${xmlEscape(suiteClassname)}" name="${xmlEscape(name)}" time="${time}">
|
|
121
|
+
<system-out>${xmlEscape(`Optional phase failure; excluded from storyboard verdict.
|
|
122
|
+
${failureDetails}`)}</system-out>
|
|
123
|
+
</testcase>`
|
|
124
|
+
);
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
totalFailures += 1;
|
|
128
|
+
suiteFailures += 1;
|
|
117
129
|
const message = step.error || firstHintMessage(step) || "validation failed";
|
|
118
130
|
suiteCases.push(
|
|
119
131
|
` <testcase classname="${xmlEscape(suiteClassname)}" name="${xmlEscape(name)}" time="${time}">
|
|
@@ -136,6 +148,7 @@ function formatStoryboardResultsAsJUnit(results, options = {}) {
|
|
|
136
148
|
if (hasStoryboardFixtureSkip) {
|
|
137
149
|
totalTests += 1;
|
|
138
150
|
totalSkipped += 1;
|
|
151
|
+
suiteSkipped += 1;
|
|
139
152
|
suiteCases.push(
|
|
140
153
|
` <testcase classname="${xmlEscape(suiteClassname)}" name="Fixture resolution" time="0.000">
|
|
141
154
|
<skipped message="${xmlEscape(fixtureGap.detail)}"/>
|
|
@@ -159,7 +172,7 @@ ${assertion.error}` : ""}`;
|
|
|
159
172
|
totalDuration += sb.total_duration_ms || 0;
|
|
160
173
|
const suiteTests = suiteCases.length;
|
|
161
174
|
suites.push(
|
|
162
|
-
` <testsuite name="${xmlEscape(suiteGroup ? `${suiteGroup} \u203A ${sb.storyboard_title}` : sb.storyboard_title)}"${suiteGroup ? ` package="${xmlEscape(`adcp.${suiteGroup}`)}"` : ""} tests="${suiteTests}" failures="${suiteFailures}" skipped="${
|
|
175
|
+
` <testsuite name="${xmlEscape(suiteGroup ? `${suiteGroup} \u203A ${sb.storyboard_title}` : sb.storyboard_title)}"${suiteGroup ? ` package="${xmlEscape(`adcp.${suiteGroup}`)}"` : ""} tests="${suiteTests}" failures="${suiteFailures}" skipped="${suiteSkipped}" time="${((sb.total_duration_ms || 0) / 1e3).toFixed(3)}" timestamp="${sb.tested_at || (/* @__PURE__ */ new Date()).toISOString()}">
|
|
163
176
|
` + suiteCases.join("\n") + `
|
|
164
177
|
</testsuite>`
|
|
165
178
|
);
|
|
@@ -58,8 +58,9 @@ function formatStoryboardResultsAsJUnit(results, options = {}) {
|
|
|
58
58
|
const suiteGroup = options.suite_groups?.[sb.storyboard_id];
|
|
59
59
|
const suiteClassname = suiteGroup ? `${suiteGroup}.${sb.storyboard_id}` : sb.storyboard_id;
|
|
60
60
|
const suiteCases = [];
|
|
61
|
-
let suiteFailures =
|
|
61
|
+
let suiteFailures = 0;
|
|
62
62
|
let representedSkipped = 0;
|
|
63
|
+
let suiteSkipped = 0;
|
|
63
64
|
const passPhases = sb.passes?.length ? sb.passes.map((pass) => ({ label: `Pass ${pass.pass_index} \u203A `, phases: pass.phases })) : [{ label: "", phases: sb.phases }];
|
|
64
65
|
for (const pass of passPhases) {
|
|
65
66
|
for (const phase of pass.phases) {
|
|
@@ -69,6 +70,7 @@ function formatStoryboardResultsAsJUnit(results, options = {}) {
|
|
|
69
70
|
const time = ((step.duration_ms || 0) / 1e3).toFixed(3);
|
|
70
71
|
if (step.skipped) {
|
|
71
72
|
representedSkipped += 1;
|
|
73
|
+
suiteSkipped += 1;
|
|
72
74
|
totalSkipped += 1;
|
|
73
75
|
suiteCases.push(
|
|
74
76
|
` <testcase classname="${xmlEscape(suiteClassname)}" name="${xmlEscape(name)}" time="${time}">
|
|
@@ -78,7 +80,6 @@ function formatStoryboardResultsAsJUnit(results, options = {}) {
|
|
|
78
80
|
continue;
|
|
79
81
|
}
|
|
80
82
|
if (!step.passed) {
|
|
81
|
-
totalFailures += 1;
|
|
82
83
|
const failureDetails = [
|
|
83
84
|
step.error,
|
|
84
85
|
...step.validations.filter((v) => !v.passed).map(
|
|
@@ -90,6 +91,17 @@ function formatStoryboardResultsAsJUnit(results, options = {}) {
|
|
|
90
91
|
// report body.
|
|
91
92
|
...hintLines(step.hints)
|
|
92
93
|
].filter(Boolean).join("\n");
|
|
94
|
+
if (step.counts_toward_failure === false) {
|
|
95
|
+
suiteCases.push(
|
|
96
|
+
` <testcase classname="${xmlEscape(suiteClassname)}" name="${xmlEscape(name)}" time="${time}">
|
|
97
|
+
<system-out>${xmlEscape(`Optional phase failure; excluded from storyboard verdict.
|
|
98
|
+
${failureDetails}`)}</system-out>
|
|
99
|
+
</testcase>`
|
|
100
|
+
);
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
totalFailures += 1;
|
|
104
|
+
suiteFailures += 1;
|
|
93
105
|
const message = step.error || firstHintMessage(step) || "validation failed";
|
|
94
106
|
suiteCases.push(
|
|
95
107
|
` <testcase classname="${xmlEscape(suiteClassname)}" name="${xmlEscape(name)}" time="${time}">
|
|
@@ -112,6 +124,7 @@ function formatStoryboardResultsAsJUnit(results, options = {}) {
|
|
|
112
124
|
if (hasStoryboardFixtureSkip) {
|
|
113
125
|
totalTests += 1;
|
|
114
126
|
totalSkipped += 1;
|
|
127
|
+
suiteSkipped += 1;
|
|
115
128
|
suiteCases.push(
|
|
116
129
|
` <testcase classname="${xmlEscape(suiteClassname)}" name="Fixture resolution" time="0.000">
|
|
117
130
|
<skipped message="${xmlEscape(fixtureGap.detail)}"/>
|
|
@@ -135,7 +148,7 @@ ${assertion.error}` : ""}`;
|
|
|
135
148
|
totalDuration += sb.total_duration_ms || 0;
|
|
136
149
|
const suiteTests = suiteCases.length;
|
|
137
150
|
suites.push(
|
|
138
|
-
` <testsuite name="${xmlEscape(suiteGroup ? `${suiteGroup} \u203A ${sb.storyboard_title}` : sb.storyboard_title)}"${suiteGroup ? ` package="${xmlEscape(`adcp.${suiteGroup}`)}"` : ""} tests="${suiteTests}" failures="${suiteFailures}" skipped="${
|
|
151
|
+
` <testsuite name="${xmlEscape(suiteGroup ? `${suiteGroup} \u203A ${sb.storyboard_title}` : sb.storyboard_title)}"${suiteGroup ? ` package="${xmlEscape(`adcp.${suiteGroup}`)}"` : ""} tests="${suiteTests}" failures="${suiteFailures}" skipped="${suiteSkipped}" time="${((sb.total_duration_ms || 0) / 1e3).toFixed(3)}" timestamp="${sb.tested_at || (/* @__PURE__ */ new Date()).toISOString()}">
|
|
139
152
|
` + suiteCases.join("\n") + `
|
|
140
153
|
</testsuite>`
|
|
141
154
|
);
|