@fedify/fedify 2.4.0-dev.2200 → 2.4.0-dev.2212

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 (95) hide show
  1. package/dist/audience-CHQ-FlLD.mjs +95 -0
  2. package/dist/{builder-BGSNMnVH.mjs → builder-CKAVFvcH.mjs} +2 -2
  3. package/dist/compat/mod.d.cts +1 -1
  4. package/dist/compat/mod.d.ts +1 -1
  5. package/dist/compat/public-audience.test.mjs +1 -1
  6. package/dist/compat/transformers.test.mjs +1 -1
  7. package/dist/{compound-proof-Zpg7Fbk9.mjs → compound-proof-BP5SOdXv.mjs} +2 -2
  8. package/dist/{context-B2rlkkyJ.mjs → context-8Tu9kea0.mjs} +2 -0
  9. package/dist/{context-CDcAgm4l.d.cts → context-Bl_Y7T_3.d.cts} +80 -1
  10. package/dist/{context-9T--NSo4.d.ts → context-DMd11IAu.d.ts} +80 -1
  11. package/dist/{deno-CxoV48e0.mjs → deno-BKp9fYB3.mjs} +1 -1
  12. package/dist/{docloader-U141HKIl.mjs → docloader-BjJreYQU.mjs} +3 -3
  13. package/dist/federation/audience.test.d.mts +2 -0
  14. package/dist/federation/audience.test.mjs +130 -0
  15. package/dist/federation/builder.test.mjs +1 -1
  16. package/dist/federation/compound-fanout.test.mjs +4 -4
  17. package/dist/federation/handler-compound-proof.test.mjs +5 -5
  18. package/dist/federation/handler.test.mjs +7 -7
  19. package/dist/federation/hashlink-media.test.mjs +3 -3
  20. package/dist/federation/idempotency.test.mjs +2 -2
  21. package/dist/federation/metrics.test.mjs +2 -2
  22. package/dist/federation/middleware.test.mjs +9 -9
  23. package/dist/federation/mod.cjs +1 -1
  24. package/dist/federation/mod.d.cts +3 -3
  25. package/dist/federation/mod.d.ts +3 -3
  26. package/dist/federation/mod.js +1 -1
  27. package/dist/federation/outgoing-proof.test.mjs +6 -6
  28. package/dist/federation/portable-actor.test.mjs +4 -4
  29. package/dist/federation/portable-chain.test.mjs +4 -4
  30. package/dist/federation/portable-collection.test.mjs +8 -6
  31. package/dist/federation/portable-delivery.test.d.mts +2 -0
  32. package/dist/federation/portable-delivery.test.mjs +746 -0
  33. package/dist/federation/portable-gateway-keys.test.mjs +6 -6
  34. package/dist/federation/portable-inbox.test.mjs +5 -5
  35. package/dist/federation/portable-parse-uri.test.mjs +2 -2
  36. package/dist/federation/portable-route-activity.test.mjs +3 -3
  37. package/dist/federation/portable.test.mjs +234 -19
  38. package/dist/federation/send.test.mjs +4 -4
  39. package/dist/federation/tasks/enqueue.test.mjs +2 -2
  40. package/dist/federation/tasks/tasks.test.mjs +3 -3
  41. package/dist/federation/temporal.test.mjs +1 -1
  42. package/dist/federation/webfinger.test.mjs +3 -3
  43. package/dist/{federation-Ij8VLHpy.cjs → federation-B-0EXZPH.cjs} +679 -35
  44. package/dist/{http-BQq1Uldg.mjs → http--IGUT1mg.mjs} +5 -5
  45. package/dist/{http-D8XakCKn.cjs → http-ByBlnY3D.cjs} +1 -1
  46. package/dist/{http-CshNXXui.js → http-Di8yiDMY.js} +1 -1
  47. package/dist/{key-DYgoirTo.mjs → key-ChfV9C3v.mjs} +2 -2
  48. package/dist/{kv-cache-f_GNq0e7.cjs → kv-cache-BDwUzxBJ.cjs} +1 -1
  49. package/dist/{kv-cache-D0Mn-VQW.mjs → kv-cache-DFIUR-E5.mjs} +1 -1
  50. package/dist/{kv-cache-Bpnt_Cck.js → kv-cache-DpwDPt9V.js} +1 -1
  51. package/dist/{ld-eIekvNtm.mjs → ld-DBBVabBC.mjs} +3 -3
  52. package/dist/{metrics-BVpdjsaR.mjs → metrics-DPQKFkiJ.mjs} +1 -1
  53. package/dist/{middleware-BbTJ6C1-.mjs → middleware-BW7-tHAG.mjs} +422 -44
  54. package/dist/{middleware-rpYJG85a.mjs → middleware-G9zpxUsD.mjs} +1 -1
  55. package/dist/{middleware-CS3H_4tQ.js → middleware-VXGLiHtY.js} +681 -37
  56. package/dist/{mod-B3OBLj5g.d.ts → mod-BVNGHgVr.d.ts} +1 -1
  57. package/dist/{mod-D58KCqUg.d.cts → mod-Cyy1Wahy.d.cts} +1 -1
  58. package/dist/mod.cjs +4 -4
  59. package/dist/mod.d.cts +4 -4
  60. package/dist/mod.d.ts +4 -4
  61. package/dist/mod.js +4 -4
  62. package/dist/nodeinfo/client.test.mjs +1 -1
  63. package/dist/nodeinfo/handler.test.mjs +2 -2
  64. package/dist/{owner-DpxyMeo9.mjs → owner-BW5VsXG3.mjs} +3 -3
  65. package/dist/{portable-collection-DyJwtPz1.cjs → portable-collection-CXcAlwei.cjs} +1 -1
  66. package/dist/{portable-collection-DOL3l2C9.mjs → portable-collection-Ct99JsBe.mjs} +2 -2
  67. package/dist/{portable-collection-BvJ5om91.js → portable-collection-FTPa5D91.js} +1 -1
  68. package/dist/{portable-key-B8kTV4n8.mjs → portable-key-U3lW06QJ.mjs} +1 -1
  69. package/dist/{proof-B9DEHHiO.mjs → proof-MFz2960e.mjs} +4 -4
  70. package/dist/{send-CZckda2X.mjs → send-Duj8xys1.mjs} +191 -10
  71. package/dist/sig/compound-proof-discovery.test.mjs +1 -1
  72. package/dist/sig/compound-proof-producer.test.mjs +1 -1
  73. package/dist/sig/compound-proof-verification.test.mjs +3 -3
  74. package/dist/sig/compound-proof.test.mjs +3 -3
  75. package/dist/sig/http.test.mjs +3 -3
  76. package/dist/sig/key-owner-evidence.test.mjs +2 -2
  77. package/dist/sig/key.test.mjs +2 -2
  78. package/dist/sig/ld.test.mjs +2 -2
  79. package/dist/sig/mod.cjs +2 -2
  80. package/dist/sig/mod.js +2 -2
  81. package/dist/sig/owner.test.mjs +1 -1
  82. package/dist/sig/portable-collection.test.mjs +2 -2
  83. package/dist/sig/portable-key-id.test.mjs +2 -2
  84. package/dist/sig/portable-key.test.mjs +7 -7
  85. package/dist/sig/proof.test.mjs +4 -4
  86. package/dist/{temporal-Cc1ymEup.mjs → temporal-CVcPKB1N.mjs} +1 -1
  87. package/dist/testing/kv.test.mjs +1 -1
  88. package/dist/testing/mod.d.mts +79 -0
  89. package/dist/testing/mod.mjs +2 -2
  90. package/dist/{testing-C1itIjqW.mjs → testing-t8xDeJZ5.mjs} +1 -1
  91. package/dist/utils/docloader.test.mjs +6 -6
  92. package/dist/utils/kv-cache.test.mjs +2 -2
  93. package/dist/utils/mod.cjs +1 -1
  94. package/dist/utils/mod.js +1 -1
  95. package/package.json +6 -6
