@fedify/fedify 2.1.0-dev.503 → 2.1.0-dev.513

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.
Files changed (78) hide show
  1. package/dist/{builder-BHUnSQtB.js → builder-DSYM2n7q.js} +9 -3
  2. package/dist/compat/mod.d.cts +3 -3
  3. package/dist/compat/mod.d.ts +3 -3
  4. package/dist/compat/transformers.test.js +12 -12
  5. package/dist/{context-DZJhUmzF.d.ts → context--RwChtri.d.ts} +54 -2
  6. package/dist/{context-D3QkEtZd.d.cts → context-DL0cPpPV.d.cts} +54 -2
  7. package/dist/{deno-BYerLnry.js → deno-aj03PJW6.js} +1 -1
  8. package/dist/{docloader-MSkogD2T.js → docloader-tMQxWuTM.js} +2 -2
  9. package/dist/federation/builder.test.js +14 -3
  10. package/dist/federation/handler.test.js +97 -13
  11. package/dist/federation/idempotency.test.js +12 -12
  12. package/dist/federation/inbox.test.js +2 -2
  13. package/dist/federation/keycache.test.js +46 -2
  14. package/dist/federation/middleware.test.js +206 -12
  15. package/dist/federation/mod.cjs +4 -4
  16. package/dist/federation/mod.d.cts +4 -4
  17. package/dist/federation/mod.d.ts +4 -4
  18. package/dist/federation/mod.js +4 -4
  19. package/dist/federation/send.test.js +5 -5
  20. package/dist/federation/webfinger.test.js +12 -12
  21. package/dist/{http-DkHdFfrc.d.ts → http-BbfOqHGG.d.ts} +80 -8
  22. package/dist/{http-CSX1-Mgi.js → http-CoM8qFZV.js} +295 -101
  23. package/dist/{http-Cz3MlXAZ.d.cts → http-DsqqmkXi.d.cts} +80 -8
  24. package/dist/{http-DJT6NciB.cjs → http-_l2XRk4S.cjs} +305 -99
  25. package/dist/{http-S2U3qDwN.js → http-pa5yLKK7.js} +153 -57
  26. package/dist/{inbox-BaA0g5I_.js → inbox-EI2EtQri.js} +1 -1
  27. package/dist/{key-DCdTVZiK.js → key-j1AjF3HL.js} +145 -47
  28. package/dist/keycache-C7k8s1Bk.js +102 -0
  29. package/dist/{kv-cache-CQPL_aGY.js → kv-cache-CMk-kfWp.js} +1 -1
  30. package/dist/{kv-cache-Vtxhbo1W.cjs → kv-cache-D0IkOVYm.cjs} +1 -1
  31. package/dist/{ld-CrX7pQda.js → ld-CD48Tb3_.js} +2 -2
  32. package/dist/middleware-BQhgZunR.cjs +12 -0
  33. package/dist/{middleware-MlO5iUeZ.js → middleware-BRIM_AL5.js} +158 -22
  34. package/dist/{middleware-C8PKuPrm.js → middleware-CWMuqleA.js} +4 -4
  35. package/dist/{middleware-D4S6i4A_.cjs → middleware-D1Awtgcf.cjs} +158 -22
  36. package/dist/{middleware-BelSJK7m.js → middleware-DbmQ5-Ph.js} +100 -24
  37. package/dist/{middleware-CfI9C9Xy.js → middleware-b9jiK13s.js} +12 -12
  38. package/dist/{mod-CwZXZJ9d.d.ts → mod-BugwI0JN.d.ts} +1 -1
  39. package/dist/{mod-DPkRU3EK.d.cts → mod-CFBU2OT3.d.cts} +1 -1
  40. package/dist/{mod-DUWcVv49.d.ts → mod-CvxylbuV.d.ts} +1 -1
  41. package/dist/{mod-DVwHUI_x.d.cts → mod-DE8MYisy.d.cts} +1 -1
  42. package/dist/{mod-DXsQakeS.d.cts → mod-DKG0ovjR.d.cts} +1 -1
  43. package/dist/{mod-DnSsduJF.d.ts → mod-DcfFNgYf.d.ts} +1 -1
  44. package/dist/{mod-Di3W5OdP.d.cts → mod-Dp0kK0hO.d.cts} +1 -1
  45. package/dist/{mod-DosD6NsG.d.ts → mod-Z7lIaCfo.d.ts} +1 -1
  46. package/dist/mod.cjs +8 -4
  47. package/dist/mod.d.cts +8 -8
  48. package/dist/mod.d.ts +8 -8
  49. package/dist/mod.js +7 -5
  50. package/dist/nodeinfo/handler.test.js +12 -12
  51. package/dist/otel/exporter.test.js +43 -2
  52. package/dist/otel/mod.cjs +7 -1
  53. package/dist/otel/mod.d.cts +12 -0
  54. package/dist/otel/mod.d.ts +12 -0
  55. package/dist/otel/mod.js +7 -1
  56. package/dist/{owner-BAlnLKMO.js → owner-ZjRdCtVA.js} +1 -1
  57. package/dist/{proof-BgUVmaJz.js → proof-CQfh4S5r.js} +1 -1
  58. package/dist/{proof-CR5RUAmy.cjs → proof-D2auLGZD.cjs} +1 -1
  59. package/dist/{proof-DMgHaXNJ.js → proof-gmdzyEsv.js} +2 -2
  60. package/dist/{send-B2aZYf9A.js → send-1lNwij0f.js} +2 -2
  61. package/dist/sig/http.test.js +85 -5
  62. package/dist/sig/key.test.js +70 -3
  63. package/dist/sig/ld.test.js +3 -3
  64. package/dist/sig/mod.cjs +4 -2
  65. package/dist/sig/mod.d.cts +3 -3
  66. package/dist/sig/mod.d.ts +3 -3
  67. package/dist/sig/mod.js +3 -3
  68. package/dist/sig/owner.test.js +3 -3
  69. package/dist/sig/proof.test.js +3 -3
  70. package/dist/testing/mod.d.ts +92 -0
  71. package/dist/utils/docloader.test.js +4 -4
  72. package/dist/utils/mod.cjs +2 -2
  73. package/dist/utils/mod.d.cts +2 -2
  74. package/dist/utils/mod.d.ts +2 -2
  75. package/dist/utils/mod.js +2 -2
  76. package/package.json +5 -5
  77. package/dist/keycache-DRxpZ5r9.js +0 -48
  78. package/dist/middleware-D4XcpSBG.cjs +0 -12
