@adcp/sdk 15.0.0 → 15.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/adcp-grade.js +46 -2
- package/bin/adcp.js +7 -2
- package/dist/lib/canonical-references/index.d.mts +19 -1
- package/dist/lib/canonical-references/index.d.ts +19 -1
- package/dist/lib/canonical-references/index.js +81 -9
- package/dist/lib/canonical-references/index.mjs +80 -9
- package/dist/lib/client/core.d.mts +1 -0
- package/dist/lib/client/core.d.ts +1 -0
- package/dist/lib/core/AgentClient.d.mts +9 -1
- package/dist/lib/core/AgentClient.d.ts +9 -1
- package/dist/lib/core/AgentClient.js +10 -0
- package/dist/lib/core/AgentClient.mjs +10 -0
- package/dist/lib/core/SingleAgentClient.d.mts +13 -2
- package/dist/lib/core/SingleAgentClient.d.ts +13 -2
- package/dist/lib/core/SingleAgentClient.js +23 -5
- package/dist/lib/core/SingleAgentClient.mjs +23 -5
- package/dist/lib/index.d.mts +1 -1
- package/dist/lib/index.d.ts +1 -1
- package/dist/lib/reporting/gcs/adapter.d.mts +31 -0
- package/dist/lib/reporting/gcs/adapter.d.ts +31 -0
- package/dist/lib/reporting/gcs/adapter.js +342 -0
- package/dist/lib/reporting/gcs/adapter.mjs +325 -0
- package/dist/lib/reporting/gcs/bucket-policy.d.mts +3 -0
- package/dist/lib/reporting/gcs/bucket-policy.d.ts +3 -0
- package/dist/lib/reporting/gcs/bucket-policy.js +31 -0
- package/dist/lib/reporting/gcs/bucket-policy.mjs +7 -0
- package/dist/lib/reporting/gcs/errors.d.mts +5 -0
- package/dist/lib/reporting/gcs/errors.d.ts +5 -0
- package/dist/lib/reporting/gcs/errors.js +41 -0
- package/dist/lib/reporting/gcs/errors.mjs +16 -0
- package/dist/lib/reporting/gcs/fence.d.mts +36 -0
- package/dist/lib/reporting/gcs/fence.d.ts +36 -0
- package/dist/lib/reporting/gcs/fence.js +250 -0
- package/dist/lib/reporting/gcs/fence.mjs +230 -0
- package/dist/lib/reporting/gcs/index.d.mts +7 -0
- package/dist/lib/reporting/gcs/index.d.ts +7 -0
- package/dist/lib/reporting/gcs/index.js +41 -0
- package/dist/lib/reporting/gcs/index.mjs +12 -0
- package/dist/lib/reporting/gcs/io.d.mts +7 -0
- package/dist/lib/reporting/gcs/io.d.ts +7 -0
- package/dist/lib/reporting/gcs/io.js +95 -0
- package/dist/lib/reporting/gcs/io.mjs +69 -0
- package/dist/lib/reporting/gcs/reader.d.mts +43 -0
- package/dist/lib/reporting/gcs/reader.d.ts +43 -0
- package/dist/lib/reporting/gcs/reader.js +193 -0
- package/dist/lib/reporting/gcs/reader.mjs +169 -0
- package/dist/lib/reporting/gcs/validation.d.mts +5 -0
- package/dist/lib/reporting/gcs/validation.d.ts +5 -0
- package/dist/lib/reporting/gcs/validation.js +49 -0
- package/dist/lib/reporting/gcs/validation.mjs +25 -0
- package/dist/lib/reporting/ledger/handler.d.mts +1 -0
- package/dist/lib/reporting/ledger/handler.d.ts +1 -0
- package/dist/lib/reporting/ledger/index.d.mts +1 -0
- package/dist/lib/reporting/ledger/index.d.ts +1 -0
- package/dist/lib/reporting/ledger/index.js +2 -0
- package/dist/lib/reporting/ledger/index.mjs +1 -0
- package/dist/lib/reporting/ledger/managed-postgres.d.mts +16 -1
- package/dist/lib/reporting/ledger/managed-postgres.d.ts +16 -1
- package/dist/lib/reporting/ledger/managed-postgres.js +243 -2
- package/dist/lib/reporting/ledger/managed-postgres.mjs +248 -2
- package/dist/lib/reporting/ledger/object-writes.d.mts +69 -0
- package/dist/lib/reporting/ledger/object-writes.d.ts +69 -0
- package/dist/lib/reporting/ledger/object-writes.js +147 -0
- package/dist/lib/reporting/ledger/object-writes.mjs +114 -0
- package/dist/lib/schemas-data/v2.5/_provenance.json +1 -1
- package/dist/lib/server/a2a-adapter.d.mts +50 -1
- package/dist/lib/server/a2a-adapter.d.ts +50 -1
- package/dist/lib/server/a2a-adapter.js +230 -7
- package/dist/lib/server/a2a-adapter.mjs +237 -7
- package/dist/lib/server/auth-signature.d.mts +84 -46
- package/dist/lib/server/auth-signature.d.ts +84 -46
- package/dist/lib/server/auth-signature.js +48 -14
- package/dist/lib/server/auth-signature.mjs +58 -15
- package/dist/lib/server/create-adcp-server.js +5 -16
- package/dist/lib/server/create-adcp-server.mjs +5 -16
- package/dist/lib/server/index.d.mts +1 -1
- package/dist/lib/server/index.d.ts +1 -1
- package/dist/lib/server/index.js +2 -0
- package/dist/lib/server/index.mjs +3 -1
- package/dist/lib/signing/agent-fetch.js +3 -33
- package/dist/lib/signing/agent-fetch.mjs +3 -33
- package/dist/lib/signing/middleware.d.mts +9 -1
- package/dist/lib/signing/middleware.d.ts +9 -1
- package/dist/lib/signing/operation-resolution.d.mts +37 -0
- package/dist/lib/signing/operation-resolution.d.ts +37 -0
- package/dist/lib/signing/operation-resolution.js +154 -0
- package/dist/lib/signing/operation-resolution.mjs +125 -0
- package/dist/lib/signing/postgres-replay-store.d.mts +1 -1
- package/dist/lib/signing/postgres-replay-store.d.ts +1 -1
- package/dist/lib/signing/redis-replay-store.d.mts +1 -1
- package/dist/lib/signing/redis-replay-store.d.ts +1 -1
- package/dist/lib/signing/server.d.mts +1 -0
- package/dist/lib/signing/server.d.ts +1 -0
- package/dist/lib/signing/server.js +7 -0
- package/dist/lib/signing/server.mjs +8 -0
- package/dist/lib/signing/verifier.d.mts +36 -1
- package/dist/lib/signing/verifier.d.ts +36 -1
- package/dist/lib/signing/verifier.js +66 -34
- package/dist/lib/signing/verifier.mjs +61 -34
- package/dist/lib/testing/storyboard/request-signing/a2a-dispatch.d.mts +12 -0
- package/dist/lib/testing/storyboard/request-signing/a2a-dispatch.d.ts +12 -0
- package/dist/lib/testing/storyboard/request-signing/a2a-dispatch.js +29 -2
- package/dist/lib/testing/storyboard/request-signing/a2a-dispatch.mjs +27 -1
- package/dist/lib/testing/storyboard/request-signing/a2a-operation-resolution.d.mts +61 -0
- package/dist/lib/testing/storyboard/request-signing/a2a-operation-resolution.d.ts +61 -0
- package/dist/lib/testing/storyboard/request-signing/a2a-operation-resolution.js +298 -0
- package/dist/lib/testing/storyboard/request-signing/a2a-operation-resolution.mjs +276 -0
- package/dist/lib/testing/storyboard/request-signing/grader.d.mts +31 -1
- package/dist/lib/testing/storyboard/request-signing/grader.d.ts +31 -1
- package/dist/lib/testing/storyboard/request-signing/grader.js +11 -0
- package/dist/lib/testing/storyboard/request-signing/grader.mjs +11 -0
- package/dist/lib/testing/storyboard/request-signing/index.d.mts +5 -4
- package/dist/lib/testing/storyboard/request-signing/index.d.ts +5 -4
- package/dist/lib/testing/storyboard/request-signing/index.js +13 -0
- package/dist/lib/testing/storyboard/request-signing/index.mjs +18 -2
- package/dist/lib/testing/storyboard/request-signing/types.d.mts +43 -0
- package/dist/lib/testing/storyboard/request-signing/types.d.ts +43 -0
- package/dist/lib/testing/storyboard/request-signing/types.js +3 -0
- package/dist/lib/testing/storyboard/request-signing/types.mjs +2 -0
- package/dist/lib/testing/storyboard/request-signing/vector-loader.d.mts +42 -1
- package/dist/lib/testing/storyboard/request-signing/vector-loader.d.ts +42 -1
- package/dist/lib/testing/storyboard/request-signing/vector-loader.js +100 -1
- package/dist/lib/testing/storyboard/request-signing/vector-loader.mjs +104 -3
- 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 +2 -2
- package/docs/guides/BUILD-AN-AGENT.md +9 -4
- package/docs/guides/CREATIVE-DELIVERY.md +34 -0
- package/docs/guides/REPORTING-GCS-FENCE.md +52 -0
- package/docs/guides/REPORTING-GCS-MANAGED.md +56 -0
- package/docs/guides/REPORTING-OPERATIONS.md +4 -0
- package/docs/guides/SIGNING-GUIDE.md +20 -6
- package/docs/guides/VALIDATE-YOUR-AGENT.md +4 -0
- package/docs/llms.txt +2 -2
- package/docs/migration-14.x-rc-worksheet.md +5 -4
- package/docs/migration-buyer-agent-registry.md +1 -1
- package/package.json +19 -1
- package/skills/build-seller-agent/specialisms/signed-requests.md +2 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { parseStrictJson, StrictJsonError } from "./agent-resolver/strict-json.mjs";
|
|
2
|
+
const UNRESOLVABLE_OPERATION = /* @__PURE__ */ Symbol.for("@adcp/client.signing.unresolvableOperation");
|
|
3
|
+
function isUnresolvableOperation(value) {
|
|
4
|
+
return value === UNRESOLVABLE_OPERATION;
|
|
5
|
+
}
|
|
6
|
+
const A2A_MESSAGE_METHODS = /* @__PURE__ */ new Set([
|
|
7
|
+
"SendMessage",
|
|
8
|
+
"SendStreamingMessage",
|
|
9
|
+
"message/send",
|
|
10
|
+
"message/stream"
|
|
11
|
+
]);
|
|
12
|
+
const CONTENT_MEMBERS = ["text", "data", "raw", "url", "file"];
|
|
13
|
+
const FILE_MEMBERS = /* @__PURE__ */ new Set(["raw", "url", "file"]);
|
|
14
|
+
const HTTP_JSON_MESSAGE_PATH = /(?:^|\/)message:(?:send|stream)$/;
|
|
15
|
+
const ENVELOPE_MEMBERS = ["method", "params"];
|
|
16
|
+
const TOOLS_CALL_PARAMS_MEMBERS = ["name"];
|
|
17
|
+
const CARRIER_MEMBERS = ["message"];
|
|
18
|
+
const MESSAGE_MEMBERS = ["parts", "taskId"];
|
|
19
|
+
const PART_MEMBERS = [...CONTENT_MEMBERS, "kind"];
|
|
20
|
+
const DATA_MEMBERS = ["skill"];
|
|
21
|
+
function resolveRequestOperationDetailed(input) {
|
|
22
|
+
if (input.method !== void 0 && input.method.toUpperCase() !== "POST") return { kind: "none" };
|
|
23
|
+
const raw = typeof input.rawBody === "string" ? input.rawBody : input.rawBody?.toString("utf8");
|
|
24
|
+
if (raw === void 0 || raw.trim() === "") return unresolvable("request body is empty");
|
|
25
|
+
if (raw.charCodeAt(0) === 65279) return unresolvable("body starts with a byte order mark");
|
|
26
|
+
let body;
|
|
27
|
+
try {
|
|
28
|
+
body = parseStrictJson(raw);
|
|
29
|
+
} catch (err) {
|
|
30
|
+
return unresolvable(err instanceof StrictJsonError ? `body is not strict JSON (${err.code})` : "body is not JSON");
|
|
31
|
+
}
|
|
32
|
+
if (!isPlainObject(body)) {
|
|
33
|
+
return unresolvable(Array.isArray(body) ? "JSON-RPC batches are not supported" : "body is not a JSON object");
|
|
34
|
+
}
|
|
35
|
+
if (isHttpJsonMessagePath(input.url)) {
|
|
36
|
+
return resolveMessageCarrier(body);
|
|
37
|
+
}
|
|
38
|
+
if (hasCaseVariantMember(body, ENVELOPE_MEMBERS)) return unresolvable("case variant of an envelope member");
|
|
39
|
+
const method = body.method;
|
|
40
|
+
if (method === void 0 && isJsonRpcResponse(body)) {
|
|
41
|
+
return { kind: "none" };
|
|
42
|
+
}
|
|
43
|
+
if (typeof method !== "string") return unresolvable("JSON-RPC request has no string method");
|
|
44
|
+
if (method === "tools/call") {
|
|
45
|
+
const params = body.params;
|
|
46
|
+
if (!isPlainObject(params)) return unresolvable("tools/call params is not an object");
|
|
47
|
+
if (hasCaseVariantMember(params, TOOLS_CALL_PARAMS_MEMBERS)) return unresolvable("case variant of a params member");
|
|
48
|
+
const name = params.name;
|
|
49
|
+
if (typeof name !== "string" || name.length === 0) return unresolvable("tools/call params.name is not a string");
|
|
50
|
+
return { kind: "operation", operation: name };
|
|
51
|
+
}
|
|
52
|
+
if (A2A_MESSAGE_METHODS.has(method)) {
|
|
53
|
+
const params = body.params;
|
|
54
|
+
if (!isPlainObject(params)) return unresolvable("message method params is not an object");
|
|
55
|
+
return resolveMessageCarrier(params);
|
|
56
|
+
}
|
|
57
|
+
return { kind: "none" };
|
|
58
|
+
}
|
|
59
|
+
function resolveRequestOperation(input) {
|
|
60
|
+
const resolution = resolveRequestOperationDetailed(input);
|
|
61
|
+
if (resolution.kind === "operation") return resolution.operation;
|
|
62
|
+
return resolution.kind === "none" ? void 0 : UNRESOLVABLE_OPERATION;
|
|
63
|
+
}
|
|
64
|
+
function describeUnresolvable(input) {
|
|
65
|
+
const resolution = resolveRequestOperationDetailed(input);
|
|
66
|
+
return resolution.kind === "unresolvable" ? resolution.reason : void 0;
|
|
67
|
+
}
|
|
68
|
+
function resolveMessageCarrier(container) {
|
|
69
|
+
if (hasCaseVariantMember(container, CARRIER_MEMBERS)) return unresolvable("case variant of the message member");
|
|
70
|
+
const message = container.message;
|
|
71
|
+
if (!isPlainObject(message)) return unresolvable("request carries no message object");
|
|
72
|
+
if (hasCaseVariantMember(message, MESSAGE_MEMBERS)) return unresolvable("case variant of a message member");
|
|
73
|
+
const parts = message.parts;
|
|
74
|
+
if (!Array.isArray(parts)) return unresolvable("message.parts is not an array");
|
|
75
|
+
let invocation;
|
|
76
|
+
for (const part of parts) {
|
|
77
|
+
if (!isPlainObject(part)) return unresolvable("a Part is not an object");
|
|
78
|
+
if (hasCaseVariantMember(part, PART_MEMBERS)) return unresolvable("case variant of a Part member");
|
|
79
|
+
const present = CONTENT_MEMBERS.filter((member2) => Object.prototype.hasOwnProperty.call(part, member2));
|
|
80
|
+
if (present.length !== 1) return unresolvable("a Part must carry exactly one content member");
|
|
81
|
+
const member = present[0];
|
|
82
|
+
if (FILE_MEMBERS.has(member)) return unresolvable("FileParts are not allowed on an AdCP invocation");
|
|
83
|
+
const kind = part.kind;
|
|
84
|
+
if (kind !== void 0 && kind !== member) return unresolvable("a Part kind disagrees with its content member");
|
|
85
|
+
if (member === "data") {
|
|
86
|
+
if (invocation !== void 0) return unresolvable("message carries more than one DataPart");
|
|
87
|
+
if (!isPlainObject(part.data)) return unresolvable("DataPart data is not an object");
|
|
88
|
+
invocation = part.data;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (invocation === void 0) return unresolvable("message carries no DataPart");
|
|
92
|
+
if (hasCaseVariantMember(invocation, DATA_MEMBERS)) return unresolvable("case variant of skill");
|
|
93
|
+
const skill = invocation.skill;
|
|
94
|
+
if (typeof skill !== "string" || skill.length === 0) return unresolvable("DataPart skill is not a non-empty string");
|
|
95
|
+
return { kind: "operation", operation: skill };
|
|
96
|
+
}
|
|
97
|
+
function isJsonRpcResponse(body) {
|
|
98
|
+
const has = (key) => Object.prototype.hasOwnProperty.call(body, key);
|
|
99
|
+
return (has("result") || has("error")) && !has("params");
|
|
100
|
+
}
|
|
101
|
+
function unresolvable(reason) {
|
|
102
|
+
return { kind: "unresolvable", reason };
|
|
103
|
+
}
|
|
104
|
+
function isHttpJsonMessagePath(url) {
|
|
105
|
+
if (!url) return false;
|
|
106
|
+
const queryStart = url.search(/[?#]/);
|
|
107
|
+
return HTTP_JSON_MESSAGE_PATH.test(queryStart === -1 ? url : url.slice(0, queryStart));
|
|
108
|
+
}
|
|
109
|
+
function isPlainObject(value) {
|
|
110
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
111
|
+
}
|
|
112
|
+
function hasCaseVariantMember(obj, recognized) {
|
|
113
|
+
return Object.keys(obj).some((key) => {
|
|
114
|
+
const lowered = key.toLowerCase();
|
|
115
|
+
return recognized.some((name) => name.toLowerCase() === lowered && name !== key);
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
export {
|
|
119
|
+
A2A_MESSAGE_METHODS,
|
|
120
|
+
UNRESOLVABLE_OPERATION,
|
|
121
|
+
describeUnresolvable,
|
|
122
|
+
isUnresolvableOperation,
|
|
123
|
+
resolveRequestOperation,
|
|
124
|
+
resolveRequestOperationDetailed
|
|
125
|
+
};
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
* capability: { ... },
|
|
25
25
|
* jwks,
|
|
26
26
|
* replayStore, // <-- shared across instances
|
|
27
|
-
* resolveOperation:
|
|
27
|
+
* resolveOperation: adcpOperationResolver,
|
|
28
28
|
* }));
|
|
29
29
|
*
|
|
30
30
|
* // Schedule the sweeper somewhere (cron, app timer, pg_cron, etc.):
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
* capability: { ... },
|
|
25
25
|
* jwks,
|
|
26
26
|
* replayStore, // <-- shared across instances
|
|
27
|
-
* resolveOperation:
|
|
27
|
+
* resolveOperation: adcpOperationResolver,
|
|
28
28
|
* }));
|
|
29
29
|
*
|
|
30
30
|
* // Schedule the sweeper somewhere (cron, app timer, pg_cron, etc.):
|
|
@@ -25,6 +25,7 @@ export { InMemoryRevocationStore, type RevocationStore } from './revocation.mjs'
|
|
|
25
25
|
export { HttpsRevocationStore, type HttpsRevocationStoreOptions } from './revocation-https.mjs';
|
|
26
26
|
export { ALLOWED_ALGS, CLOCK_SKEW_TOLERANCE_SECONDS, MANDATORY_COMPONENTS, MAX_SIGNATURE_WINDOW_SECONDS, REQUEST_SIGNING_TAG, RESPONSE_MANDATORY_COMPONENTS, RESPONSE_SIGNING_TAG, type AdcpJsonWebKey, type ContentDigestPolicy, type RevocationSnapshot, type VerifiedSigner, type VerifierCapability, type VerifyResult, } from './types.mjs';
|
|
27
27
|
export { verifyRequestSignature, type VerifyRequestOptions } from './verifier.mjs';
|
|
28
|
+
export { isUnresolvableOperation, resolveRequestOperation, UNRESOLVABLE_OPERATION, type OperationResolutionInput, type ResolvedOperation, type UnresolvableOperation, } from './operation-resolution.mjs';
|
|
28
29
|
export { createWebhookVerifier, isWebhookLoopbackHost, verifyWebhookSignature, WEBHOOK_MANDATORY_COMPONENTS, WEBHOOK_SIGNING_TAG, type CreateWebhookVerifierOptions, type VerifyWebhookOptions, type VerifyWebhookResult, type WebhookRequestLike, } from './webhook-verifier.mjs';
|
|
29
30
|
export { createExpressVerifier, type ExpressLike, type ExpressMiddlewareOptions } from './middleware.mjs';
|
|
30
31
|
export { finalizeResponseSignature, prepareResponseSignature, signResponse, type PreparedResponseSignature, type SignedResponse, type SignResponseOptions, } from './signer.mjs';
|
|
@@ -25,6 +25,7 @@ export { InMemoryRevocationStore, type RevocationStore } from './revocation';
|
|
|
25
25
|
export { HttpsRevocationStore, type HttpsRevocationStoreOptions } from './revocation-https';
|
|
26
26
|
export { ALLOWED_ALGS, CLOCK_SKEW_TOLERANCE_SECONDS, MANDATORY_COMPONENTS, MAX_SIGNATURE_WINDOW_SECONDS, REQUEST_SIGNING_TAG, RESPONSE_MANDATORY_COMPONENTS, RESPONSE_SIGNING_TAG, type AdcpJsonWebKey, type ContentDigestPolicy, type RevocationSnapshot, type VerifiedSigner, type VerifierCapability, type VerifyResult, } from './types';
|
|
27
27
|
export { verifyRequestSignature, type VerifyRequestOptions } from './verifier';
|
|
28
|
+
export { isUnresolvableOperation, resolveRequestOperation, UNRESOLVABLE_OPERATION, type OperationResolutionInput, type ResolvedOperation, type UnresolvableOperation, } from './operation-resolution';
|
|
28
29
|
export { createWebhookVerifier, isWebhookLoopbackHost, verifyWebhookSignature, WEBHOOK_MANDATORY_COMPONENTS, WEBHOOK_SIGNING_TAG, type CreateWebhookVerifierOptions, type VerifyWebhookOptions, type VerifyWebhookResult, type WebhookRequestLike, } from './webhook-verifier';
|
|
29
30
|
export { createExpressVerifier, type ExpressLike, type ExpressMiddlewareOptions } from './middleware';
|
|
30
31
|
export { finalizeResponseSignature, prepareResponseSignature, signResponse, type PreparedResponseSignature, type SignedResponse, type SignResponseOptions, } from './signer';
|
|
@@ -41,6 +41,7 @@ __export(server_exports, {
|
|
|
41
41
|
ResolvedAgentJwksResolver: () => import_agent_resolver.ResolvedAgentJwksResolver,
|
|
42
42
|
ResponseSignatureError: () => import_errors.ResponseSignatureError,
|
|
43
43
|
StaticJwksResolver: () => import_jwks.StaticJwksResolver,
|
|
44
|
+
UNRESOLVABLE_OPERATION: () => import_operation_resolution.UNRESOLVABLE_OPERATION,
|
|
44
45
|
WEBHOOK_MANDATORY_COMPONENTS: () => import_webhook_verifier.WEBHOOK_MANDATORY_COMPONENTS,
|
|
45
46
|
WEBHOOK_SIGNING_TAG: () => import_webhook_verifier.WEBHOOK_SIGNING_TAG,
|
|
46
47
|
WebhookSignatureError: () => import_errors.WebhookSignatureError,
|
|
@@ -61,6 +62,7 @@ __export(server_exports, {
|
|
|
61
62
|
getAgentJwks: () => import_agent_resolver.getAgentJwks,
|
|
62
63
|
getHeaderValue: () => import_canonicalize.getHeaderValue,
|
|
63
64
|
getReplayStoreMigration: () => import_postgres_replay_store.getReplayStoreMigration,
|
|
65
|
+
isUnresolvableOperation: () => import_operation_resolution.isUnresolvableOperation,
|
|
64
66
|
isWebhookLoopbackHost: () => import_webhook_verifier.isWebhookLoopbackHost,
|
|
65
67
|
jwkToPublicKey: () => import_crypto.jwkToPublicKey,
|
|
66
68
|
parseContentDigest: () => import_content_digest.parseContentDigest,
|
|
@@ -73,6 +75,7 @@ __export(server_exports, {
|
|
|
73
75
|
requestContextFromFetch: () => import_request_context.requestContextFromFetch,
|
|
74
76
|
requestContextFromLambda: () => import_request_context.requestContextFromLambda,
|
|
75
77
|
resolveAgent: () => import_agent_resolver.resolveAgent,
|
|
78
|
+
resolveRequestOperation: () => import_operation_resolution.resolveRequestOperation,
|
|
76
79
|
signResponse: () => import_signer.signResponse,
|
|
77
80
|
signResponseAsync: () => import_signer_async.signResponseAsync,
|
|
78
81
|
sweepExpiredReplays: () => import_postgres_replay_store.sweepExpiredReplays,
|
|
@@ -97,6 +100,7 @@ var import_revocation = require('./revocation.js');
|
|
|
97
100
|
var import_revocation_https = require('./revocation-https.js');
|
|
98
101
|
var import_types = require('./types.js');
|
|
99
102
|
var import_verifier = require('./verifier.js');
|
|
103
|
+
var import_operation_resolution = require('./operation-resolution.js');
|
|
100
104
|
var import_webhook_verifier = require('./webhook-verifier.js');
|
|
101
105
|
var import_middleware = require('./middleware.js');
|
|
102
106
|
var import_signer = require('./signer.js');
|
|
@@ -127,6 +131,7 @@ var import_agent_resolver = require('./agent-resolver/index.js');
|
|
|
127
131
|
ResolvedAgentJwksResolver,
|
|
128
132
|
ResponseSignatureError,
|
|
129
133
|
StaticJwksResolver,
|
|
134
|
+
UNRESOLVABLE_OPERATION,
|
|
130
135
|
WEBHOOK_MANDATORY_COMPONENTS,
|
|
131
136
|
WEBHOOK_SIGNING_TAG,
|
|
132
137
|
WebhookSignatureError,
|
|
@@ -147,6 +152,7 @@ var import_agent_resolver = require('./agent-resolver/index.js');
|
|
|
147
152
|
getAgentJwks,
|
|
148
153
|
getHeaderValue,
|
|
149
154
|
getReplayStoreMigration,
|
|
155
|
+
isUnresolvableOperation,
|
|
150
156
|
isWebhookLoopbackHost,
|
|
151
157
|
jwkToPublicKey,
|
|
152
158
|
parseContentDigest,
|
|
@@ -159,6 +165,7 @@ var import_agent_resolver = require('./agent-resolver/index.js');
|
|
|
159
165
|
requestContextFromFetch,
|
|
160
166
|
requestContextFromLambda,
|
|
161
167
|
resolveAgent,
|
|
168
|
+
resolveRequestOperation,
|
|
162
169
|
signResponse,
|
|
163
170
|
signResponseAsync,
|
|
164
171
|
sweepExpiredReplays,
|
|
@@ -52,6 +52,11 @@ import {
|
|
|
52
52
|
RESPONSE_SIGNING_TAG
|
|
53
53
|
} from "./types.mjs";
|
|
54
54
|
import { verifyRequestSignature } from "./verifier.mjs";
|
|
55
|
+
import {
|
|
56
|
+
isUnresolvableOperation,
|
|
57
|
+
resolveRequestOperation,
|
|
58
|
+
UNRESOLVABLE_OPERATION
|
|
59
|
+
} from "./operation-resolution.mjs";
|
|
55
60
|
import {
|
|
56
61
|
createWebhookVerifier,
|
|
57
62
|
isWebhookLoopbackHost,
|
|
@@ -101,6 +106,7 @@ export {
|
|
|
101
106
|
ResolvedAgentJwksResolver,
|
|
102
107
|
ResponseSignatureError,
|
|
103
108
|
StaticJwksResolver,
|
|
109
|
+
UNRESOLVABLE_OPERATION,
|
|
104
110
|
WEBHOOK_MANDATORY_COMPONENTS,
|
|
105
111
|
WEBHOOK_SIGNING_TAG,
|
|
106
112
|
WebhookSignatureError,
|
|
@@ -121,6 +127,7 @@ export {
|
|
|
121
127
|
getAgentJwks,
|
|
122
128
|
getHeaderValue,
|
|
123
129
|
getReplayStoreMigration,
|
|
130
|
+
isUnresolvableOperation,
|
|
124
131
|
isWebhookLoopbackHost,
|
|
125
132
|
jwkToPublicKey,
|
|
126
133
|
parseContentDigest,
|
|
@@ -133,6 +140,7 @@ export {
|
|
|
133
140
|
requestContextFromFetch,
|
|
134
141
|
requestContextFromLambda,
|
|
135
142
|
resolveAgent,
|
|
143
|
+
resolveRequestOperation,
|
|
136
144
|
signResponse,
|
|
137
145
|
signResponseAsync,
|
|
138
146
|
sweepExpiredReplays,
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { type RequestLike } from './canonicalize.mjs';
|
|
2
|
+
import { RequestSignatureError } from './errors.mjs';
|
|
3
|
+
import { type UnresolvableOperation } from './operation-resolution.mjs';
|
|
2
4
|
import type { JwksResolver } from './jwks.mjs';
|
|
3
5
|
import type { ReplayStore } from './replay.mjs';
|
|
4
6
|
import type { RevocationStore } from './revocation.mjs';
|
|
@@ -15,8 +17,13 @@ export interface VerifyRequestOptions {
|
|
|
15
17
|
* the verifier treats the operation as "not in any required_for list" and
|
|
16
18
|
* returns an unsigned result rather than rejecting — callers in
|
|
17
19
|
* always-verify mode (where every request is signed) can leave this blank.
|
|
20
|
+
*
|
|
21
|
+
* Pass {@link UNRESOLVABLE_OPERATION} when the request body does not resolve
|
|
22
|
+
* to exactly one operation (see `resolveRequestOperation`). The verifier
|
|
23
|
+
* then rejects with `request_body_malformed` before it looks at any
|
|
24
|
+
* signature header or capability list, whether or not the request is signed.
|
|
18
25
|
*/
|
|
19
|
-
operation?: string;
|
|
26
|
+
operation?: string | UnresolvableOperation;
|
|
20
27
|
/**
|
|
21
28
|
* Trusted endpoint release pin; never inferred from request payload data.
|
|
22
29
|
* A 3.2+ pin parses `Signature` and `Content-Digest` strictly as RFC 8941
|
|
@@ -31,3 +38,31 @@ export interface VerifyRequestOptions {
|
|
|
31
38
|
agentUrlForKeyid?: (keyid: string) => string | undefined;
|
|
32
39
|
}
|
|
33
40
|
export declare function verifyRequestSignature(request: RequestLike, options: VerifyRequestOptions): Promise<VerifyResult>;
|
|
41
|
+
/** The `request_body_malformed` rejection for a body that resolves to no single operation. */
|
|
42
|
+
export declare function unresolvableOperationError(): RequestSignatureError;
|
|
43
|
+
/**
|
|
44
|
+
* Throw `request_signature_required` when an UNSIGNED request must be signed:
|
|
45
|
+
* its resolved operation is in `capability.required_for`, or its JSON-RPC
|
|
46
|
+
* method is in `capability.protocol_methods_required_for`, or it carries
|
|
47
|
+
* webhook authentication. Returns normally when none applies.
|
|
48
|
+
*
|
|
49
|
+
* "Unsigned" is the caller's contract: no `Signature` / `Signature-Input`
|
|
50
|
+
* header. The checks do not consult any fallback credential; a caller that
|
|
51
|
+
* lets a configured fallback authenticator satisfy `required_for` (the
|
|
52
|
+
* presence-gated composer) runs the pieces separately.
|
|
53
|
+
*/
|
|
54
|
+
export declare function assertUnsignedRequestAllowed(body: string | undefined, operation: string | undefined, capability: Pick<VerifierCapability, 'required_for' | 'protocol_methods_required_for'>): void;
|
|
55
|
+
/** `required_for` half of {@link assertUnsignedRequestAllowed}. */
|
|
56
|
+
export declare function assertUnsignedOperationNotRequired(operation: string | undefined, capability: Pick<VerifierCapability, 'required_for'>): void;
|
|
57
|
+
/** `protocol_methods_required_for` half of {@link assertUnsignedRequestAllowed}. */
|
|
58
|
+
export declare function assertUnsignedProtocolMethodNotRequired(body: string | undefined, capability: Pick<VerifierCapability, 'protocol_methods_required_for'>): void;
|
|
59
|
+
/**
|
|
60
|
+
* Payload-driven elevation: any request carrying webhook receiver credentials
|
|
61
|
+
* (task, reporting, artifact, or revocation callbacks) MUST be RFC 9421
|
|
62
|
+
* signed, regardless of whether the operation appears in `required_for`
|
|
63
|
+
* (#webhook-security downgrade-resistance). A bearer-only channel would
|
|
64
|
+
* otherwise let an attacker who captured a token register or update webhook
|
|
65
|
+
* credentials and redirect callbacks to a hostile URL. Unlike `required_for`,
|
|
66
|
+
* a valid fallback credential does not satisfy this.
|
|
67
|
+
*/
|
|
68
|
+
export declare function assertUnsignedWebhookAuthenticationAbsent(body: string | undefined): void;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { type RequestLike } from './canonicalize';
|
|
2
|
+
import { RequestSignatureError } from './errors';
|
|
3
|
+
import { type UnresolvableOperation } from './operation-resolution';
|
|
2
4
|
import type { JwksResolver } from './jwks';
|
|
3
5
|
import type { ReplayStore } from './replay';
|
|
4
6
|
import type { RevocationStore } from './revocation';
|
|
@@ -15,8 +17,13 @@ export interface VerifyRequestOptions {
|
|
|
15
17
|
* the verifier treats the operation as "not in any required_for list" and
|
|
16
18
|
* returns an unsigned result rather than rejecting — callers in
|
|
17
19
|
* always-verify mode (where every request is signed) can leave this blank.
|
|
20
|
+
*
|
|
21
|
+
* Pass {@link UNRESOLVABLE_OPERATION} when the request body does not resolve
|
|
22
|
+
* to exactly one operation (see `resolveRequestOperation`). The verifier
|
|
23
|
+
* then rejects with `request_body_malformed` before it looks at any
|
|
24
|
+
* signature header or capability list, whether or not the request is signed.
|
|
18
25
|
*/
|
|
19
|
-
operation?: string;
|
|
26
|
+
operation?: string | UnresolvableOperation;
|
|
20
27
|
/**
|
|
21
28
|
* Trusted endpoint release pin; never inferred from request payload data.
|
|
22
29
|
* A 3.2+ pin parses `Signature` and `Content-Digest` strictly as RFC 8941
|
|
@@ -31,3 +38,31 @@ export interface VerifyRequestOptions {
|
|
|
31
38
|
agentUrlForKeyid?: (keyid: string) => string | undefined;
|
|
32
39
|
}
|
|
33
40
|
export declare function verifyRequestSignature(request: RequestLike, options: VerifyRequestOptions): Promise<VerifyResult>;
|
|
41
|
+
/** The `request_body_malformed` rejection for a body that resolves to no single operation. */
|
|
42
|
+
export declare function unresolvableOperationError(): RequestSignatureError;
|
|
43
|
+
/**
|
|
44
|
+
* Throw `request_signature_required` when an UNSIGNED request must be signed:
|
|
45
|
+
* its resolved operation is in `capability.required_for`, or its JSON-RPC
|
|
46
|
+
* method is in `capability.protocol_methods_required_for`, or it carries
|
|
47
|
+
* webhook authentication. Returns normally when none applies.
|
|
48
|
+
*
|
|
49
|
+
* "Unsigned" is the caller's contract: no `Signature` / `Signature-Input`
|
|
50
|
+
* header. The checks do not consult any fallback credential; a caller that
|
|
51
|
+
* lets a configured fallback authenticator satisfy `required_for` (the
|
|
52
|
+
* presence-gated composer) runs the pieces separately.
|
|
53
|
+
*/
|
|
54
|
+
export declare function assertUnsignedRequestAllowed(body: string | undefined, operation: string | undefined, capability: Pick<VerifierCapability, 'required_for' | 'protocol_methods_required_for'>): void;
|
|
55
|
+
/** `required_for` half of {@link assertUnsignedRequestAllowed}. */
|
|
56
|
+
export declare function assertUnsignedOperationNotRequired(operation: string | undefined, capability: Pick<VerifierCapability, 'required_for'>): void;
|
|
57
|
+
/** `protocol_methods_required_for` half of {@link assertUnsignedRequestAllowed}. */
|
|
58
|
+
export declare function assertUnsignedProtocolMethodNotRequired(body: string | undefined, capability: Pick<VerifierCapability, 'protocol_methods_required_for'>): void;
|
|
59
|
+
/**
|
|
60
|
+
* Payload-driven elevation: any request carrying webhook receiver credentials
|
|
61
|
+
* (task, reporting, artifact, or revocation callbacks) MUST be RFC 9421
|
|
62
|
+
* signed, regardless of whether the operation appears in `required_for`
|
|
63
|
+
* (#webhook-security downgrade-resistance). A bearer-only channel would
|
|
64
|
+
* otherwise let an attacker who captured a token register or update webhook
|
|
65
|
+
* credentials and redirect callbacks to a hostile URL. Unlike `required_for`,
|
|
66
|
+
* a valid fallback credential does not satisfy this.
|
|
67
|
+
*/
|
|
68
|
+
export declare function assertUnsignedWebhookAuthenticationAbsent(body: string | undefined): void;
|
|
@@ -18,6 +18,11 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
18
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
19
|
var verifier_exports = {};
|
|
20
20
|
__export(verifier_exports, {
|
|
21
|
+
assertUnsignedOperationNotRequired: () => assertUnsignedOperationNotRequired,
|
|
22
|
+
assertUnsignedProtocolMethodNotRequired: () => assertUnsignedProtocolMethodNotRequired,
|
|
23
|
+
assertUnsignedRequestAllowed: () => assertUnsignedRequestAllowed,
|
|
24
|
+
assertUnsignedWebhookAuthenticationAbsent: () => assertUnsignedWebhookAuthenticationAbsent,
|
|
25
|
+
unresolvableOperationError: () => unresolvableOperationError,
|
|
21
26
|
verifyRequestSignature: () => verifyRequestSignature
|
|
22
27
|
});
|
|
23
28
|
module.exports = __toCommonJS(verifier_exports);
|
|
@@ -29,6 +34,7 @@ var import_errors2 = require('./agent-resolver/errors.js');
|
|
|
29
34
|
var import_strict_json = require('./agent-resolver/strict-json.js');
|
|
30
35
|
var import_parser = require('./parser.js');
|
|
31
36
|
var import_crypto = require('./crypto.js');
|
|
37
|
+
var import_operation_resolution = require('./operation-resolution.js');
|
|
32
38
|
var import_webhook_auth_detection = require('./webhook-auth-detection.js');
|
|
33
39
|
var import_types = require('./types.js');
|
|
34
40
|
async function verifyRequestSignature(request, options) {
|
|
@@ -37,39 +43,10 @@ async function verifyRequestSignature(request, options) {
|
|
|
37
43
|
const pinnedBinaryEncoding = options.adcpVersion !== void 0 ? (0, import_content_digest.requestSigningEncodingForVersion)(options.adcpVersion) : void 0;
|
|
38
44
|
const sigInputHeader = (0, import_canonicalize.getHeaderValue)(request.headers, "Signature-Input");
|
|
39
45
|
const sigHeader = (0, import_canonicalize.getHeaderValue)(request.headers, "Signature");
|
|
46
|
+
if ((0, import_operation_resolution.isUnresolvableOperation)(options.operation)) throw unresolvableOperationError();
|
|
47
|
+
const operation = options.operation;
|
|
40
48
|
if (!sigInputHeader && !sigHeader) {
|
|
41
|
-
|
|
42
|
-
if (operation && options.capability.required_for.includes(operation)) {
|
|
43
|
-
throw new import_errors.RequestSignatureError(
|
|
44
|
-
"request_signature_required",
|
|
45
|
-
0,
|
|
46
|
-
`Operation "${operation}" requires a signed request`
|
|
47
|
-
);
|
|
48
|
-
}
|
|
49
|
-
const protocolMethodsRequiredFor = options.capability.protocol_methods_required_for ?? [];
|
|
50
|
-
if (protocolMethodsRequiredFor.length > 0 && exceedsUnsignedBodyInspectionCap(request.body)) {
|
|
51
|
-
throw new import_errors.RequestSignatureError(
|
|
52
|
-
"request_signature_required",
|
|
53
|
-
0,
|
|
54
|
-
"Unsigned request body exceeds the protocol method inspection cap while protocol methods require signing"
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
const protocolMethods = jsonRpcProtocolMethods(request.body);
|
|
58
|
-
const requiredProtocolMethod = protocolMethods.find((method) => protocolMethodsRequiredFor.includes(method));
|
|
59
|
-
if (requiredProtocolMethod) {
|
|
60
|
-
throw new import_errors.RequestSignatureError(
|
|
61
|
-
"request_signature_required",
|
|
62
|
-
0,
|
|
63
|
-
`Protocol method "${requiredProtocolMethod}" requires a signed request`
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
|
-
if (carriesWebhookAuthentication(request)) {
|
|
67
|
-
throw new import_errors.RequestSignatureError(
|
|
68
|
-
"request_signature_required",
|
|
69
|
-
0,
|
|
70
|
-
"Requests carrying webhook authentication must be RFC 9421 signed"
|
|
71
|
-
);
|
|
72
|
-
}
|
|
49
|
+
assertUnsignedRequestAllowed(request.body, operation, options.capability);
|
|
73
50
|
return { status: "unsigned", verified_at: now };
|
|
74
51
|
}
|
|
75
52
|
if (!sigInputHeader || !sigHeader) {
|
|
@@ -292,6 +269,57 @@ function signatureEncodingCandidates(pinned, digestPolicy) {
|
|
|
292
269
|
if (pinned === "rfc8941-base64" || digestPolicy !== "either") return [pinned];
|
|
293
270
|
return ["legacy-base64url", "rfc8941-base64"];
|
|
294
271
|
}
|
|
272
|
+
function unresolvableOperationError() {
|
|
273
|
+
return new import_errors.RequestSignatureError(
|
|
274
|
+
"request_body_malformed",
|
|
275
|
+
0,
|
|
276
|
+
"Request body does not resolve to exactly one AdCP operation"
|
|
277
|
+
);
|
|
278
|
+
}
|
|
279
|
+
function assertUnsignedRequestAllowed(body, operation, capability) {
|
|
280
|
+
assertUnsignedOperationNotRequired(operation, capability);
|
|
281
|
+
assertUnsignedProtocolMethodNotRequired(body, capability);
|
|
282
|
+
assertUnsignedWebhookAuthenticationAbsent(body);
|
|
283
|
+
}
|
|
284
|
+
function assertUnsignedOperationNotRequired(operation, capability) {
|
|
285
|
+
if (operation && capability.required_for.includes(operation)) {
|
|
286
|
+
throw new import_errors.RequestSignatureError(
|
|
287
|
+
"request_signature_required",
|
|
288
|
+
0,
|
|
289
|
+
`Operation "${operation}" requires a signed request`
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
function assertUnsignedProtocolMethodNotRequired(body, capability) {
|
|
294
|
+
const protocolMethodsRequiredFor = capability.protocol_methods_required_for ?? [];
|
|
295
|
+
if (protocolMethodsRequiredFor.length === 0) return;
|
|
296
|
+
if (exceedsUnsignedBodyInspectionCap(body)) {
|
|
297
|
+
throw new import_errors.RequestSignatureError(
|
|
298
|
+
"request_signature_required",
|
|
299
|
+
0,
|
|
300
|
+
"Unsigned request body exceeds the protocol method inspection cap while protocol methods require signing"
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
const requiredProtocolMethod = jsonRpcProtocolMethods(body).find(
|
|
304
|
+
(method) => protocolMethodsRequiredFor.includes(method)
|
|
305
|
+
);
|
|
306
|
+
if (requiredProtocolMethod) {
|
|
307
|
+
throw new import_errors.RequestSignatureError(
|
|
308
|
+
"request_signature_required",
|
|
309
|
+
0,
|
|
310
|
+
`Protocol method "${requiredProtocolMethod}" requires a signed request`
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
function assertUnsignedWebhookAuthenticationAbsent(body) {
|
|
315
|
+
if (carriesWebhookAuthentication(body)) {
|
|
316
|
+
throw new import_errors.RequestSignatureError(
|
|
317
|
+
"request_signature_required",
|
|
318
|
+
0,
|
|
319
|
+
"Requests carrying webhook authentication must be RFC 9421 signed"
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
295
323
|
function contentDigestEncodingCandidates(pinned) {
|
|
296
324
|
if (pinned === "rfc8941-base64") return [pinned];
|
|
297
325
|
return ["rfc8941-base64", "legacy-base64url"];
|
|
@@ -498,8 +526,7 @@ const MAX_UNSIGNED_BODY_INSPECTION_BYTES = 1048576;
|
|
|
498
526
|
function exceedsUnsignedBodyInspectionCap(body) {
|
|
499
527
|
return typeof body === "string" && body.length > MAX_UNSIGNED_BODY_INSPECTION_BYTES;
|
|
500
528
|
}
|
|
501
|
-
function carriesWebhookAuthentication(
|
|
502
|
-
const body = request.body;
|
|
529
|
+
function carriesWebhookAuthentication(body) {
|
|
503
530
|
if (!body) return false;
|
|
504
531
|
if (exceedsUnsignedBodyInspectionCap(body)) return true;
|
|
505
532
|
let parsed;
|
|
@@ -546,5 +573,10 @@ function validateCoveredComponents(components, capability, request) {
|
|
|
546
573
|
}
|
|
547
574
|
// Annotate the CommonJS export names for ESM import in node:
|
|
548
575
|
0 && (module.exports = {
|
|
576
|
+
assertUnsignedOperationNotRequired,
|
|
577
|
+
assertUnsignedProtocolMethodNotRequired,
|
|
578
|
+
assertUnsignedRequestAllowed,
|
|
579
|
+
assertUnsignedWebhookAuthenticationAbsent,
|
|
580
|
+
unresolvableOperationError,
|
|
549
581
|
verifyRequestSignature
|
|
550
582
|
});
|
|
@@ -16,6 +16,7 @@ import { AgentResolverError } from "./agent-resolver/errors.mjs";
|
|
|
16
16
|
import { parseStrictJson, StrictJsonError } from "./agent-resolver/strict-json.mjs";
|
|
17
17
|
import { parseSignature, parseSignatureInput } from "./parser.mjs";
|
|
18
18
|
import { jwkToPublicKey, verifySignature } from "./crypto.mjs";
|
|
19
|
+
import { isUnresolvableOperation } from "./operation-resolution.mjs";
|
|
19
20
|
import { containsWebhookAuthentication, WEBHOOK_AUTH_TRAVERSAL_DEPTH } from "./webhook-auth-detection.mjs";
|
|
20
21
|
import {
|
|
21
22
|
ALLOWED_ALGS,
|
|
@@ -30,39 +31,10 @@ async function verifyRequestSignature(request, options) {
|
|
|
30
31
|
const pinnedBinaryEncoding = options.adcpVersion !== void 0 ? requestSigningEncodingForVersion(options.adcpVersion) : void 0;
|
|
31
32
|
const sigInputHeader = getHeaderValue(request.headers, "Signature-Input");
|
|
32
33
|
const sigHeader = getHeaderValue(request.headers, "Signature");
|
|
34
|
+
if (isUnresolvableOperation(options.operation)) throw unresolvableOperationError();
|
|
35
|
+
const operation = options.operation;
|
|
33
36
|
if (!sigInputHeader && !sigHeader) {
|
|
34
|
-
|
|
35
|
-
if (operation && options.capability.required_for.includes(operation)) {
|
|
36
|
-
throw new RequestSignatureError(
|
|
37
|
-
"request_signature_required",
|
|
38
|
-
0,
|
|
39
|
-
`Operation "${operation}" requires a signed request`
|
|
40
|
-
);
|
|
41
|
-
}
|
|
42
|
-
const protocolMethodsRequiredFor = options.capability.protocol_methods_required_for ?? [];
|
|
43
|
-
if (protocolMethodsRequiredFor.length > 0 && exceedsUnsignedBodyInspectionCap(request.body)) {
|
|
44
|
-
throw new RequestSignatureError(
|
|
45
|
-
"request_signature_required",
|
|
46
|
-
0,
|
|
47
|
-
"Unsigned request body exceeds the protocol method inspection cap while protocol methods require signing"
|
|
48
|
-
);
|
|
49
|
-
}
|
|
50
|
-
const protocolMethods = jsonRpcProtocolMethods(request.body);
|
|
51
|
-
const requiredProtocolMethod = protocolMethods.find((method) => protocolMethodsRequiredFor.includes(method));
|
|
52
|
-
if (requiredProtocolMethod) {
|
|
53
|
-
throw new RequestSignatureError(
|
|
54
|
-
"request_signature_required",
|
|
55
|
-
0,
|
|
56
|
-
`Protocol method "${requiredProtocolMethod}" requires a signed request`
|
|
57
|
-
);
|
|
58
|
-
}
|
|
59
|
-
if (carriesWebhookAuthentication(request)) {
|
|
60
|
-
throw new RequestSignatureError(
|
|
61
|
-
"request_signature_required",
|
|
62
|
-
0,
|
|
63
|
-
"Requests carrying webhook authentication must be RFC 9421 signed"
|
|
64
|
-
);
|
|
65
|
-
}
|
|
37
|
+
assertUnsignedRequestAllowed(request.body, operation, options.capability);
|
|
66
38
|
return { status: "unsigned", verified_at: now };
|
|
67
39
|
}
|
|
68
40
|
if (!sigInputHeader || !sigHeader) {
|
|
@@ -285,6 +257,57 @@ function signatureEncodingCandidates(pinned, digestPolicy) {
|
|
|
285
257
|
if (pinned === "rfc8941-base64" || digestPolicy !== "either") return [pinned];
|
|
286
258
|
return ["legacy-base64url", "rfc8941-base64"];
|
|
287
259
|
}
|
|
260
|
+
function unresolvableOperationError() {
|
|
261
|
+
return new RequestSignatureError(
|
|
262
|
+
"request_body_malformed",
|
|
263
|
+
0,
|
|
264
|
+
"Request body does not resolve to exactly one AdCP operation"
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
function assertUnsignedRequestAllowed(body, operation, capability) {
|
|
268
|
+
assertUnsignedOperationNotRequired(operation, capability);
|
|
269
|
+
assertUnsignedProtocolMethodNotRequired(body, capability);
|
|
270
|
+
assertUnsignedWebhookAuthenticationAbsent(body);
|
|
271
|
+
}
|
|
272
|
+
function assertUnsignedOperationNotRequired(operation, capability) {
|
|
273
|
+
if (operation && capability.required_for.includes(operation)) {
|
|
274
|
+
throw new RequestSignatureError(
|
|
275
|
+
"request_signature_required",
|
|
276
|
+
0,
|
|
277
|
+
`Operation "${operation}" requires a signed request`
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
function assertUnsignedProtocolMethodNotRequired(body, capability) {
|
|
282
|
+
const protocolMethodsRequiredFor = capability.protocol_methods_required_for ?? [];
|
|
283
|
+
if (protocolMethodsRequiredFor.length === 0) return;
|
|
284
|
+
if (exceedsUnsignedBodyInspectionCap(body)) {
|
|
285
|
+
throw new RequestSignatureError(
|
|
286
|
+
"request_signature_required",
|
|
287
|
+
0,
|
|
288
|
+
"Unsigned request body exceeds the protocol method inspection cap while protocol methods require signing"
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
const requiredProtocolMethod = jsonRpcProtocolMethods(body).find(
|
|
292
|
+
(method) => protocolMethodsRequiredFor.includes(method)
|
|
293
|
+
);
|
|
294
|
+
if (requiredProtocolMethod) {
|
|
295
|
+
throw new RequestSignatureError(
|
|
296
|
+
"request_signature_required",
|
|
297
|
+
0,
|
|
298
|
+
`Protocol method "${requiredProtocolMethod}" requires a signed request`
|
|
299
|
+
);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
function assertUnsignedWebhookAuthenticationAbsent(body) {
|
|
303
|
+
if (carriesWebhookAuthentication(body)) {
|
|
304
|
+
throw new RequestSignatureError(
|
|
305
|
+
"request_signature_required",
|
|
306
|
+
0,
|
|
307
|
+
"Requests carrying webhook authentication must be RFC 9421 signed"
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
288
311
|
function contentDigestEncodingCandidates(pinned) {
|
|
289
312
|
if (pinned === "rfc8941-base64") return [pinned];
|
|
290
313
|
return ["rfc8941-base64", "legacy-base64url"];
|
|
@@ -491,8 +514,7 @@ const MAX_UNSIGNED_BODY_INSPECTION_BYTES = 1048576;
|
|
|
491
514
|
function exceedsUnsignedBodyInspectionCap(body) {
|
|
492
515
|
return typeof body === "string" && body.length > MAX_UNSIGNED_BODY_INSPECTION_BYTES;
|
|
493
516
|
}
|
|
494
|
-
function carriesWebhookAuthentication(
|
|
495
|
-
const body = request.body;
|
|
517
|
+
function carriesWebhookAuthentication(body) {
|
|
496
518
|
if (!body) return false;
|
|
497
519
|
if (exceedsUnsignedBodyInspectionCap(body)) return true;
|
|
498
520
|
let parsed;
|
|
@@ -538,5 +560,10 @@ function validateCoveredComponents(components, capability, request) {
|
|
|
538
560
|
}
|
|
539
561
|
}
|
|
540
562
|
export {
|
|
563
|
+
assertUnsignedOperationNotRequired,
|
|
564
|
+
assertUnsignedProtocolMethodNotRequired,
|
|
565
|
+
assertUnsignedRequestAllowed,
|
|
566
|
+
assertUnsignedWebhookAuthenticationAbsent,
|
|
567
|
+
unresolvableOperationError,
|
|
541
568
|
verifyRequestSignature
|
|
542
569
|
};
|