@@ -2,22 +2,22 @@ import { Temporal } from "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as esm_default } from "../esm-BHXXx4Wx.mjs";
5
- import { r as createRequestContext, t as createInboxContext } from "../context-B2rlkkyJ.mjs";
5
+ import { r as createRequestContext, t as createInboxContext } from "../context-8Tu9kea0.mjs";
6
6
  import { t as MemoryKvStore } from "../kv-DCHkaJ9-.mjs";
7
7
  import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
8
8
  import "../std__assert-DvMDRPes.mjs";
9
9
  import { t as assertRejects } from "../assert_rejects-CTptdYck.mjs";
10
10
  import { r as getVerifiedKeyOwnerEvidence } from "../key-owner-evidence-APB6NpSk.mjs";
11
- import { p as signJsonLd } from "../ld-eIekvNtm.mjs";
12
- import { f as verifyRequest, l as signRequest, p as verifyRequestDetailed } from "../http-BQq1Uldg.mjs";
11
+ import { p as signJsonLd } from "../ld-DBBVabBC.mjs";
12
+ import { f as verifyRequest, l as signRequest, p as verifyRequestDetailed } from "../http--IGUT1mg.mjs";
13
13
  import { a as rsaPrivateKey3, i as rsaPrivateKey2, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2 } from "../keys-DIJNHW-Y.mjs";
14
- import { f as handleInbox, n as FederationImpl, o as createFederation } from "../middleware-BbTJ6C1-.mjs";
15
- import { n as getKeyOwner, t as doesActorOwnKey } from "../owner-DpxyMeo9.mjs";
14
+ import { f as handleInbox, n as FederationImpl, o as createFederation } from "../middleware-BW7-tHAG.mjs";
15
+ import { n as getKeyOwner, t as doesActorOwnKey } from "../owner-BW5VsXG3.mjs";
16
16
  import { t as ActivityListenerSet } from "../activity-listener-C-BSbKI7.mjs";