package/dist/otel/mod.cjs CHANGED
@@ -127,11 +127,17 @@ var FedifySpanExporter = class {
127
127
  const verified = attrs["activitypub.activity.verified"];
128
128
  const httpSigVerified = attrs["http_signatures.verified"];
129
129
  const httpSigKeyId = attrs["http_signatures.key_id"];
130
+ const httpSigFailureReason = attrs["http_signatures.failure_reason"];
131
+ const httpSigKeyFetchStatus = attrs["http_signatures.key_fetch_status"];
132
+ const httpSigKeyFetchError = attrs["http_signatures.key_fetch_error"];
130
133
  const ldSigVerified = attrs["ld_signatures.verified"];
131
134
  let signatureDetails;
132
- if (typeof httpSigVerified === "boolean" || typeof ldSigVerified === "boolean") signatureDetails = {
135
+ if (typeof httpSigVerified === "boolean" || typeof ldSigVerified === "boolean" || typeof httpSigFailureReason === "string") signatureDetails = {
133
136
  httpSignaturesVerified: httpSigVerified === true,
134
137
  httpSignaturesKeyId: typeof httpSigKeyId === "string" && httpSigKeyId !== "" ? httpSigKeyId : void 0,
138
+ httpSignaturesFailureReason: typeof httpSigFailureReason === "string" && httpSigFailureReason !== "" ? httpSigFailureReason : void 0,
139
+ httpSignaturesKeyFetchStatus: typeof httpSigKeyFetchStatus === "number" ? httpSigKeyFetchStatus : void 0,
140
+ httpSignaturesKeyFetchError: typeof httpSigKeyFetchError === "string" && httpSigKeyFetchError !== "" ? httpSigKeyFetchError : void 0,
135
141
  ldSignaturesVerified: ldSigVerified === true
136
142
  };
137
143
  return {
@@ -24,6 +24,18 @@ interface SignatureVerificationDetails {
24
24
  */
25
25
  readonly httpSignaturesKeyId?: string;
26
26
  /**
27
+ * The reason why HTTP signature verification failed, if available.
28
+ */
29
+ readonly httpSignaturesFailureReason?: string;
30
+ /**
31
+ * The HTTP status code from a failed key fetch, if available.
32
+ */
33
+ readonly httpSignaturesKeyFetchStatus?: number;
34
+ /**
35
+ * The error type from a non-HTTP key fetch failure, if available.
36
+ */
37
+ readonly httpSignaturesKeyFetchError?: string;
38
+ /**
27
39
  * Whether Linked Data Signatures were verified.
28
40
  */
29
41
  readonly ldSignaturesVerified: boolean;
@@ -26,6 +26,18 @@ interface SignatureVerificationDetails {
26
26
  */
27
27
  readonly httpSignaturesKeyId?: string;
28
28
  /**
29
+ * The reason why HTTP signature verification failed, if available.
30
+ */
31
+ readonly httpSignaturesFailureReason?: string;
32
+ /**
33
+ * The HTTP status code from a failed key fetch, if available.
34
+ */
35
+ readonly httpSignaturesKeyFetchStatus?: number;
36
+ /**
37
+ * The error type from a non-HTTP key fetch failure, if available.
38
+ */
39
+ readonly httpSignaturesKeyFetchError?: string;
40
+ /**
29
41
  * Whether Linked Data Signatures were verified.
30
42
  */
31
43
  readonly ldSignaturesVerified: boolean;
package/dist/otel/mod.js CHANGED
@@ -126,11 +126,17 @@ var FedifySpanExporter = class {
126
126
  const verified = attrs["activitypub.activity.verified"];
127
127
  const httpSigVerified = attrs["http_signatures.verified"];
128
128
  const httpSigKeyId = attrs["http_signatures.key_id"];
129
+ const httpSigFailureReason = attrs["http_signatures.failure_reason"];
130
+ const httpSigKeyFetchStatus = attrs["http_signatures.key_fetch_status"];
131
+ const httpSigKeyFetchError = attrs["http_signatures.key_fetch_error"];
129
132
  const ldSigVerified = attrs["ld_signatures.verified"];
130
133
  let signatureDetails;
131
- if (typeof httpSigVerified === "boolean" || typeof ldSigVerified === "boolean") signatureDetails = {
134
+ if (typeof httpSigVerified === "boolean" || typeof ldSigVerified === "boolean" || typeof httpSigFailureReason === "string") signatureDetails = {
132
135
  httpSignaturesVerified: httpSigVerified === true,
133
136
  httpSignaturesKeyId: typeof httpSigKeyId === "string" && httpSigKeyId !== "" ? httpSigKeyId : void 0,
137
+ httpSignaturesFailureReason: typeof httpSigFailureReason === "string" && httpSigFailureReason !== "" ? httpSigFailureReason : void 0,
138
+ httpSignaturesKeyFetchStatus: typeof httpSigKeyFetchStatus === "number" ? httpSigKeyFetchStatus : void 0,
139
+ httpSignaturesKeyFetchError: typeof httpSigKeyFetchError === "string" && httpSigKeyFetchError !== "" ? httpSigKeyFetchError : void 0,
134
140
  ldSignaturesVerified: ldSigVerified === true
135
141
  };
136
142
  return {
@@ -3,7 +3,7 @@
3
3
  import { URLPattern } from "urlpattern-polyfill";
4
4
  globalThis.addEventListener = () => {};
5
5
 
6
- import { deno_default } from "./deno-BYerLnry.js";
6
+ import { deno_default } from "./deno-aj03PJW6.js";
7
7
  import { CryptographicKey, Object as Object$1, isActor } from "@fedify/vocab";
8
8
  import { getDocumentLoader } from "@fedify/vocab-runtime";
9
9
  import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
@@ -2,7 +2,7 @@
2
2
  import { Temporal } from "@js-temporal/polyfill";
3
3
  import { URLPattern } from "urlpattern-polyfill";
4
4
 
5
- import { deno_default, fetchKey, validateCryptoKey } from "./http-CSX1-Mgi.js";
5
+ import { deno_default, fetchKey, validateCryptoKey } from "./http-CoM8qFZV.js";
6
6
  import { getLogger } from "@logtape/logtape";
7
7
  import { Activity, CryptographicKey, DataIntegrityProof, Multikey, Object as Object$1, getTypeId, isActor } from "@fedify/vocab";
8
8
  import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
@@ -3,7 +3,7 @@
3
3
  const { URLPattern } = require("urlpattern-polyfill");
4
4
 
5
5
  const require_chunk = require('./chunk-CGaQZ11T.cjs');
6
- const require_http = require('./http-DJT6NciB.cjs');
6
+ const require_http = require('./http-_l2XRk4S.cjs');
7
7
  const __logtape_logtape = require_chunk.__toESM(require("@logtape/logtape"));
8
8
  const __fedify_vocab = require_chunk.__toESM(require("@fedify/vocab"));
9
9
  const __opentelemetry_api = require_chunk.__toESM(require("@opentelemetry/api"));
@@ -3,8 +3,8 @@
3
3
  import { URLPattern } from "urlpattern-polyfill";
4
4
  globalThis.addEventListener = () => {};
5
5
 
6
- import { deno_default } from "./deno-BYerLnry.js";
7
- import { fetchKey, validateCryptoKey } from "./key-DCdTVZiK.js";
6
+ import { deno_default } from "./deno-aj03PJW6.js";
7
+ import { fetchKey, validateCryptoKey } from "./key-j1AjF3HL.js";
8
8
  import { getLogger } from "@logtape/logtape";
9
9
  import { Activity, DataIntegrityProof, Multikey, getTypeId } from "@fedify/vocab";
10
10
  import { SpanStatusCode, trace } from "@opentelemetry/api";
@@ -3,8 +3,8 @@
3
3
  import { URLPattern } from "urlpattern-polyfill";
4
4
  globalThis.addEventListener = () => {};
5
5
 
6
- import { deno_default } from "./deno-BYerLnry.js";
7
- import { doubleKnock } from "./http-S2U3qDwN.js";
6
+ import { deno_default } from "./deno-aj03PJW6.js";
7
+ import { doubleKnock } from "./http-pa5yLKK7.js";
8
8
  import { getLogger } from "@logtape/logtape";
9
9
  import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
10
10
 
@@ -3,20 +3,20 @@
3
3
  import { URLPattern } from "urlpattern-polyfill";
4
4
  globalThis.addEventListener = () => {};
5
5
 
6
- import { mockDocumentLoader, test } from "../dist-B5f6a8Tt.js";
6
+ import { createTestTracerProvider, mockDocumentLoader, test } from "../dist-B5f6a8Tt.js";
7
7
  import { assertEquals } from "../assert_equals-DSbWqCm3.js";
8
8
  import { assert } from "../assert-MZs1qjMx.js";
9
9
  import "../assert_instance_of-DHz7EHNU.js";
10
- import "../deno-BYerLnry.js";
11
- import { exportJwk } from "../key-DCdTVZiK.js";
12
- import { createRfc9421SignatureBase, doubleKnock, formatRfc9421Signature, formatRfc9421SignatureParameters, parseRfc9421Signature, parseRfc9421SignatureInput, signRequest, timingSafeEqual, verifyRequest } from "../http-S2U3qDwN.js";
10
+ import "../deno-aj03PJW6.js";
11
+ import { exportJwk } from "../key-j1AjF3HL.js";
12
+ import { createRfc9421SignatureBase, doubleKnock, formatRfc9421Signature, formatRfc9421SignatureParameters, parseRfc9421Signature, parseRfc9421SignatureInput, signRequest, timingSafeEqual, verifyRequest, verifyRequestDetailed } from "../http-pa5yLKK7.js";
13
13
  import { assertExists, assertStringIncludes } from "../std__assert-DWivtrGR.js";
14
14
  import { assertFalse, assertRejects } from "../assert_rejects-Ce45JcFg.js";
15
15
  import { assertThrows } from "../assert_throws-BNXdRGWP.js";
16
16
  import "../assert_not_equals-C80BG-_5.js";
17
17
  import { rsaPrivateKey2, rsaPublicKey1, rsaPublicKey2, rsaPublicKey5 } from "../keys-ZbcByPg9.js";
18
18
  import { esm_default } from "../esm-DGl7uK1r.js";
19
- import { exportSpki } from "@fedify/vocab-runtime";
19
+ import { FetchError, exportSpki } from "@fedify/vocab-runtime";
20
20
  import { encodeBase64 } from "byte-encodings/base64";
21
21
 
22
22
  //#region src/sig/http.test.ts
@@ -162,6 +162,86 @@ test("verifyRequest() [draft-cavage]", async () => {
162
162
  };
163
163
  assert(await verifyRequest(request2, options2) != null);
164
164
  });
165
+ test("verifyRequestDetailed() classifies malformed signatures as invalid", async () => {
166
+ const draftMissingKeyId = await verifyRequestDetailed(new Request("https://example.com/", {
167
+ method: "POST",
168
+ headers: {
169
+ Date: "Tue, 05 Mar 2024 07:49:44 GMT",
170
+ Digest: "sha-256=47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=",
171
+ Signature: "headers=\"(request-target) date digest\",signature=\"AAAA\""
172
+ },
173
+ body: ""
174
+ }), {
175
+ documentLoader: mockDocumentLoader,
176
+ contextLoader: mockDocumentLoader
177
+ });
178
+ assertFalse(draftMissingKeyId.verified);
179
+ assertEquals(draftMissingKeyId.reason.type, "invalidSignature");
180
+ assertFalse("keyId" in draftMissingKeyId.reason);
181
+ const draftInvalidKeyId = await verifyRequestDetailed(new Request("https://example.com/", {
182
+ method: "POST",
183
+ headers: {
184
+ Date: "Tue, 05 Mar 2024 07:49:44 GMT",
185
+ Digest: "sha-256=47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=",
186
+ Signature: "keyId=\"not a url\",headers=\"(request-target) date digest\",signature=\"AAAA\""
187
+ },
188
+ body: ""
189
+ }), {
190
+ documentLoader: mockDocumentLoader,
191
+ contextLoader: mockDocumentLoader
192
+ });
193
+ assertFalse(draftInvalidKeyId.verified);
194
+ assertEquals(draftInvalidKeyId.reason.type, "invalidSignature");
195
+ assertFalse("keyId" in draftInvalidKeyId.reason);
196
+ const rfcMissingKeyId = await verifyRequestDetailed(new Request("https://example.com/api/resource", {
197
+ method: "GET",
198
+ headers: {
199
+ Host: "example.com",
200
+ Date: "Tue, 05 Mar 2024 07:49:44 GMT",
201
+ "Signature-Input": "sig1=(\"@method\" \"@target-uri\" \"@authority\");created=1709626184",
202
+ Signature: "sig1=:AAAA:"
203
+ }
204
+ }), {
205
+ documentLoader: mockDocumentLoader,
206
+ contextLoader: mockDocumentLoader,
207
+ spec: "rfc9421"
208
+ });
209
+ assertFalse(rfcMissingKeyId.verified);
210
+ assertEquals(rfcMissingKeyId.reason.type, "invalidSignature");
211
+ assertFalse("keyId" in rfcMissingKeyId.reason);
212
+ });
213
+ test("verifyRequestDetailed() records failure details on span", async () => {
214
+ const [tracerProvider, exporter] = createTestTracerProvider();
215
+ const keyId = new URL("https://gone.example/actors/alice#main-key");
216
+ const request = await signRequest(new Request("https://example.com/inbox", {
217
+ method: "POST",
218
+ headers: {
219
+ "Content-Type": "application/activity+json",
220
+ accept: "application/ld+json"
221
+ },
222
+ body: JSON.stringify({
223
+ "@context": "https://www.w3.org/ns/activitystreams",
224
+ type: "Create",
225
+ actor: "https://gone.example/actors/alice"
226
+ })
227
+ }), rsaPrivateKey2, keyId);
228
+ const result = await verifyRequestDetailed(request, {
229
+ tracerProvider,
230
+ contextLoader: mockDocumentLoader,
231
+ documentLoader(url) {
232
+ if (url === keyId.href) throw new FetchError(keyId, `HTTP 410: ${keyId.href}`, new Response(null, { status: 410 }));
233
+ return mockDocumentLoader(url);
234
+ }
235
+ });
236
+ assertFalse(result.verified);
237
+ const spans = exporter.getSpans("http_signatures.verify");
238
+ assertEquals(spans.length, 1);
239
+ const span = spans[0];
240
+ assertEquals(span.attributes["http_signatures.verified"], false);
241
+ assertEquals(span.attributes["http_signatures.failure_reason"], "keyFetchError");
242
+ assertEquals(span.attributes["http_signatures.key_id"], keyId.href);
243
+ assertEquals(span.attributes["http_signatures.key_fetch_status"], 410);
244
+ });
165
245
  test("signRequest() and verifyRequest() [rfc9421] implementation", async () => {
166
246
  const currentTimestamp = 1709626184;
167
247
  const currentTime = Temporal.Instant.from("2024-03-05T08:09:44Z");
@@ -3,18 +3,19 @@
3
3
  import { URLPattern } from "urlpattern-polyfill";
4
4
  globalThis.addEventListener = () => {};
5
5
 
6
- import { mockDocumentLoader, test } from "../dist-B5f6a8Tt.js";
6
+ import { createTestTracerProvider, mockDocumentLoader, test } from "../dist-B5f6a8Tt.js";
7
7
  import { assertEquals } from "../assert_equals-DSbWqCm3.js";
8
8
  import "../assert-MZs1qjMx.js";
9
9
  import "../assert_instance_of-DHz7EHNU.js";
10
- import "../deno-BYerLnry.js";
11
- import { exportJwk, fetchKey, generateCryptoKeyPair, importJwk, validateCryptoKey } from "../key-DCdTVZiK.js";
10
+ import "../deno-aj03PJW6.js";
11
+ import { exportJwk, fetchKey, fetchKeyDetailed, generateCryptoKeyPair, importJwk, validateCryptoKey } from "../key-j1AjF3HL.js";
12
12
  import "../std__assert-DWivtrGR.js";
13
13
  import { assertRejects } from "../assert_rejects-Ce45JcFg.js";
14
14
  import { assertThrows } from "../assert_throws-BNXdRGWP.js";
15
15
  import "../assert_not_equals-C80BG-_5.js";
16
16
  import { ed25519Multikey, rsaPrivateKey2, rsaPublicKey1, rsaPublicKey2, rsaPublicKey3 } from "../keys-ZbcByPg9.js";
17
17
  import { CryptographicKey, Multikey } from "@fedify/vocab";
18
+ import { FetchError } from "@fedify/vocab-runtime";
18
19
 
19
20
  //#region src/sig/key.test.ts
20
21
  test("validateCryptoKey()", async () => {
@@ -243,5 +244,71 @@ test("fetchKey()", async () => {
243
244
  cached: false
244
245
  });
245
246
  });
247
+ test("fetchKeyDetailed()", async () => {
248
+ const cache = { "https://example.com/nothing": null };
249
+ let documentLoaderCalls = 0;
250
+ const [tracerProvider, exporter] = createTestTracerProvider();
251
+ const options = {
252
+ documentLoader(url) {
253
+ documentLoaderCalls++;
254
+ return mockDocumentLoader(url);
255
+ },
256
+ contextLoader: mockDocumentLoader,
257
+ tracerProvider,
258
+ keyCache: {
259
+ get(keyId) {
260
+ return Promise.resolve(cache[keyId.href]);
261
+ },
262
+ set(keyId, key) {
263
+ cache[keyId.href] = key;
264
+ return Promise.resolve();
265
+ }
266
+ }
267
+ };
268
+ assertEquals(await fetchKeyDetailed("https://example.com/nothing", CryptographicKey, options), {
269
+ key: null,
270
+ cached: true
271
+ });
272
+ assertEquals(documentLoaderCalls, 0);
273
+ assertEquals(await fetchKeyDetailed("https://example.com/key", CryptographicKey, options), {
274
+ key: rsaPublicKey1,
275
+ cached: false
276
+ });
277
+ assertEquals(documentLoaderCalls, 1);
278
+ const spans = exporter.getSpans("activitypub.fetch_key");
279
+ assertEquals(spans.length, 2);
280
+ assertEquals(spans[0].attributes["activitypub.actor.key.cached"], true);
281
+ assertEquals(spans[1].attributes["activitypub.actor.key.cached"], false);
282
+ });
283
+ test("fetchKeyDetailed() returns detailed fetch errors", async () => {
284
+ const goneKeyId = new URL("https://example.com/gone-key");
285
+ const goneResult = await fetchKeyDetailed(goneKeyId, CryptographicKey, {
286
+ documentLoader(url) {
287
+ if (url === goneKeyId.href) throw new FetchError(goneKeyId, `HTTP 410: ${goneKeyId.href}`, new Response(null, { status: 410 }));
288
+ return mockDocumentLoader(url);
289
+ },
290
+ contextLoader: mockDocumentLoader
291
+ });
292
+ assertEquals(goneResult.key, null);
293
+ assertEquals(goneResult.cached, false);
294
+ const goneError = goneResult.fetchError;
295
+ assertEquals(goneError != null && "status" in goneError, true);
296
+ if (goneError == null || !("status" in goneError)) throw new Error("Expected HTTP fetch error details.");
297
+ assertEquals(goneError.status, 410);
298
+ assertEquals(goneError.response.status, 410);
299
+ const failure = /* @__PURE__ */ new TypeError("boom");
300
+ const errorResult = await fetchKeyDetailed("https://example.com/error-key", CryptographicKey, {
301
+ documentLoader() {
302
+ throw failure;
303
+ },
304
+ contextLoader: mockDocumentLoader
305
+ });
306
+ assertEquals(errorResult.key, null);
307
+ assertEquals(errorResult.cached, false);
308
+ const detailedError = errorResult.fetchError;
309
+ assertEquals(detailedError != null && "error" in detailedError, true);
310
+ if (detailedError == null || !("error" in detailedError)) throw new Error("Expected non-HTTP fetch error details.");
311
+ assertEquals(detailedError.error, failure);
312
+ });
246
313
 
247
314
  //#endregion
@@ -6,9 +6,9 @@
6
6
  import { mockDocumentLoader, test } from "../dist-B5f6a8Tt.js";
7
7
  import { assertEquals } from "../assert_equals-DSbWqCm3.js";
8
8
  import { assert } from "../assert-MZs1qjMx.js";
9
- import "../deno-BYerLnry.js";
10
- import { generateCryptoKeyPair } from "../key-DCdTVZiK.js";
11
- import { attachSignature, createSignature, detachSignature, signJsonLd, verifyJsonLd, verifySignature } from "../ld-CrX7pQda.js";
9
+ import "../deno-aj03PJW6.js";
10
+ import { generateCryptoKeyPair } from "../key-j1AjF3HL.js";
11
+ import { attachSignature, createSignature, detachSignature, signJsonLd, verifyJsonLd, verifySignature } from "../ld-CD48Tb3_.js";
12
12
  import { assertFalse, assertRejects } from "../assert_rejects-Ce45JcFg.js";
13
13
  import { assertThrows } from "../assert_throws-BNXdRGWP.js";
14
14
  import { ed25519Multikey, ed25519PrivateKey, rsaPrivateKey2, rsaPrivateKey3, rsaPublicKey2, rsaPublicKey3 } from "../keys-ZbcByPg9.js";
package/dist/sig/mod.cjs CHANGED
@@ -2,8 +2,8 @@
2
2
  const { Temporal } = require("@js-temporal/polyfill");
3
3
  const { URLPattern } = require("urlpattern-polyfill");
4
4
 
5
- const require_http = require('../http-DJT6NciB.cjs');
6
- const require_proof = require('../proof-CR5RUAmy.cjs');
5
+ const require_http = require('../http-_l2XRk4S.cjs');
6
+ const require_proof = require('../proof-D2auLGZD.cjs');
7
7
  require('../sig-vX39WyWI.cjs');
8
8
 
9
9
  exports.attachSignature = require_proof.attachSignature;
@@ -13,6 +13,7 @@ exports.detachSignature = require_proof.detachSignature;
13
13
  exports.doesActorOwnKey = require_proof.doesActorOwnKey;
14
14
  exports.exportJwk = require_http.exportJwk;
15
15
  exports.fetchKey = require_http.fetchKey;
16
+ exports.fetchKeyDetailed = require_http.fetchKeyDetailed;
16
17
  exports.generateCryptoKeyPair = require_http.generateCryptoKeyPair;
17
18
  exports.getKeyOwner = require_proof.getKeyOwner;
18
19
  exports.importJwk = require_http.importJwk;
@@ -23,4 +24,5 @@ exports.verifyJsonLd = require_proof.verifyJsonLd;
23
24
  exports.verifyObject = require_proof.verifyObject;
24
25
  exports.verifyProof = require_proof.verifyProof;
25
26
  exports.verifyRequest = require_http.verifyRequest;
27
+ exports.verifyRequestDetailed = require_http.verifyRequestDetailed;
26
28
  exports.verifySignature = require_proof.verifySignature;
@@ -1,4 +1,4 @@
1
- import { FetchKeyOptions, FetchKeyResult, HttpMessageSignaturesSpec, HttpMessageSignaturesSpecDeterminer, KeyCache, SignRequestOptions, VerifyRequestOptions, exportJwk, fetchKey, generateCryptoKeyPair, importJwk, signRequest, verifyRequest } from "../http-Cz3MlXAZ.cjs";
1
+ import { FetchKeyDetailedResult, FetchKeyErrorResult, FetchKeyOptions, FetchKeyResult, HttpMessageSignaturesSpec, HttpMessageSignaturesSpecDeterminer, KeyCache, SignRequestOptions, VerifyRequestDetailedResult, VerifyRequestFailureReason, VerifyRequestOptions, exportJwk, fetchKey, fetchKeyDetailed, generateCryptoKeyPair, importJwk, signRequest, verifyRequest, verifyRequestDetailed } from "../http-DsqqmkXi.cjs";
2
2
  import { DoesActorOwnKeyOptions, GetKeyOwnerOptions, doesActorOwnKey, getKeyOwner } from "../owner-1AbPBOOZ.cjs";
3
- import { CreateProofOptions, CreateSignatureOptions, SignJsonLdOptions, SignObjectOptions, VerifyJsonLdOptions, VerifyObjectOptions, VerifyProofOptions, VerifySignatureOptions, attachSignature, createProof, createSignature, detachSignature, signJsonLd, signObject, verifyJsonLd, verifyObject, verifyProof, verifySignature } from "../mod-DPkRU3EK.cjs";
4
- export { CreateProofOptions, CreateSignatureOptions, DoesActorOwnKeyOptions, FetchKeyOptions, FetchKeyResult, GetKeyOwnerOptions, HttpMessageSignaturesSpec, HttpMessageSignaturesSpecDeterminer, KeyCache, SignJsonLdOptions, SignObjectOptions, SignRequestOptions, VerifyJsonLdOptions, VerifyObjectOptions, VerifyProofOptions, VerifyRequestOptions, VerifySignatureOptions, attachSignature, createProof, createSignature, detachSignature, doesActorOwnKey, exportJwk, fetchKey, generateCryptoKeyPair, getKeyOwner, importJwk, signJsonLd, signObject, signRequest, verifyJsonLd, verifyObject, verifyProof, verifyRequest, verifySignature };
3
+ import { CreateProofOptions, CreateSignatureOptions, SignJsonLdOptions, SignObjectOptions, VerifyJsonLdOptions, VerifyObjectOptions, VerifyProofOptions, VerifySignatureOptions, attachSignature, createProof, createSignature, detachSignature, signJsonLd, signObject, verifyJsonLd, verifyObject, verifyProof, verifySignature } from "../mod-CFBU2OT3.cjs";
4
+ export { CreateProofOptions, CreateSignatureOptions, DoesActorOwnKeyOptions, FetchKeyDetailedResult, FetchKeyErrorResult, FetchKeyOptions, FetchKeyResult, GetKeyOwnerOptions, HttpMessageSignaturesSpec, HttpMessageSignaturesSpecDeterminer, KeyCache, SignJsonLdOptions, SignObjectOptions, SignRequestOptions, VerifyJsonLdOptions, VerifyObjectOptions, VerifyProofOptions, VerifyRequestDetailedResult, VerifyRequestFailureReason, VerifyRequestOptions, VerifySignatureOptions, attachSignature, createProof, createSignature, detachSignature, doesActorOwnKey, exportJwk, fetchKey, fetchKeyDetailed, generateCryptoKeyPair, getKeyOwner, importJwk, signJsonLd, signObject, signRequest, verifyJsonLd, verifyObject, verifyProof, verifyRequest, verifyRequestDetailed, verifySignature };
package/dist/sig/mod.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
- import { FetchKeyOptions, FetchKeyResult, HttpMessageSignaturesSpec, HttpMessageSignaturesSpecDeterminer, KeyCache, SignRequestOptions, VerifyRequestOptions, exportJwk, fetchKey, generateCryptoKeyPair, importJwk, signRequest, verifyRequest } from "../http-DkHdFfrc.js";
3
+ import { FetchKeyDetailedResult, FetchKeyErrorResult, FetchKeyOptions, FetchKeyResult, HttpMessageSignaturesSpec, HttpMessageSignaturesSpecDeterminer, KeyCache, SignRequestOptions, VerifyRequestDetailedResult, VerifyRequestFailureReason, VerifyRequestOptions, exportJwk, fetchKey, fetchKeyDetailed, generateCryptoKeyPair, importJwk, signRequest, verifyRequest, verifyRequestDetailed } from "../http-BbfOqHGG.js";
4
4
  import { DoesActorOwnKeyOptions, GetKeyOwnerOptions, doesActorOwnKey, getKeyOwner } from "../owner-gd0Q9FuU.js";
5
- import { CreateProofOptions, CreateSignatureOptions, SignJsonLdOptions, SignObjectOptions, VerifyJsonLdOptions, VerifyObjectOptions, VerifyProofOptions, VerifySignatureOptions, attachSignature, createProof, createSignature, detachSignature, signJsonLd, signObject, verifyJsonLd, verifyObject, verifyProof, verifySignature } from "../mod-DUWcVv49.js";
6
- export { CreateProofOptions, CreateSignatureOptions, DoesActorOwnKeyOptions, FetchKeyOptions, FetchKeyResult, GetKeyOwnerOptions, HttpMessageSignaturesSpec, HttpMessageSignaturesSpecDeterminer, KeyCache, SignJsonLdOptions, SignObjectOptions, SignRequestOptions, VerifyJsonLdOptions, VerifyObjectOptions, VerifyProofOptions, VerifyRequestOptions, VerifySignatureOptions, attachSignature, createProof, createSignature, detachSignature, doesActorOwnKey, exportJwk, fetchKey, generateCryptoKeyPair, getKeyOwner, importJwk, signJsonLd, signObject, signRequest, verifyJsonLd, verifyObject, verifyProof, verifyRequest, verifySignature };
5
+ import { CreateProofOptions, CreateSignatureOptions, SignJsonLdOptions, SignObjectOptions, VerifyJsonLdOptions, VerifyObjectOptions, VerifyProofOptions, VerifySignatureOptions, attachSignature, createProof, createSignature, detachSignature, signJsonLd, signObject, verifyJsonLd, verifyObject, verifyProof, verifySignature } from "../mod-CvxylbuV.js";
6
+ export { CreateProofOptions, CreateSignatureOptions, DoesActorOwnKeyOptions, FetchKeyDetailedResult, FetchKeyErrorResult, FetchKeyOptions, FetchKeyResult, GetKeyOwnerOptions, HttpMessageSignaturesSpec, HttpMessageSignaturesSpecDeterminer, KeyCache, SignJsonLdOptions, SignObjectOptions, SignRequestOptions, VerifyJsonLdOptions, VerifyObjectOptions, VerifyProofOptions, VerifyRequestDetailedResult, VerifyRequestFailureReason, VerifyRequestOptions, VerifySignatureOptions, attachSignature, createProof, createSignature, detachSignature, doesActorOwnKey, exportJwk, fetchKey, fetchKeyDetailed, generateCryptoKeyPair, getKeyOwner, importJwk, signJsonLd, signObject, signRequest, verifyJsonLd, verifyObject, verifyProof, verifyRequest, verifyRequestDetailed, verifySignature };
package/dist/sig/mod.js CHANGED
@@ -2,8 +2,8 @@
2
2
  import { Temporal } from "@js-temporal/polyfill";
3
3
  import { URLPattern } from "urlpattern-polyfill";
4
4
 
5
- import { exportJwk, fetchKey, generateCryptoKeyPair, importJwk, signRequest, verifyRequest } from "../http-CSX1-Mgi.js";
6
- import { attachSignature, createProof, createSignature, detachSignature, doesActorOwnKey, getKeyOwner, signJsonLd, signObject, verifyJsonLd, verifyObject, verifyProof, verifySignature } from "../proof-BgUVmaJz.js";
5
+ import { exportJwk, fetchKey, fetchKeyDetailed, generateCryptoKeyPair, importJwk, signRequest, verifyRequest, verifyRequestDetailed } from "../http-CoM8qFZV.js";
6
+ import { attachSignature, createProof, createSignature, detachSignature, doesActorOwnKey, getKeyOwner, signJsonLd, signObject, verifyJsonLd, verifyObject, verifyProof, verifySignature } from "../proof-CQfh4S5r.js";
7
7
  import "../sig-BNhspNOf.js";
8
8
 
9
- export { attachSignature, createProof, createSignature, detachSignature, doesActorOwnKey, exportJwk, fetchKey, generateCryptoKeyPair, getKeyOwner, importJwk, signJsonLd, signObject, signRequest, verifyJsonLd, verifyObject, verifyProof, verifyRequest, verifySignature };
9
+ export { attachSignature, createProof, createSignature, detachSignature, doesActorOwnKey, exportJwk, fetchKey, fetchKeyDetailed, generateCryptoKeyPair, getKeyOwner, importJwk, signJsonLd, signObject, signRequest, verifyJsonLd, verifyObject, verifyProof, verifyRequest, verifyRequestDetailed, verifySignature };
@@ -7,9 +7,9 @@ import { createTestTracerProvider, mockDocumentLoader, test } from "../dist-B5f6
7
7
  import { assertEquals } from "../assert_equals-DSbWqCm3.js";
8
8
  import { assert } from "../assert-MZs1qjMx.js";
9
9
  import "../assert_instance_of-DHz7EHNU.js";
10
- import "../deno-BYerLnry.js";
11
- import "../key-DCdTVZiK.js";
12
- import { doesActorOwnKey, getKeyOwner } from "../owner-BAlnLKMO.js";
10
+ import "../deno-aj03PJW6.js";
11
+ import "../key-j1AjF3HL.js";
12
+ import { doesActorOwnKey, getKeyOwner } from "../owner-ZjRdCtVA.js";
13
13
  import "../std__assert-DWivtrGR.js";
14
14
  import { assertFalse } from "../assert_rejects-Ce45JcFg.js";
15
15
  import "../assert_throws-BNXdRGWP.js";
@@ -7,9 +7,9 @@ import { mockDocumentLoader, test } from "../dist-B5f6a8Tt.js";
7
7
  import { assertEquals } from "../assert_equals-DSbWqCm3.js";
8
8
  import "../assert-MZs1qjMx.js";
9
9
  import { assertInstanceOf } from "../assert_instance_of-DHz7EHNU.js";
10
- import "../deno-BYerLnry.js";
11
- import "../key-DCdTVZiK.js";
12
- import { createProof, signObject, verifyObject, verifyProof } from "../proof-DMgHaXNJ.js";
10
+ import "../deno-aj03PJW6.js";
11
+ import "../key-j1AjF3HL.js";
12
+ import { createProof, signObject, verifyObject, verifyProof } from "../proof-gmdzyEsv.js";
13
13
  import "../std__assert-DWivtrGR.js";
14
14
  import { assertRejects } from "../assert_rejects-Ce45JcFg.js";
15
15
  import "../assert_throws-BNXdRGWP.js";
@@ -184,6 +184,24 @@ interface GetNodeInfoOptions {
184
184
  * Otherwise, `undefined` is returned.
185
185
  * @since 1.2.0
186
186
  */
187
+
188
+ //#endregion
189
+ //#region src/sig/key.d.ts
190
+
191
+ /**
192
+ * Detailed fetch failure information from {@link fetchKeyDetailed}.
193
+ * @since 2.1.0
194
+ */
195
+ type FetchKeyErrorResult = {
196
+ readonly status: number;
197
+ readonly response: Response;
198
+ } | {
199
+ readonly error: Error;
200
+ };
201
+ /**
202
+ * The result of {@link fetchKeyDetailed}.
203
+ * @since 2.1.0
204
+ */
187
205
  //#endregion
188
206
  //#region src/sig/owner.d.ts
189
207
  /**
@@ -216,6 +234,28 @@ interface GetKeyOwnerOptions {
216
234
  * owner.
217
235
  * @since 0.7.0
218
236
  */
237
+
238
+ //#endregion
239
+ //#region src/sig/http.d.ts
240
+
241
+ /**
242
+ * The reason why {@link verifyRequestDetailed} could not verify a request.
243
+ * @since 2.1.0
244
+ */
245
+ type VerifyRequestFailureReason = {
246
+ readonly type: "keyFetchError";
247
+ readonly keyId: URL;
248
+ readonly result: FetchKeyErrorResult;
249
+ } | {
250
+ readonly type: "invalidSignature";
251
+ readonly keyId?: URL;
252
+ } | {
253
+ readonly type: "noSignature";
254
+ };
255
+ /**
256
+ * The detailed result of {@link verifyRequestDetailed}.
257
+ * @since 2.1.0
258
+ */
219
259
  //#endregion
220
260
  //#region src/federation/collection.d.ts
221
261
  /**
@@ -406,6 +446,29 @@ type CollectionCursor<TContext extends Context<TContextData>, TContextData, TFil
406
446
  * @param activity The activity that was received.
407
447
  */
408
448
  type InboxListener<TContextData, TActivity extends Activity> = (context: InboxContext<TContextData>, activity: TActivity) => void | Promise<void>;
449
+ /**
450
+ * The reason why an incoming activity could not be verified.
451
+ *
452
+ * Unlike inbox listeners registered through {@link InboxListenerSetters.on},
453
+ * unverified activity handlers are called only when the activity payload could
454
+ * be parsed but its HTTP signatures could not be verified.
455
+ *
456
+ * @since 2.1.0
457
+ */
458
+ type UnverifiedActivityReason = VerifyRequestFailureReason;
459
+ /**
460
+ * A callback that handles activities whose signatures could not be verified.
461
+ *
462
+ * Returning a {@link Response} overrides Fedify's default `401 Unauthorized`
463
+ * response. Returning `void` keeps the default behavior.
464
+ *
465
+ * @template TContextData The context data to pass to the {@link Context}.
466
+ * @param context The request context.
467
+ * @param activity The incoming activity that could be parsed.
468
+ * @param reason The reason why signature verification failed.
469
+ * @since 2.1.0
470
+ */
471
+ type UnverifiedActivityHandler<TContextData> = (context: RequestContext<TContextData>, activity: Activity, reason: UnverifiedActivityReason) => void | Response | Promise<void | Response>;
409
472
  /**
410
473
  * A callback that handles errors in an inbox.
411
474
  *
@@ -1198,6 +1261,35 @@ interface InboxListenerSetters<TContextData> {
1198
1261
  * @returns The setters object so that settings can be chained.
1199
1262
  */
1200
1263
  onError(handler: InboxErrorHandler<TContextData>): InboxListenerSetters<TContextData>;
1264
+ /**
1265
+ * Registers a callback for incoming activities whose HTTP signatures could
1266
+ * not be verified.
1267
+ *
1268
+ * The regular inbox listeners registered through {@link on} continue to
1269
+ * receive only verified activities. This hook is an opt-in escape hatch for
1270
+ * applications that need to inspect unverified deliveries and optionally
1271
+ * override the default `401 Unauthorized` response.
1272
+ *
1273
+ * @example
1274
+ * ``` typescript
1275
+ * federation
1276
+ * .setInboxListeners("/users/{identifier}/inbox", "/inbox")
1277
+ * .onUnverifiedActivity((ctx, activity, reason) => {
1278
+ * if (
1279
+ * reason.type === "keyFetchError" &&
1280
+ * "status" in reason.result &&
1281
+ * reason.result.status === 410
1282
+ * ) {
1283
+ * return new Response(null, { status: 202 });
1284
+ * }
1285
+ * });
1286
+ * ```
1287
+ *
1288
+ * @param handler A callback to handle an unverified activity.
1289
+ * @returns The setters object so that settings can be chained.
1290
+ * @since 2.1.0
1291
+ */
1292
+ onUnverifiedActivity(handler: UnverifiedActivityHandler<TContextData>): InboxListenerSetters<TContextData>;
1201
1293
  /**
1202
1294
  * Configures a callback to dispatch the key pair for the authenticated
1203
1295
  * document loader of the {@link Context} passed to the shared inbox listener.
@@ -7,10 +7,10 @@ import { mockDocumentLoader, test } from "../dist-B5f6a8Tt.js";
7
7
  import { assertEquals } from "../assert_equals-DSbWqCm3.js";
8
8
  import "../assert-MZs1qjMx.js";
9
9
  import "../assert_instance_of-DHz7EHNU.js";
10
- import "../deno-BYerLnry.js";
11
- import "../key-DCdTVZiK.js";
12
- import { verifyRequest } from "../http-S2U3qDwN.js";
13
- import { getAuthenticatedDocumentLoader } from "../docloader-MSkogD2T.js";
10
+ import "../deno-aj03PJW6.js";
11
+ import "../key-j1AjF3HL.js";
12
+ import { verifyRequest } from "../http-pa5yLKK7.js";
13
+ import { getAuthenticatedDocumentLoader } from "../docloader-tMQxWuTM.js";
14
14
  import "../std__assert-DWivtrGR.js";
15
15
  import { assertRejects } from "../assert_rejects-Ce45JcFg.js";
16
16
  import "../assert_throws-BNXdRGWP.js";
@@ -2,8 +2,8 @@
2
2
  const { Temporal } = require("@js-temporal/polyfill");
3
3
  const { URLPattern } = require("urlpattern-polyfill");
4
4
 
5
- require('../http-DJT6NciB.cjs');
6
- const require_kv_cache = require('../kv-cache-Vtxhbo1W.cjs');
5
+ require('../http-_l2XRk4S.cjs');
6
+ const require_kv_cache = require('../kv-cache-D0IkOVYm.cjs');
7
7
  require('../utils-BQ9KqEK9.cjs');
8
8
 
9
9
  exports.getAuthenticatedDocumentLoader = require_kv_cache.getAuthenticatedDocumentLoader;
@@ -1,4 +1,4 @@
1
- import "../http-Cz3MlXAZ.cjs";
1
+ import "../http-DsqqmkXi.cjs";
2
2
  import "../kv-BL4nlICN.cjs";
3
- import { getAuthenticatedDocumentLoader, kvCache } from "../mod-DXsQakeS.cjs";
3
+ import { getAuthenticatedDocumentLoader, kvCache } from "../mod-DKG0ovjR.cjs";
4
4
  export { getAuthenticatedDocumentLoader, kvCache };
@@ -1,6 +1,6 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
- import "../http-DkHdFfrc.js";
3
+ import "../http-BbfOqHGG.js";
4
4
  import "../kv-DXEUEP6z.js";
5
- import { getAuthenticatedDocumentLoader, kvCache } from "../mod-CwZXZJ9d.js";
5
+ import { getAuthenticatedDocumentLoader, kvCache } from "../mod-BugwI0JN.js";
6
6
  export { getAuthenticatedDocumentLoader, kvCache };
package/dist/utils/mod.js CHANGED
@@ -2,8 +2,8 @@
2
2
  import { Temporal } from "@js-temporal/polyfill";
3
3
  import { URLPattern } from "urlpattern-polyfill";
4
4
 
5
- import "../http-CSX1-Mgi.js";
6
- import { getAuthenticatedDocumentLoader, kvCache } from "../kv-cache-CQPL_aGY.js";
5
+ import "../http-CoM8qFZV.js";
6
+ import { getAuthenticatedDocumentLoader, kvCache } from "../kv-cache-CMk-kfWp.js";
7
7
  import "../utils-Dn5OPdSW.js";
8
8
 
9
9
  export { getAuthenticatedDocumentLoader, kvCache };