17
17
  import { t as KvKeyCache } from "../keycache-Da8A8mZa.mjs";
18
18
  import { Create, CryptographicKey, Person } from "@fedify/vocab";
19
- import { mockDocumentLoader, test } from "@fedify/fixture";
20
19
  import { FetchError, encodeMultibase, exportDidKey, parseIri } from "@fedify/vocab-runtime";
20
+ import { mockDocumentLoader, test } from "@fedify/fixture";
21
21
  import serialize from "json-canon";
22
22
  //#region src/federation/portable-gateway-keys.test.ts
23
23
  const did = await exportDidKey(ed25519PublicKey.publicKey);
@@ -8,14 +8,14 @@ import { t as assertFalse } from "../assert_false-CPdRnc3E.mjs";
8
8
  import { t as assertThrows } from "../assert_throws-BYITOvPO.mjs";
9
9
  import "../std__assert-DvMDRPes.mjs";
10
10
  import { t as assert } from "../assert-D1Eafmd_.mjs";
11
- import { p as signJsonLd } from "../ld-eIekvNtm.mjs";
12
- import { a as signObject } from "../proof-B9DEHHiO.mjs";
13
- import { l as signRequest, p as verifyRequestDetailed } from "../http-BQq1Uldg.mjs";
11
+ import { p as signJsonLd } from "../ld-DBBVabBC.mjs";
12
+ import { a as signObject } from "../proof-MFz2960e.mjs";
13
+ import { l as signRequest, p as verifyRequestDetailed } from "../http--IGUT1mg.mjs";
14
14
  import { a as rsaPrivateKey3, c as rsaPublicKey3, i as rsaPrivateKey2, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2 } from "../keys-DIJNHW-Y.mjs";
15
- import { _ as coversDelivery, o as createFederation, v as forwardPortableInboxActivity, y as resolvePortableInboxForwardingOptions } from "../middleware-BbTJ6C1-.mjs";
15
+ import { _ as coversDelivery, o as createFederation, v as forwardPortableInboxActivity, y as resolvePortableInboxForwardingOptions } from "../middleware-BW7-tHAG.mjs";
16
16
  import { Create, CryptographicKey, Follow, Note, Person, Tombstone } from "@fedify/vocab";
17
- import { mockDocumentLoader, test } from "@fedify/fixture";
18
17
  import { exportDidKey, formatIri, parseIri } from "@fedify/vocab-runtime";
18
+ import { mockDocumentLoader, test } from "@fedify/fixture";
19
19
  import { encodeBase64 } from "byte-encodings/base64";
20
20
  //#region src/federation/portable-inbox.test.ts
21
21
  const did = await exportDidKey(ed25519PublicKey.publicKey);
@@ -7,10 +7,10 @@ import { t as assertFalse } from "../assert_false-CPdRnc3E.mjs";
7
7
  import "../std__assert-DvMDRPes.mjs";
8
8
  import { t as assert } from "../assert-D1Eafmd_.mjs";
9
9
  import { r as ed25519PublicKey } from "../keys-DIJNHW-Y.mjs";
10
- import { o as createFederation } from "../middleware-BbTJ6C1-.mjs";
10
+ import { o as createFederation } from "../middleware-BW7-tHAG.mjs";
11
11
  import { Note, Object as Object$1, Person } from "@fedify/vocab";
12
- import { test } from "@fedify/fixture";
13
12
  import { exportDidKey, parseIri } from "@fedify/vocab-runtime";
13
+ import { test } from "@fedify/fixture";
14
14
  //#region src/federation/portable-parse-uri.test.ts
15
15
  const did = await exportDidKey(ed25519PublicKey.publicKey);
16
16
  const otherDid = await exportDidKey((await crypto.subtle.generateKey("Ed25519", true, ["sign", "verify"])).publicKey);
@@ -6,12 +6,12 @@ import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
6
6
  import { t as assertFalse } from "../assert_false-CPdRnc3E.mjs";
7
7
  import "../std__assert-DvMDRPes.mjs";
8
8
  import { t as assert } from "../assert-D1Eafmd_.mjs";
9
- import { a as signObject } from "../proof-B9DEHHiO.mjs";
9
+ import { a as signObject } from "../proof-MFz2960e.mjs";
10
10
  import { n as ed25519PrivateKey, r as ed25519PublicKey } from "../keys-DIJNHW-Y.mjs";
11
- import { o as createFederation } from "../middleware-BbTJ6C1-.mjs";
11
+ import { o as createFederation } from "../middleware-BW7-tHAG.mjs";
12
12
  import { Create, Note } from "@fedify/vocab";
13
- import { mockDocumentLoader, test } from "@fedify/fixture";
14
13
  import { exportDidKey, formatIri, parseIri, toCompatibleEf61Id } from "@fedify/vocab-runtime";
14
+ import { mockDocumentLoader, test } from "@fedify/fixture";
15
15
  //#region src/federation/portable-route-activity.test.ts
16
16
  const did = await exportDidKey(ed25519PublicKey.publicKey);
17
17
  const keyId = new URL(`${did}#${did.slice(8)}`);
@@ -8,14 +8,14 @@ import { t as assertThrows } from "../assert_throws-BYITOvPO.mjs";
8
8
  import "../std__assert-DvMDRPes.mjs";
9
9
  import { t as assertStrictEquals } from "../assert_strict_equals-CYvBmkpC.mjs";
10
10
  import { t as assert } from "../assert-D1Eafmd_.mjs";
11
- import { a as signObject, c as verifyPortableObjectProof } from "../proof-B9DEHHiO.mjs";
12
- import { l as signRequest } from "../http-BQq1Uldg.mjs";
11
+ import { a as signObject, c as verifyPortableObjectProof } from "../proof-MFz2960e.mjs";
12
+ import { l as signRequest } from "../http--IGUT1mg.mjs";
13
13
  import { a as rsaPrivateKey3, c as rsaPublicKey3, i as rsaPrivateKey2, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2 } from "../keys-DIJNHW-Y.mjs";
14
- import { b as PORTABLE_OBJECT_CONTENT_TYPE, o as createFederation } from "../middleware-BbTJ6C1-.mjs";
14
+ import { b as PORTABLE_OBJECT_CONTENT_TYPE, o as createFederation } from "../middleware-BW7-tHAG.mjs";
15
15
  import { configure, reset } from "@logtape/logtape";
16
- import { Collection, Create, Note, Object as Object$1, Person, Tombstone, lookupObject } from "@fedify/vocab";
17
- import { mockDocumentLoader, test } from "@fedify/fixture";
16
+ import { Collection, Create, Note, Object as Object$1, PUBLIC_COLLECTION, Person, Tombstone, lookupObject } from "@fedify/vocab";
18
17
  import { exportDidKey, formatIri, parseIri, toCompatibleEf61Id } from "@fedify/vocab-runtime";
18
+ import { mockDocumentLoader, test } from "@fedify/fixture";
19
19
  //#region src/federation/portable.test.ts
20
20
  const did = await exportDidKey(ed25519PublicKey.publicKey);
21
21
  const keyId = new URL(`${did}#${did.substring(8)}`);
@@ -50,6 +50,7 @@ async function signedNote(path, authority = did, content = "Hello") {
50
50
  return await sign(new Note({
51
51
  id: parseIri(`ap+ef61://${authority}${path}`),
52
52
  attribution: parseIri(`ap+ef61://${authority}/actor`),
53
+ to: PUBLIC_COLLECTION,
53
54
  content
54
55
  }));
55
56
  }
@@ -68,6 +69,7 @@ test("Federation.fetch() serves portable objects through object dispatchers", as
68
69
  });
69
70
  if (values.noteId === "ap") return await sign(new Note({
70
71
  id: parseIri(`ap://${did}/users/${values.userId}/notes/ap`),
72
+ to: PUBLIC_COLLECTION,
71
73
  content: "Hello"
72
74
  }));
73
75
  if (values.noteId === "ignores-authority") return await signedNote(`/users/${values.userId}/notes/${values.noteId}`);
@@ -75,6 +77,7 @@ test("Federation.fetch() serves portable objects through object dispatchers", as
75
77
  return await sign(new Note({
76
78
  id: ctx.getPortableObjectUri(Note, values),
77
79
  attribution: parseIri(`ap+ef61://${did}/actor`),
80
+ to: PUBLIC_COLLECTION,
78
81
  content: `Note ${values.noteId} by ${values.userId}`
79
82
  }));
80
83
  });
@@ -227,7 +230,7 @@ test("Federation.fetch() applies the FEP-ef61 proof policy to portable objects",
227
230
  gateways: [new URL("https://example.com/")]
228
231
  }))
229
232
  };
230
- federation.setObjectDispatcher(Object$1, "/objects/{id}", (_ctx, { id }) => objects[id]?.() ?? null);
233
+ federation.setObjectDispatcher(Object$1, "/objects/{id}", (_ctx, { id }) => objects[id]?.() ?? null).authorize(() => true);
231
234
  for (const [id, status] of globalThis.Object.entries({
232
235
  "valid": 200,
233
236
  "unsigned": 500,
@@ -408,22 +411,228 @@ test("Federation.fetch() authorizes portable object requests", async (t) => {
408
411
  assertEquals(dispatched, 1);
409
412
  });
410
413
  });
414
+ test("Federation.fetch() serves only public portable objects by default", async (t) => {
415
+ const federation = createTestFederation();
416
+ const person2 = new URL("https://example.com/person2");
417
+ const note = (id, values = {}) => sign(new Note({
418
+ id: parseIri(`ap+ef61://${did}/objects/${id}`),
419
+ ...values
420
+ }));
421
+ const objects = {
422
+ "to": () => note("to", { to: PUBLIC_COLLECTION }),
423
+ "cc": () => note("cc", {
424
+ to: person2,
425
+ cc: PUBLIC_COLLECTION
426
+ }),
427
+ "bto": () => note("bto", { bto: PUBLIC_COLLECTION }),
428
+ "bcc": () => note("bcc", { bcc: PUBLIC_COLLECTION }),
429
+ "audience": () => note("audience", { audience: PUBLIC_COLLECTION }),
430
+ "unaddressed": () => note("unaddressed"),
431
+ "direct": () => note("direct", { to: person2 }),
432
+ "followers": () => note("followers", { cc: new URL("https://example.com/followers") }),
433
+ "wrong-did": () => sign(new Note({
434
+ id: parseIri(`ap+ef61://${did}/objects/wrong-did`),
435
+ to: person2
436
+ }), otherKeyPair.privateKey, otherKeyId),
437
+ "public-create-of-private-note": async () => await sign(new Create({
438
+ id: parseIri(`ap+ef61://${did}/objects/public-create-of-private-note`),
439
+ actor: parseIri(`ap+ef61://${did}/actor`),
440
+ to: PUBLIC_COLLECTION,
441
+ object: await note("private-note", { to: person2 })
442
+ })),
443
+ "private-create-of-public-note": async () => await sign(new Create({
444
+ id: parseIri(`ap+ef61://${did}/objects/private-create-of-public-note`),
445
+ actor: parseIri(`ap+ef61://${did}/actor`),
446
+ object: await note("public-note", { to: PUBLIC_COLLECTION })
447
+ })),
448
+ "collection": () => Promise.resolve(new Collection({
449
+ id: parseIri(`ap+ef61://${did}/objects/collection`),
450
+ totalItems: 0
451
+ })),
452
+ "public-collection": () => Promise.resolve(new Collection({
453
+ id: parseIri(`ap+ef61://${did}/objects/public-collection`),
454
+ to: PUBLIC_COLLECTION,
455
+ totalItems: 0
456
+ })),
457
+ "tombstone": () => sign(new Tombstone({ id: parseIri(`ap+ef61://${did}/objects/tombstone`) })),
458
+ "public-tombstone": () => sign(new Tombstone({
459
+ id: parseIri(`ap+ef61://${did}/objects/public-tombstone`),
460
+ to: PUBLIC_COLLECTION
461
+ })),
462
+ "actor": () => sign(new Person({
463
+ id: parseIri(`ap+ef61://${did}/objects/actor`),
464
+ inbox: parseIri(`ap+ef61://${did}/objects/actor/inbox`),
465
+ outbox: parseIri(`ap+ef61://${did}/objects/actor/outbox`),
466
+ gateways: [new URL("https://example.com/")]
467
+ })),
468
+ "mutated-audience": async () => {
469
+ const signed = await note("mutated-audience", { to: person2 });
470
+ const object = await Note.fromJsonLd(await signed.toJsonLd({ contextLoader: mockDocumentLoader }), {
471
+ contextLoader: mockDocumentLoader,
472
+ documentLoader: mockDocumentLoader
473
+ });
474
+ object.toIds[0].href = PUBLIC_COLLECTION.href;
475
+ return object;
476
+ }
477
+ };
478
+ let dispatched = 0;
479
+ federation.setObjectDispatcher(Object$1, "/objects/{id}", (ctx, { id }) => {
480
+ dispatched++;
481
+ if (ctx.portableRequest == null) return new Note({
482
+ id: ctx.getObjectUri(Object$1, { id }),
483
+ to: person2
484
+ });
485
+ return objects[id]?.() ?? null;
486
+ });
487
+ for (const [id, status] of globalThis.Object.entries({
488
+ "to": 200,
489
+ "cc": 200,
490
+ "bto": 200,
491
+ "bcc": 200,
492
+ "audience": 200,
493
+ "unaddressed": 404,
494
+ "direct": 404,
495
+ "followers": 404,
496
+ "wrong-did": 404,
497
+ "public-create-of-private-note": 200,
498
+ "private-create-of-public-note": 404,
499
+ "collection": 404,
500
+ "public-collection": 200,
501
+ "tombstone": 404,
502
+ "public-tombstone": 410,
503
+ "actor": 200,
504
+ "mutated-audience": 404
505
+ })) await t.step(`${id} → ${status}`, async () => {
506
+ for (const method of ["GET", "HEAD"]) {
507
+ dispatched = 0;
508
+ let response;
509
+ const records = await captureWarnings(async () => {
510
+ response = await federation.fetch(gatewayRequest(`/objects/${id}`, { method }), { contextData: void 0 });
511
+ });
512
+ assertEquals(response?.status, status, method);
513
+ assertEquals(dispatched, 1);
514
+ assertEquals(response?.headers.get("Cache-Control"), null);
515
+ assertEquals(records.filter((r) => r.level === "error").map((r) => r.rawMessage), []);
516
+ if (method === "HEAD") assertEquals(response?.body, null);
517
+ }
518
+ });
519
+ await t.step("responds to a denied request with onNotFound()", async () => {
520
+ let notFound = false;
521
+ assertEquals((await federation.fetch(gatewayRequest("/objects/direct"), {
522
+ contextData: void 0,
523
+ onNotFound() {
524
+ notFound = true;
525
+ return new Response("Not found", { status: 404 });
526
+ }
527
+ })).status, 404);
528
+ assertEquals(notFound, true);
529
+ });
530
+ await t.step("keeps ordinary object requests unchanged", async () => {
531
+ assertEquals((await federation.fetch(new Request("https://example.com/objects/direct", { headers: { Accept: ACCEPT } }), { contextData: void 0 })).status, 200);
532
+ });
533
+ });
534
+ test("Federation.fetch() serves non-public portable objects to their audience", async (t) => {
535
+ const federation = createTestFederation();
536
+ const person2 = new URL("https://example.com/person2");
537
+ const followers = new URL("https://example.com/users/alice/followers");
538
+ const notes = {
539
+ direct: sign(new Note({
540
+ id: parseIri(`ap+ef61://${did}/notes/direct`),
541
+ to: person2
542
+ })),
543
+ followers: sign(new Note({
544
+ id: parseIri(`ap+ef61://${did}/notes/followers`),
545
+ cc: followers
546
+ })),
547
+ public: signedNote("/notes/public"),
548
+ other: sign(new Note({
549
+ id: parseIri(`ap+ef61://${did}/notes/other`),
550
+ to: new URL("https://example.com/other")
551
+ }))
552
+ };
553
+ federation.setObjectDispatcher(Note, "/notes/{id}", (_ctx, values) => notes[values.id] ?? null).authorize(async (ctx, values) => {
554
+ const note = await notes[values.id];
555
+ return note != null && await ctx.isSignedByAudience(note, { isMember: (addressee, actor) => addressee.href === followers.href && actor.id?.href === person2.href });
556
+ });
557
+ const signed = async (id, key = "key3") => await signRequest(gatewayRequest(`/notes/${id}`), key === "key3" ? rsaPrivateKey3 : rsaPrivateKey2, key === "key3" ? rsaPublicKey3.id : rsaPublicKey2.id);
558
+ await t.step("serves a request signed by an addressee", async () => {
559
+ for (const id of ["direct", "followers"]) {
560
+ const response = await federation.fetch(await signed(id), { contextData: void 0 });
561
+ assertEquals(response.status, 200, id);
562
+ assertEquals(response.headers.get("Cache-Control"), "private", id);
563
+ assertEquals((await response.json()).id, `ap+ef61://${did}/notes/${id}`);
564
+ }
565
+ });
566
+ await t.step("denies a request by anyone else", async () => {
567
+ for (const request of [
568
+ gatewayRequest("/notes/direct"),
569
+ await signed("direct", "key2"),
570
+ await signed("followers", "key2"),
571
+ await signed("other")
572
+ ]) assertEquals((await federation.fetch(request, { contextData: void 0 })).status, 401, request.url);
573
+ });
574
+ await t.step("serves a public object to anyone", async () => {
575
+ const response = await federation.fetch(gatewayRequest("/notes/public"), { contextData: void 0 });
576
+ assertEquals(response.status, 200);
577
+ assertEquals(response.headers.get("Cache-Control"), "private");
578
+ });
579
+ await t.step("serves anything if the predicate allows it", async () => {
580
+ const permissive = createTestFederation();
581
+ permissive.setObjectDispatcher(Note, "/notes/{id}", (_ctx, values) => notes[values.id] ?? null).authorize(() => true);
582
+ const response = await permissive.fetch(gatewayRequest("/notes/direct"), { contextData: void 0 });
583
+ assertEquals(response.status, 200);
584
+ assertEquals(response.headers.get("Cache-Control"), "private");
585
+ });
586
+ });
411
587
  test("Federation.fetch() serves portable objects with compatible IDs", async (t) => {
412
588
  const federation = createTestFederation();
413
589
  const compatible = (path, authority = did, origin = "example.com") => new URL(`https://${origin}/.well-known/apgateway/${authority}${path}`);
414
590
  const objects = {
415
- "compatible": () => sign(new Note({ id: compatible("/objects/compatible") })),
416
- "other-gateway": () => sign(new Note({ id: compatible("/objects/other-gateway", did, "other.example") })),
417
- "percent-encoded": () => sign(new Note({ id: compatible("/objects/percent-encoded", did.replaceAll(":", "%3A")) })),
418
- "other-did": () => sign(new Note({ id: compatible("/objects/other-did", otherDid) }), otherKeyPair.privateKey, otherKeyId),
419
- "other-path": () => sign(new Note({ id: compatible("/objects/else") })),
420
- "fragment": () => sign(new Note({ id: compatible("/objects/fragment#note") })),
421
- "malformed": () => sign(new Note({ id: new URL(compatible("/objects/malformed").href + "?@gateway=https%3A%2F%2Fexample.com") })),
422
- "ordinary": () => sign(new Note({ id: new URL("https://example.com/objects/ordinary") })),
423
- "unsigned": () => Promise.resolve(new Note({ id: compatible("/objects/unsigned") })),
424
- "wrong-did": () => sign(new Note({ id: compatible("/objects/wrong-did") }), otherKeyPair.privateKey, otherKeyId),
591
+ "compatible": () => sign(new Note({
592
+ to: PUBLIC_COLLECTION,
593
+ id: compatible("/objects/compatible")
594
+ })),
595
+ "other-gateway": () => sign(new Note({
596
+ to: PUBLIC_COLLECTION,
597
+ id: compatible("/objects/other-gateway", did, "other.example")
598
+ })),
599
+ "percent-encoded": () => sign(new Note({
600
+ to: PUBLIC_COLLECTION,
601
+ id: compatible("/objects/percent-encoded", did.replaceAll(":", "%3A"))
602
+ })),
603
+ "other-did": () => sign(new Note({
604
+ to: PUBLIC_COLLECTION,
605
+ id: compatible("/objects/other-did", otherDid)
606
+ }), otherKeyPair.privateKey, otherKeyId),
607
+ "other-path": () => sign(new Note({
608
+ to: PUBLIC_COLLECTION,
609
+ id: compatible("/objects/else")
610
+ })),
611
+ "fragment": () => sign(new Note({
612
+ to: PUBLIC_COLLECTION,
613
+ id: compatible("/objects/fragment#note")
614
+ })),
615
+ "malformed": () => sign(new Note({
616
+ to: PUBLIC_COLLECTION,
617
+ id: new URL(compatible("/objects/malformed").href + "?@gateway=https%3A%2F%2Fexample.com")
618
+ })),
619
+ "ordinary": () => sign(new Note({
620
+ to: PUBLIC_COLLECTION,
621
+ id: new URL("https://example.com/objects/ordinary")
622
+ })),
623
+ "unsigned": () => Promise.resolve(new Note({
624
+ to: PUBLIC_COLLECTION,
625
+ id: compatible("/objects/unsigned")
626
+ })),
627
+ "wrong-did": () => sign(new Note({
628
+ to: PUBLIC_COLLECTION,
629
+ id: compatible("/objects/wrong-did")
630
+ }), otherKeyPair.privateKey, otherKeyId),
425
631
  "mutated-id": async () => {
426
- const signed = await sign(new Note({ id: compatible("/objects/original") }));
632
+ const signed = await sign(new Note({
633
+ to: PUBLIC_COLLECTION,
634
+ id: compatible("/objects/original")
635
+ }));
427
636
  const object = await Note.fromJsonLd(await signed.toJsonLd({ contextLoader: mockDocumentLoader }), {
428
637
  contextLoader: mockDocumentLoader,
429
638
  documentLoader: mockDocumentLoader
@@ -488,7 +697,10 @@ test("Federation.fetch() lets application routes shadow the gateway", async () =
488
697
  test("Federation.fetch() compares portable IDs with the requested path", async () => {
489
698
  const federation = createTestFederation();
490
699
  federation.setObjectDispatcher(Note, "/notes/{id}", async (ctx, values) => {
491
- if (values.id === "a/b") return await sign(new Note({ id: ctx.getPortableObjectUri(Note, values) }));
700
+ if (values.id === "a/b") return await sign(new Note({
701
+ id: ctx.getPortableObjectUri(Note, values),
702
+ to: PUBLIC_COLLECTION
703
+ }));
492
704
  return await signedNote(`/notes/${values.id}`);
493
705
  });
494
706
  let response = await federation.fetch(gatewayRequest("/notes/a%2Fb"), { contextData: void 0 });
@@ -516,7 +728,10 @@ test("RequestContext.portableRequest", async (t) => {
516
728
  ctx.portableRequest.id.pathname = "/notes/other";
517
729
  await ctx.getObject(Tombstone, { id: values.id });
518
730
  contexts.push(ctx.clone(void 0));
519
- return await sign(new Note({ id: ctx.getPortableObjectUri(Note, values) }));
731
+ return await sign(new Note({
732
+ id: ctx.getPortableObjectUri(Note, values),
733
+ to: PUBLIC_COLLECTION
734
+ }));
520
735
  });
521
736
  assertEquals((await federation.fetch(gatewayRequest("/notes/1"), { contextData: void 0 })).status, 200);
522
737
  await t.step("is frozen and returns a new URL each time", () => {
@@ -10,13 +10,13 @@ import { t as assertNotEquals } from "../assert_not_equals-CpPK2t89.mjs";
10
10
  import "../std__assert-DvMDRPes.mjs";
11
11
  import { t as assertRejects } from "../assert_rejects-CTptdYck.mjs";
12
12
  import { t as assert } from "../assert-D1Eafmd_.mjs";
13
- import { f as verifyRequest } from "../http-BQq1Uldg.mjs";
13
+ import { f as verifyRequest } from "../http--IGUT1mg.mjs";
14
14
  import { i as rsaPrivateKey2, n as ed25519PrivateKey, s as rsaPublicKey2, t as ed25519Multikey } from "../keys-DIJNHW-Y.mjs";
15
- import { t as doesActorOwnKey } from "../owner-DpxyMeo9.mjs";
16
- import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "../send-CZckda2X.mjs";
15
+ import { t as doesActorOwnKey } from "../owner-BW5VsXG3.mjs";
16
+ import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "../send-Duj8xys1.mjs";
17
17
  import { Activity, Application, Endpoints, Group, Person, Service } from "@fedify/vocab";
18
- import { createTestMeterProvider, createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
19
18
  import { FetchError, UrlError } from "@fedify/vocab-runtime";
19
+ import { createTestMeterProvider, createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
20
20
  import dns from "node:dns/promises";
21
21
  import { AggregationTemporality, InMemoryMetricExporter, MeterProvider, PeriodicExportingMetricReader } from "@opentelemetry/sdk-metrics";
22
22
  //#region src/federation/send.test.ts
@@ -3,8 +3,8 @@ import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as MemoryKvStore } from "../../kv-DCHkaJ9-.mjs";
5
5
  import { a as numberSchema, i as makeSchema, n as baseOptions, o as stringSchema, t as MockQueue } from "../../tasks-IG3pHfvZ.mjs";
6
- import "../../testing-C1itIjqW.mjs";
7
- import { o as createFederation } from "../../middleware-BbTJ6C1-.mjs";
6
+ import "../../testing-t8xDeJZ5.mjs";
7
+ import { o as createFederation } from "../../middleware-BW7-tHAG.mjs";
8
8
  import { n as ParallelMessageQueue } from "../../mq-Dm1Wmtm-.mjs";
9
9
  import { configure, reset } from "@logtape/logtape";
10
10
  import { delay } from "es-toolkit";
@@ -3,9 +3,9 @@ import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as MemoryKvStore } from "../../kv-DCHkaJ9-.mjs";
5
5
  import { a as numberSchema, i as makeSchema, n as baseOptions, o as stringSchema, t as MockQueue } from "../../tasks-IG3pHfvZ.mjs";
6
- import "../../testing-C1itIjqW.mjs";
7
- import { o as createFederation } from "../../middleware-BbTJ6C1-.mjs";
8
- import { r as createFederationBuilder } from "../../builder-BGSNMnVH.mjs";
6
+ import "../../testing-t8xDeJZ5.mjs";
7
+ import { o as createFederation } from "../../middleware-BW7-tHAG.mjs";
8
+ import { r as createFederationBuilder } from "../../builder-CKAVFvcH.mjs";
9
9
  import { t as TaskCodec } from "../../codec-C6rTrpIp.mjs";
10
10
  import { t as InProcessMessageQueue } from "../../mq-Dm1Wmtm-.mjs";
11
11
  import { W3CTraceContextPropagator } from "@opentelemetry/core";
@@ -4,7 +4,7 @@ globalThis.addEventListener = () => {};
4
4
  import { t as assertFalse } from "../assert_false-CPdRnc3E.mjs";
5
5
  import "../std__assert-DvMDRPes.mjs";
6
6
  import { t as assert } from "../assert-D1Eafmd_.mjs";
7
- import { t as hasMalformedKnownTemporalLiteral } from "../temporal-Cc1ymEup.mjs";
7
+ import { t as hasMalformedKnownTemporalLiteral } from "../temporal-CVcPKB1N.mjs";
8
8
  import { test } from "@fedify/fixture";
9
9
  //#region src/federation/temporal.test.ts
10
10
  test("hasMalformedKnownTemporalLiteral() detects expanded proof timestamps", async () => {
@@ -1,15 +1,15 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { r as createRequestContext } from "../context-B2rlkkyJ.mjs";
4
+ import { r as createRequestContext } from "../context-8Tu9kea0.mjs";
5
5
  import { t as MemoryKvStore } from "../kv-DCHkaJ9-.mjs";
6
6
  import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
7
7
  import { t as assertNotEquals } from "../assert_not_equals-CpPK2t89.mjs";
8
8
  import "../std__assert-DvMDRPes.mjs";
9
- import { o as createFederation, s as handleWebFinger } from "../middleware-BbTJ6C1-.mjs";
9
+ import { o as createFederation, s as handleWebFinger } from "../middleware-BW7-tHAG.mjs";
10
10
  import { Image, Link, Person, Tombstone } from "@fedify/vocab";
11
- import { createTestMeterProvider, test } from "@fedify/fixture";
12
11
  import { parseIri } from "@fedify/vocab-runtime";
12
+ import { createTestMeterProvider, test } from "@fedify/fixture";
13
13
  //#region src/federation/webfinger.test.ts
14
14
  test("handleWebFinger()", async (t) => {
15
15
  const url = new URL("https://example.com/.well-known/webfinger");