@fedify/fedify 2.4.0-dev.2115 → 2.4.0-dev.2127

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 (90) hide show
  1. package/dist/{builder-CZcBWrBd.mjs → builder-B2bgGYbP.mjs} +3 -3
  2. package/dist/compat/mod.d.cts +1 -1
  3. package/dist/compat/mod.d.ts +1 -1
  4. package/dist/compat/transformers.test.mjs +1 -1
  5. package/dist/{compound-proof-CDuP9MIX.mjs → compound-proof-SVlHSzV7.mjs} +2 -2
  6. package/dist/{context-SMNOiiY6.d.cts → context-Bm6ldGvx.d.cts} +30 -4
  7. package/dist/{context-tOiJfBoD.d.ts → context-Ci5vuUr_.d.ts} +30 -4
  8. package/dist/{context-CoNRxGYp.mjs → context-DfSVPcJ8.mjs} +6 -3
  9. package/dist/{deno-CCABdw7r.mjs → deno-Be9xoxps.mjs} +1 -1
  10. package/dist/{docloader-Bby5g2iu.mjs → docloader-Dtuyb0ps.mjs} +7 -7
  11. package/dist/federation/builder.test.mjs +1 -1
  12. package/dist/federation/compound-fanout.test.mjs +3 -3
  13. package/dist/federation/handler-compound-proof.test.mjs +4 -4
  14. package/dist/federation/handler.test.mjs +6 -6
  15. package/dist/federation/hashlink-media.test.mjs +2 -2
  16. package/dist/federation/idempotency.test.mjs +2 -2
  17. package/dist/federation/metrics.test.mjs +1 -1
  18. package/dist/federation/middleware.test.mjs +8 -8
  19. package/dist/federation/mod.cjs +1 -1
  20. package/dist/federation/mod.d.cts +2 -2
  21. package/dist/federation/mod.d.ts +2 -2
  22. package/dist/federation/mod.js +1 -1
  23. package/dist/federation/outgoing-proof.test.mjs +5 -5
  24. package/dist/federation/portable-actor.test.mjs +52 -5
  25. package/dist/federation/portable-chain.test.d.mts +2 -0
  26. package/dist/federation/portable-chain.test.mjs +358 -0
  27. package/dist/federation/portable-gateway-keys.test.mjs +4 -4
  28. package/dist/federation/portable-inbox.test.mjs +4 -4
  29. package/dist/federation/portable.test.mjs +127 -30
  30. package/dist/federation/send.test.mjs +3 -3
  31. package/dist/federation/tasks/enqueue.test.mjs +2 -2
  32. package/dist/federation/tasks/tasks.test.mjs +3 -3
  33. package/dist/federation/temporal.test.mjs +1 -1
  34. package/dist/federation/webfinger.test.mjs +2 -2
  35. package/dist/{federation-DYr384xz.cjs → federation-B6IHuS6n.cjs} +64 -28
  36. package/dist/{http-BcLnr2do.mjs → http-BFeK9sBg.mjs} +4 -4
  37. package/dist/{http-Ce6_Lf7k.js → http-Rmbiu-5S.js} +1 -1
  38. package/dist/{http-B4mQvNcv.cjs → http-o8l4QeUY.cjs} +1 -1
  39. package/dist/{key-CkNxztNV.mjs → key-Cc1b6pW9.mjs} +2 -2
  40. package/dist/{kv-cache-Dqp_E-1g.cjs → kv-cache-BUkgZAcX.cjs} +6 -6
  41. package/dist/{kv-cache-DwzXzchj.js → kv-cache-BweDce2I.js} +6 -6
  42. package/dist/{kv-cache-iFaRIcIL.mjs → kv-cache-tLKBfxHc.mjs} +1 -1
  43. package/dist/{ld-CnT0f1nU.mjs → ld-DQNTIrAe.mjs} +3 -3
  44. package/dist/{metrics-em_7lwhG.mjs → metrics-DDE9S63Q.mjs} +1 -1
  45. package/dist/{middleware-BpwKvOpU.mjs → middleware-BdhvduR9.mjs} +72 -35
  46. package/dist/{middleware-U9IuzvDO.mjs → middleware-CsQorLC_.mjs} +1 -1
  47. package/dist/{middleware-DhIBngkj.js → middleware-K9aLnf57.js} +62 -26
  48. package/dist/{mod-DnElMslb.d.cts → mod-AVir5nVI.d.cts} +1 -1
  49. package/dist/{mod-DMdGFMEl.d.cts → mod-Byt5tvAS.d.cts} +13 -2
  50. package/dist/{mod-CTh7ijx5.d.ts → mod-Co53r-VP.d.ts} +13 -2
  51. package/dist/{mod-COWgSo2v.d.ts → mod-eVOUPbQE.d.ts} +1 -1
  52. package/dist/mod.cjs +8 -8
  53. package/dist/mod.d.cts +3 -3
  54. package/dist/mod.d.ts +3 -3
  55. package/dist/mod.js +5 -5
  56. package/dist/nodeinfo/handler.test.mjs +2 -2
  57. package/dist/{owner-9LiHmOv1.mjs → owner-CCfdL83M.mjs} +3 -3
  58. package/dist/{sig-Bj3fHnv0.cjs → portable-collection-DXogVs__.cjs} +220 -2
  59. package/dist/portable-collection-DdXsKBg4.js +623 -0
  60. package/dist/{sig-DN9pOglI.js → portable-collection-WXaHkNqC.mjs} +5 -4
  61. package/dist/{portable-key-9ck61lI0.mjs → portable-key-C-fPq_Yf.mjs} +1 -1
  62. package/dist/{proof-xdPYGPaA.mjs → proof-CW8k0R1G.mjs} +4 -4
  63. package/dist/{send-Bf8rSWch.mjs → send--FORaIc4.mjs} +3 -3
  64. package/dist/sig/compound-proof-discovery.test.mjs +1 -1
  65. package/dist/sig/compound-proof-producer.test.mjs +1 -1
  66. package/dist/sig/compound-proof-verification.test.mjs +2 -2
  67. package/dist/sig/compound-proof.test.mjs +2 -2
  68. package/dist/sig/http.test.mjs +2 -2
  69. package/dist/sig/key.test.mjs +1 -1
  70. package/dist/sig/ld.test.mjs +2 -2
  71. package/dist/sig/mod.cjs +5 -6
  72. package/dist/sig/mod.d.cts +1 -1
  73. package/dist/sig/mod.d.ts +1 -1
  74. package/dist/sig/mod.js +2 -3
  75. package/dist/sig/owner.test.mjs +1 -1
  76. package/dist/sig/portable-collection.test.mjs +20 -431
  77. package/dist/sig/portable-key.test.mjs +5 -5
  78. package/dist/sig/proof.test.mjs +3 -3
  79. package/dist/{temporal-tAtwv6vX.mjs → temporal-CffY_XOJ.mjs} +1 -1
  80. package/dist/testing/kv.test.mjs +1 -1
  81. package/dist/testing/mod.d.mts +30 -4
  82. package/dist/testing/mod.mjs +2 -2
  83. package/dist/{testing-BWq1dCzx.mjs → testing-otQhu4aD.mjs} +1 -1
  84. package/dist/utils/docloader.test.mjs +81 -2
  85. package/dist/utils/kv-cache.test.mjs +1 -1
  86. package/dist/utils/mod.cjs +1 -1
  87. package/dist/utils/mod.js +1 -1
  88. package/package.json +6 -6
  89. package/dist/owner-BtoMCv6Z.cjs +0 -225
  90. package/dist/owner-Cb3ytrlq.js +0 -215
@@ -1,4 +1,4 @@
1
- import "temporal-polyfill";
1
+ import { Temporal } from "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as MemoryKvStore } from "../kv-DCHkaJ9-.mjs";
@@ -8,10 +8,10 @@ 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 { i as signObject, s as verifyPortableObjectProof } from "../proof-xdPYGPaA.mjs";
12
- import { l as signRequest } from "../http-BcLnr2do.mjs";
11
+ import { i as signObject, s as verifyPortableObjectProof } from "../proof-CW8k0R1G.mjs";
12
+ import { l as signRequest } from "../http-BFeK9sBg.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-BpwKvOpU.mjs";
14
+ import { b as PORTABLE_OBJECT_CONTENT_TYPE, o as createFederation } from "../middleware-BdhvduR9.mjs";
15
15
  import { configure, reset } from "@logtape/logtape";
16
16
  import { Collection, Create, Note, Object as Object$1, Person, Tombstone, lookupObject } from "@fedify/vocab";
17
17
  import { mockDocumentLoader, test } from "@fedify/fixture";
@@ -220,18 +220,6 @@ test("Federation.fetch() applies the FEP-ef61 proof policy to portable objects",
220
220
  inbox: parseIri(`ap+ef61://${did}/objects/unsigned-actor/inbox`),
221
221
  outbox: parseIri(`ap+ef61://${did}/objects/unsigned-actor/outbox`),
222
222
  gateways: [new URL("https://example.com/")]
223
- })),
224
- "tombstone": () => sign(new Tombstone({
225
- id: parseIri(`ap+ef61://${did}/objects/tombstone`),
226
- formerType: Note
227
- })),
228
- "unsigned-tombstone": () => Promise.resolve(new Tombstone({
229
- id: parseIri(`ap+ef61://${did}/objects/unsigned-tombstone`),
230
- formerType: Note
231
- })),
232
- "mismatched-tombstone": () => sign(new Tombstone({
233
- id: parseIri(`ap+ef61://${did}/objects/other`),
234
- formerType: Note
235
223
  }))
236
224
  };
237
225
  federation.setObjectDispatcher(Object$1, "/objects/{id}", (_ctx, { id }) => objects[id]?.() ?? null);
@@ -246,10 +234,7 @@ test("Federation.fetch() applies the FEP-ef61 proof policy to portable objects",
246
234
  "activity": 200,
247
235
  "actor": 200,
248
236
  "mutated-id": 404,
249
- "unsigned-actor": 500,
250
- "tombstone": 200,
251
- "unsigned-tombstone": 500,
252
- "mismatched-tombstone": 404
237
+ "unsigned-actor": 500
253
238
  })) await t.step(`${id} → ${status}`, async () => {
254
239
  const response = await federation.fetch(gatewayRequest(`/objects/${id}`), { contextData: void 0 });
255
240
  assertEquals(response.status, status);
@@ -260,17 +245,129 @@ test("Federation.fetch() applies the FEP-ef61 proof policy to portable objects",
260
245
  assertEquals(response.status, 500);
261
246
  assertEquals(response.body, null);
262
247
  });
263
- await t.step("serves a signed tombstone with its body", async () => {
264
- const response = await federation.fetch(gatewayRequest("/objects/tombstone"), { contextData: void 0 });
265
- assertEquals(response.status, 200);
266
- const body = await response.json();
267
- assertEquals(body.type, "Tombstone");
268
- assertEquals(body.id, `ap+ef61://${did}/objects/tombstone`);
248
+ });
249
+ test("Federation.fetch() serves tombstones of portable objects", async (t) => {
250
+ const federation = createTestFederation();
251
+ const compatible = (path) => new URL(`https://example.com/.well-known/apgateway/${did}${path}`);
252
+ const tombstoneFromJsonLd = (path, properties) => Tombstone.fromJsonLd({
253
+ "@context": "https://www.w3.org/ns/activitystreams",
254
+ type: "Tombstone",
255
+ id: `ap+ef61://${did}${path}`,
256
+ ...properties
257
+ }, {
258
+ contextLoader: mockDocumentLoader,
259
+ documentLoader: mockDocumentLoader
269
260
  });
270
- await t.step("responds to HEAD for a tombstone without a body", async () => {
271
- const response = await federation.fetch(gatewayRequest("/objects/tombstone", { method: "HEAD" }), { contextData: void 0 });
272
- assertEquals(response.status, 200);
273
- assertEquals(response.body, null);
261
+ const tombstones = {
262
+ "signed": () => sign(new Tombstone({
263
+ id: parseIri(`ap+ef61://${did}/notes/signed`),
264
+ formerType: Note,
265
+ deleted: Temporal.Instant.from("2026-01-01T00:00:00Z")
266
+ })),
267
+ "compatible": () => sign(new Tombstone({
268
+ id: compatible("/notes/compatible"),
269
+ formerType: Note
270
+ })),
271
+ "signed-collection": async () => sign(await tombstoneFromJsonLd("/notes/signed-collection", { totalItems: 0 })),
272
+ "unsigned": () => Promise.resolve(new Tombstone({
273
+ id: parseIri(`ap+ef61://${did}/notes/unsigned`),
274
+ formerType: Note
275
+ })),
276
+ "wrong-did": () => sign(new Tombstone({ id: parseIri(`ap+ef61://${did}/notes/wrong-did`) }), otherKeyPair.privateKey, otherKeyId),
277
+ "tampered": async () => (await sign(new Tombstone({ id: parseIri(`ap+ef61://${did}/notes/tampered`) }))).clone({ formerType: Note }),
278
+ "unsigned-collection": () => tombstoneFromJsonLd("/notes/unsigned-collection", { totalItems: 0 }),
279
+ "link": () => tombstoneFromJsonLd("/notes/link", { href: "https://example.com/" }),
280
+ "mismatched": () => sign(new Tombstone({ id: parseIri(`ap+ef61://${did}/notes/other`) })),
281
+ "mutated-id": async () => {
282
+ const signed = await sign(new Tombstone({ id: parseIri(`ap+ef61://${did}/notes/original`) }));
283
+ const tombstone = await Tombstone.fromJsonLd(await signed.toJsonLd({ contextLoader: mockDocumentLoader }), {
284
+ contextLoader: mockDocumentLoader,
285
+ documentLoader: mockDocumentLoader
286
+ });
287
+ tombstone.id.pathname = tombstone.id.pathname.replace(/original$/, "mutated-id");
288
+ return tombstone;
289
+ }
290
+ };
291
+ federation.setObjectDispatcher(Note, "/notes/{id}", (_ctx, { id }) => tombstones[id]?.() ?? null).authorize((ctx) => ctx.request.headers.get("X-Deny") == null);
292
+ for (const [id, reason] of globalThis.Object.entries({
293
+ "unsigned": "missingProof",
294
+ "wrong-did": "verificationMethodMismatch",
295
+ "tampered": "invalidProof",
296
+ "unsigned-collection": "unsecuredCollection",
297
+ "link": "unsupportedObjectType"
298
+ })) {
299
+ const result = await verifyPortableObjectProof(await (await tombstones[id]()).toJsonLd({ contextLoader: mockDocumentLoader }), { contextLoader: mockDocumentLoader });
300
+ assert(!result.verified, id);
301
+ assertEquals(result.reason.type, reason, id);
302
+ }
303
+ for (const [id, status] of globalThis.Object.entries({
304
+ "signed": 410,
305
+ "compatible": 410,
306
+ "signed-collection": 410,
307
+ "unsigned": 404,
308
+ "wrong-did": 500,
309
+ "tampered": 500,
310
+ "unsigned-collection": 500,
311
+ "link": 500,
312
+ "mismatched": 404,
313
+ "mutated-id": 404
314
+ })) await t.step(`${id} → ${status}`, async () => {
315
+ let notFound = false;
316
+ const response = await federation.fetch(gatewayRequest(`/notes/${id}`), {
317
+ contextData: void 0,
318
+ onNotFound() {
319
+ notFound = true;
320
+ return new Response("Not found", { status: 404 });
321
+ }
322
+ });
323
+ assertEquals(response.status, status);
324
+ assertEquals(notFound, status === 404);
325
+ if (status === 410) {
326
+ assertEquals(response.headers.get("Content-Type"), PORTABLE_OBJECT_CONTENT_TYPE);
327
+ assertEquals(response.headers.get("Vary"), "Accept");
328
+ const body = await response.json();
329
+ assertEquals(body.type, "Tombstone");
330
+ assert((await verifyPortableObjectProof(body, { contextLoader: mockDocumentLoader })).verified);
331
+ } else if (status === 500) assertEquals(await response.text(), "Internal server error.");
332
+ });
333
+ await t.step("serves the tombstone as the dispatcher returns it", async () => {
334
+ let response = await federation.fetch(gatewayRequest("/notes/signed"), { contextData: void 0 });
335
+ let body = await response.json();
336
+ assertEquals(body.id, `ap+ef61://${did}/notes/signed`);
337
+ assertEquals(body.formerType, "as:Note");
338
+ assertEquals(body.deleted, "2026-01-01T00:00:00Z");
339
+ response = await federation.fetch(gatewayRequest("/notes/compatible"), { contextData: void 0 });
340
+ body = await response.json();
341
+ assertEquals(body.id, compatible("/notes/compatible").href);
342
+ });
343
+ await t.step("responds to HEAD without a body", async () => {
344
+ for (const [id, status] of [["signed", 410], ["unsigned", 404]]) {
345
+ const response = await federation.fetch(gatewayRequest(`/notes/${id}`, { method: "HEAD" }), { contextData: void 0 });
346
+ assertEquals(response.status, status, id);
347
+ assertEquals(response.body, null, id);
348
+ if (status === 410) {
349
+ assertEquals(response.headers.get("Content-Type"), PORTABLE_OBJECT_CONTENT_TYPE);
350
+ assertEquals(response.headers.get("Vary"), "Accept");
351
+ }
352
+ }
353
+ });
354
+ await t.step("authorizes before serving a tombstone", async () => {
355
+ for (const id of ["signed", "unsigned"]) {
356
+ let unauthorized = false;
357
+ assertEquals((await federation.fetch(gatewayRequest(`/notes/${id}`, { headers: { "X-Deny": "1" } }), {
358
+ contextData: void 0,
359
+ onUnauthorized() {
360
+ unauthorized = true;
361
+ return new Response("Unauthorized", { status: 401 });
362
+ }
363
+ })).status, 401, id);
364
+ assertEquals(unauthorized, true, id);
365
+ }
366
+ });
367
+ await t.step("does not warn about tombstones with compatible IDs", async () => {
368
+ assertEquals((await captureWarnings(async () => {
369
+ assertEquals((await federation.fetch(gatewayRequest("/notes/compatible"), { contextData: void 0 })).status, 410);
370
+ })).filter((record) => record.category[2] === "object"), []);
274
371
  });
275
372
  });
276
373
  test("Federation.fetch() authorizes portable object requests", async (t) => {
@@ -10,10 +10,10 @@ 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 { d as verifyRequest } from "../http-BcLnr2do.mjs";
13
+ import { d as verifyRequest } from "../http-BFeK9sBg.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-9LiHmOv1.mjs";
16
- import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "../send-Bf8rSWch.mjs";
15
+ import { t as doesActorOwnKey } from "../owner-CCfdL83M.mjs";
16
+ import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "../send--FORaIc4.mjs";
17
17
  import { Activity, Application, Endpoints, Group, Person, Service } from "@fedify/vocab";
18
18
  import { createTestMeterProvider, createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
19
19
  import { FetchError, UrlError } from "@fedify/vocab-runtime";
@@ -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-dbCShlCb.mjs";
6
- import "../../testing-BWq1dCzx.mjs";
7
- import { o as createFederation } from "../../middleware-BpwKvOpU.mjs";
6
+ import "../../testing-otQhu4aD.mjs";
7
+ import { o as createFederation } from "../../middleware-BdhvduR9.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-dbCShlCb.mjs";
6
- import "../../testing-BWq1dCzx.mjs";
7
- import { o as createFederation } from "../../middleware-BpwKvOpU.mjs";
8
- import { r as createFederationBuilder } from "../../builder-CZcBWrBd.mjs";
6
+ import "../../testing-otQhu4aD.mjs";
7
+ import { o as createFederation } from "../../middleware-BdhvduR9.mjs";
8
+ import { r as createFederationBuilder } from "../../builder-B2bgGYbP.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-tAtwv6vX.mjs";
7
+ import { t as hasMalformedKnownTemporalLiteral } from "../temporal-CffY_XOJ.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,12 +1,12 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { r as createRequestContext } from "../context-CoNRxGYp.mjs";
4
+ import { r as createRequestContext } from "../context-DfSVPcJ8.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-BpwKvOpU.mjs";
9
+ import { o as createFederation, s as handleWebFinger } from "../middleware-BdhvduR9.mjs";
10
10
  import { Image, Link, Person, Tombstone } from "@fedify/vocab";
11
11
  import { createTestMeterProvider, test } from "@fedify/fixture";
12
12
  import { parseIri } from "@fedify/vocab-runtime";
@@ -1,10 +1,10 @@
1
1
  const { URLPattern } = require("urlpattern-polyfill");
2
2
  const require_rolldown_runtime = require("./rolldown-runtime-Dmm3t92W.cjs");
3
3
  const require_transformers = require("./transformers-BalCrSsr.cjs");
4
- const require_http = require("./http-B4mQvNcv.cjs");
4
+ const require_http = require("./http-o8l4QeUY.cjs");
5
5
  const require_types = require("./types-Be2zbyeG.cjs");
6
- const require_owner = require("./owner-BtoMCv6Z.cjs");
7
- const require_kv_cache = require("./kv-cache-Dqp_E-1g.cjs");
6
+ const require_portable_collection = require("./portable-collection-DXogVs__.cjs");
7
+ const require_kv_cache = require("./kv-cache-BUkgZAcX.cjs");
8
8
  let _logtape_logtape = require("@logtape/logtape");
9
9
  let _fedify_uri_template = require("@fedify/uri-template");
10
10
  let _fedify_vocab = require("@fedify/vocab");
@@ -750,7 +750,7 @@ function warnMismatchedTombstoneId(context, cls, values, tombstone) {
750
750
  });
751
751
  return;
752
752
  }
753
- if (tombstone.id.protocol === "ap:" || tombstone.id.protocol === "ap+ef61:") return;
753
+ if (require_http.isPortableId(tombstone.id)) return;
754
754
  const expected = context.getObjectUri(cls, values);
755
755
  if (tombstone.id.href !== expected.href) logger.warn("Object dispatcher for {class} returned a tombstone with an id property {tombstoneId} that does not match the object URI {objectUri}. Set the property with Context.getObjectUri().", {
756
756
  class: cls.name,
@@ -3536,6 +3536,11 @@ async function handleObject(request, { values, context, objectDispatcher, author
3536
3536
  * dispatcher, or the actor dispatcher adapted to one. The object is served
3537
3537
  * only if its ID canonically matches the requested portable ID and it
3538
3538
  * satisfies the FEP-ef61 proof policy.
3539
+ *
3540
+ * A {@link Tombstone} is served with `410 Gone` if it has an Object Integrity
3541
+ * Proof made with a key of the DID in its ID. An unsigned tombstone cannot
3542
+ * be served as a portable object, so the response is the same as for
3543
+ * an object that this server does not store, i.e., `404 Not Found`.
3539
3544
  * @template TContextData The context data to pass to the context.
3540
3545
  * @param request The HTTP request.
3541
3546
  * @param parameters The parameters for handling the portable object.
@@ -3583,17 +3588,27 @@ async function handlePortableObject(request, { values, context, objectDispatcher
3583
3588
  logger.debug("The serialized object does not match the requested portable object {portableId}.", { portableId: canonicalId });
3584
3589
  return await onNotFound(request);
3585
3590
  }
3586
- if (!result.verified && result.reason.type !== "unsecuredCollection" && result.reason.type !== "unsupportedObjectType") {
3587
- logger.error("Refusing to serve the portable object {portableId}, as it does not satisfy the FEP-ef61 proof policy: {reason}. Portable actors, activities, and objects need an Object Integrity Proof made with a key of the DID in their ID.", {
3588
- portableId: canonicalId,
3589
- reason: result.reason.type
3590
- });
3591
- return portableObjectInternalServerError(request);
3591
+ const tombstone = object instanceof _fedify_vocab.Tombstone;
3592
+ if (!result.verified) {
3593
+ if (tombstone && result.reason.type === "missingProof") {
3594
+ logger.debug("Not serving the tombstone of the portable object {portableId}, as it has no Object Integrity Proof; responding as if this server did not store the object.", { portableId: canonicalId });
3595
+ return await onNotFound(request);
3596
+ }
3597
+ if (tombstone || result.reason.type !== "unsecuredCollection" && result.reason.type !== "unsupportedObjectType") {
3598
+ logger.error("Refusing to serve the portable object {portableId}, as it does not satisfy the FEP-ef61 proof policy: {reason}. Portable actors, activities, objects, and tombstones need an Object Integrity Proof made with a key of the DID in their ID.", {
3599
+ portableId: canonicalId,
3600
+ reason: result.reason.type
3601
+ });
3602
+ return portableObjectInternalServerError(request);
3603
+ }
3592
3604
  }
3593
- return new Response(request.method === "HEAD" ? null : JSON.stringify(jsonLd), { headers: {
3594
- "Content-Type": PORTABLE_OBJECT_CONTENT_TYPE,
3595
- Vary: "Accept"
3596
- } });
3605
+ return new Response(request.method === "HEAD" ? null : JSON.stringify(jsonLd), {
3606
+ status: tombstone ? 410 : 200,
3607
+ headers: {
3608
+ "Content-Type": PORTABLE_OBJECT_CONTENT_TYPE,
3609
+ Vary: "Accept"
3610
+ }
3611
+ });
3597
3612
  }
3598
3613
  function isRequestedPortableObject(object, canonicalId) {
3599
3614
  return object.id != null && require_http.getCanonicalPortableId(object.id) === canonicalId;
@@ -4497,7 +4512,8 @@ async function handleInboxInternal(request, parameters, span) {
4497
4512
  documentLoader: ctx.documentLoader,
4498
4513
  keyCache,
4499
4514
  meterProvider,
4500
- tracerProvider
4515
+ tracerProvider,
4516
+ verifyPortableObject: ctx.verifyPortableObject
4501
4517
  });
4502
4518
  } catch (error) {
4503
4519
  if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(jsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(error);
@@ -4699,7 +4715,7 @@ async function handleInboxInternal(request, parameters, span) {
4699
4715
  });
4700
4716
  }
4701
4717
  }
4702
- if (httpSigKey != null && !await require_owner.doesActorOwnKey(activity, httpSigKey, ctx)) {
4718
+ if (httpSigKey != null && !await require_portable_collection.doesActorOwnKey(activity, httpSigKey, ctx)) {
4703
4719
  if (deferredLdSignatureError != null) throw deferredLdSignatureError;
4704
4720
  require_http.getFederationMetrics(parameters.meterProvider).recordSignatureVerificationFailure("actorKeyMismatch", httpSigKey.id == null ? void 0 : require_http.getRemoteHost(httpSigKey.id));
4705
4721
  logger.error("The signer ({keyId}) and the actor ({actorId}) do not match.", {
@@ -7196,11 +7212,16 @@ var FederationImpl = class extends FederationBuilderImpl {
7196
7212
  });
7197
7213
  return parsedActorIds;
7198
7214
  };
7199
- const parseActivity = () => _fedify_vocab.Activity.fromJsonLd(message.activity, {
7200
- contextLoader: this.contextLoaderFactory(loaderOptions),
7201
- documentLoader: rsaKeyPair == null ? this.documentLoaderFactory(loaderOptions) : this.authenticatedDocumentLoaderFactory(rsaKeyPair, loaderOptions),
7202
- tracerProvider: this.tracerProvider
7203
- });
7215
+ const parseActivity = () => {
7216
+ const contextLoader = this.contextLoaderFactory(loaderOptions);
7217
+ const documentLoader = rsaKeyPair == null ? this.documentLoaderFactory(loaderOptions) : this.authenticatedDocumentLoaderFactory(rsaKeyPair, loaderOptions);
7218
+ return _fedify_vocab.Activity.fromJsonLd(message.activity, {
7219
+ contextLoader,
7220
+ documentLoader,
7221
+ tracerProvider: this.tracerProvider,
7222
+ verifyPortableObject: createPortableObjectVerifier(documentLoader, contextLoader, this)
7223
+ });
7224
+ };
7204
7225
  const enqueueHeldOutboxMessage = async (delay, heldSince) => {
7205
7226
  const { outboxQueue } = this;
7206
7227
  if (outboxQueue == null) return;
@@ -8388,10 +8409,7 @@ var FederationImpl = class extends FederationBuilderImpl {
8388
8409
  return await handlePortableObject(request, {
8389
8410
  values: { identifier },
8390
8411
  context,
8391
- objectDispatcher: actorDispatcher == null ? void 0 : async (ctx) => {
8392
- const actor = await actorDispatcher(ctx, identifier);
8393
- return actor instanceof _fedify_vocab.Tombstone ? null : actor;
8394
- },
8412
+ objectDispatcher: actorDispatcher == null ? void 0 : (ctx) => actorDispatcher(ctx, identifier),
8395
8413
  authorizePredicate: authorizePredicate == null ? void 0 : (ctx) => authorizePredicate(ctx, identifier),
8396
8414
  canonicalId: portable.canonicalId,
8397
8415
  onUnauthorized,
@@ -8679,12 +8697,28 @@ function getCollectionMetricRoute(routeName) {
8679
8697
  }
8680
8698
  }
8681
8699
  const FANOUT_THRESHOLD = 5;
8700
+ /**
8701
+ * Creates the FEP-ef61 portable object verifier that a context applies to
8702
+ * portable objects it dereferences: `verifyPortableObject()` with the given
8703
+ * loaders and the federation's tracer provider as defaults. Other options,
8704
+ * such as the document URL, gateways, and referrer, are passed through as
8705
+ * they are.
8706
+ */
8707
+ function createPortableObjectVerifier(documentLoader, contextLoader, federation) {
8708
+ return (document, options = {}) => require_portable_collection.verifyPortableObject(document, {
8709
+ ...options,
8710
+ documentLoader: options.documentLoader ?? documentLoader,
8711
+ contextLoader: options.contextLoader ?? contextLoader,
8712
+ tracerProvider: options.tracerProvider ?? federation.tracerProvider
8713
+ });
8714
+ }
8682
8715
  var ContextImpl = class ContextImpl {
8683
8716
  url;
8684
8717
  federation;
8685
8718
  data;
8686
8719
  documentLoader;
8687
8720
  contextLoader;
8721
+ verifyPortableObject;
8688
8722
  invokedFromActorKeyPairsDispatcher;
8689
8723
  #codec;
8690
8724
  constructor({ url, federation, data, documentLoader, contextLoader, invokedFromActorKeyPairsDispatcher }) {
@@ -8693,6 +8727,7 @@ var ContextImpl = class ContextImpl {
8693
8727
  this.data = data;
8694
8728
  this.documentLoader = documentLoader;
8695
8729
  this.contextLoader = contextLoader;
8730
+ this.verifyPortableObject = createPortableObjectVerifier(documentLoader, contextLoader, federation);
8696
8731
  this.invokedFromActorKeyPairsDispatcher = invokedFromActorKeyPairsDispatcher;
8697
8732
  }
8698
8733
  /**
@@ -9044,7 +9079,7 @@ var ContextImpl = class ContextImpl {
9044
9079
  userAgent: options.userAgent ?? this.federation.userAgent,
9045
9080
  tracerProvider: options.tracerProvider ?? this.tracerProvider,
9046
9081
  meterProvider: options.meterProvider ?? this.meterProvider,
9047
- verifyPortableObject: options.verifyPortableObject ?? require_http.verifyPortableObjectProof,
9082
+ verifyPortableObject: options.verifyPortableObject ?? this.verifyPortableObject,
9048
9083
  allowPrivateAddress: this.federation.allowPrivateAddress
9049
9084
  });
9050
9085
  }
@@ -9052,7 +9087,8 @@ var ContextImpl = class ContextImpl {
9052
9087
  return (0, _fedify_vocab.traverseCollection)(collection, {
9053
9088
  ...options,
9054
9089
  documentLoader: options.documentLoader ?? this.documentLoader,
9055
- contextLoader: options.contextLoader ?? this.contextLoader
9090
+ contextLoader: options.contextLoader ?? this.contextLoader,
9091
+ verifyPortableObject: options.verifyPortableObject ?? this.verifyPortableObject
9056
9092
  });
9057
9093
  }
9058
9094
  lookupNodeInfo(url, options = {}) {
@@ -9478,7 +9514,7 @@ var RequestContextImpl = class RequestContextImpl extends ContextImpl {
9478
9514
  const key = await this.getSignedKey(options);
9479
9515
  if (key == null) return this.#signedKeyOwner = null;
9480
9516
  try {
9481
- return this.#signedKeyOwner = await require_owner.getKeyOwner(key, {
9517
+ return this.#signedKeyOwner = await require_portable_collection.getKeyOwner(key, {
9482
9518
  contextLoader: options.contextLoader ?? this.contextLoader,
9483
9519
  documentLoader: options.documentLoader ?? this.documentLoader,
9484
9520
  tracerProvider: options.tracerProvider ?? this.tracerProvider
@@ -1,11 +1,11 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-CCABdw7r.mjs";
5
- import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-em_7lwhG.mjs";
4
+ import { n as version, t as name } from "./deno-Be9xoxps.mjs";
5
+ import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-DDE9S63Q.mjs";
6
6
  import { i as validateAcceptSignature, n as fulfillAcceptSignature, r as parseAcceptSignature } from "./accept-CPVqki78.mjs";
7
- import { a as fetchKeyDetailed, c as validateCryptoKey, t as bypassKeyCacheReads } from "./key-CkNxztNV.mjs";
8
- import { n as verifyPortableGatewayKeyDocument } from "./portable-key-9ck61lI0.mjs";
7
+ import { a as fetchKeyDetailed, c as validateCryptoKey, t as bypassKeyCacheReads } from "./key-Cc1b6pW9.mjs";
8
+ import { n as verifyPortableGatewayKeyDocument } from "./portable-key-C-fPq_Yf.mjs";
9
9
  import { getLogger } from "@logtape/logtape";
10
10
  import { CryptographicKey } from "@fedify/vocab";
11
11
  import { SpanStatusCode, trace } from "@opentelemetry/api";
@@ -14,7 +14,7 @@ import serialize from "json-canon";
14
14
  import { ATTR_HTTP_REQUEST_HEADER, ATTR_HTTP_REQUEST_METHOD, ATTR_URL_FULL } from "@opentelemetry/semantic-conventions";
15
15
  //#region deno.json
16
16
  var name = "@fedify/fedify";
17
- var version = "2.4.0-dev.2115+e2feaf2b";
17
+ var version = "2.4.0-dev.2127+c41ce2eb";
18
18
  //#endregion
19
19
  //#region src/sig/portable-key-id.ts
20
20
  const COMPATIBLE_KEY_ID_PATH_PATTERN = /^\/\.well-known\/apgateway\/did(?::|%3A)/i;
@@ -16,7 +16,7 @@ json_canon = require_rolldown_runtime.__toESM(json_canon, 1);
16
16
  let _opentelemetry_semantic_conventions = require("@opentelemetry/semantic-conventions");
17
17
  //#region deno.json
18
18
  var name = "@fedify/fedify";
19
- var version = "2.4.0-dev.2115+e2feaf2b";
19
+ var version = "2.4.0-dev.2127+c41ce2eb";
20
20
  //#endregion
21
21
  //#region src/sig/portable-key-id.ts
22
22
  const COMPATIBLE_KEY_ID_PATH_PATTERN = /^\/\.well-known\/apgateway\/did(?::|%3A)/i;
@@ -1,8 +1,8 @@
1
1
  import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-CCABdw7r.mjs";
5
- import { _ as recordKeyLookup, n as getDurationMs, t as classifyFetchError } from "./metrics-em_7lwhG.mjs";
4
+ import { n as version, t as name } from "./deno-Be9xoxps.mjs";
5
+ import { _ as recordKeyLookup, n as getDurationMs, t as classifyFetchError } from "./metrics-DDE9S63Q.mjs";
6
6
  import { a as isCompatibleKeyId, o as isPortableActorDocument, s as isPortableId } from "./portable-key-id-T7lecIbt.mjs";
7
7
  import { getLogger } from "@logtape/logtape";
8
8
  import { CryptographicKey, Multikey, Object as Object$1, isActor } from "@fedify/vocab";
@@ -1,5 +1,5 @@
1
1
  const { URLPattern } = require("urlpattern-polyfill");
2
- const require_http = require("./http-B4mQvNcv.cjs");
2
+ const require_http = require("./http-o8l4QeUY.cjs");
3
3
  let _logtape_logtape = require("@logtape/logtape");
4
4
  let _fedify_vocab_runtime = require("@fedify/vocab-runtime");
5
5
  let es_toolkit = require("es-toolkit");
@@ -23,21 +23,21 @@ const logger$1 = (0, _logtape_logtape.getLogger)([
23
23
  function getAuthenticatedDocumentLoader(identity, { allowPrivateAddress, maxRedirection, userAgent, specDeterminer, tracerProvider } = {}) {
24
24
  require_http.validateCryptoKey(identity.privateKey);
25
25
  async function load(url, options) {
26
- await validateUrl(url);
26
+ await validateUrl(url, options);
27
27
  return (0, _fedify_vocab_runtime.getRemoteDocument)(url, await require_http.doubleKnock((0, _fedify_vocab_runtime.createActivityPubRequest)(url, { userAgent }), identity, {
28
28
  maxRedirection,
29
29
  specDeterminer,
30
30
  log: (0, es_toolkit.curry)(_fedify_vocab_runtime.logRequest)(logger$1),
31
31
  tracerProvider,
32
32
  signal: options?.signal,
33
- validateRedirect: validateUrl
34
- }), load);
33
+ validateRedirect: (url) => validateUrl(url, options)
34
+ }), load, options);
35
35
  }
36
- async function validateUrl(url) {
36
+ async function validateUrl(url, options) {
37
37
  if (!allowPrivateAddress) try {
38
38
  await (0, _fedify_vocab_runtime.validatePublicUrl)(url);
39
39
  } catch (error) {
40
- if (error instanceof _fedify_vocab_runtime.UrlError) logger$1.error("Disallowed private URL: {url}", {
40
+ if (error instanceof _fedify_vocab_runtime.UrlError) logger$1[options?.suppressError ? "warn" : "error"]("Disallowed private URL: {url}", {
41
41
  url,
42
42
  error
43
43
  });
@@ -1,6 +1,6 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
- import { H as validateCryptoKey, et as recordDocumentCache, t as doubleKnock } from "./http-Ce6_Lf7k.js";
3
+ import { H as validateCryptoKey, et as recordDocumentCache, t as doubleKnock } from "./http-Rmbiu-5S.js";
4
4
  import { getLogger } from "@logtape/logtape";
5
5
  import { UrlError, createActivityPubRequest, getRemoteDocument, logRequest, preloadedContexts, validatePublicUrl } from "@fedify/vocab-runtime";
6
6
  import { curry } from "es-toolkit";
@@ -24,21 +24,21 @@ const logger$1 = getLogger([
24
24
  function getAuthenticatedDocumentLoader(identity, { allowPrivateAddress, maxRedirection, userAgent, specDeterminer, tracerProvider } = {}) {
25
25
  validateCryptoKey(identity.privateKey);
26
26
  async function load(url, options) {
27
- await validateUrl(url);
27
+ await validateUrl(url, options);
28
28
  return getRemoteDocument(url, await doubleKnock(createActivityPubRequest(url, { userAgent }), identity, {
29
29
  maxRedirection,
30
30
  specDeterminer,
31
31
  log: curry(logRequest)(logger$1),
32
32
  tracerProvider,
33
33
  signal: options?.signal,
34
- validateRedirect: validateUrl
35
- }), load);
34
+ validateRedirect: (url) => validateUrl(url, options)
35
+ }), load, options);
36
36
  }
37
- async function validateUrl(url) {
37
+ async function validateUrl(url, options) {
38
38
  if (!allowPrivateAddress) try {
39
39
  await validatePublicUrl(url);
40
40
  } catch (error) {
41
- if (error instanceof UrlError) logger$1.error("Disallowed private URL: {url}", {
41
+ if (error instanceof UrlError) logger$1[options?.suppressError ? "warn" : "error"]("Disallowed private URL: {url}", {
42
42
  url,
43
43
  error
44
44
  });
@@ -1,7 +1,7 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { p as recordDocumentCache } from "./metrics-em_7lwhG.mjs";
4
+ import { p as recordDocumentCache } from "./metrics-DDE9S63Q.mjs";
5
5
  import { getLogger } from "@logtape/logtape";
6
6
  import { preloadedContexts } from "@fedify/vocab-runtime";
7
7
  //#region src/utils/kv-cache.ts
@@ -1,9 +1,9 @@
1
1
  import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-CCABdw7r.mjs";
5
- import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-em_7lwhG.mjs";
6
- import { c as validateCryptoKey, i as fetchKey, t as bypassKeyCacheReads } from "./key-CkNxztNV.mjs";
4
+ import { n as version, t as name } from "./deno-Be9xoxps.mjs";
5
+ import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-DDE9S63Q.mjs";
6
+ import { c as validateCryptoKey, i as fetchKey, t as bypassKeyCacheReads } from "./key-Cc1b6pW9.mjs";
7
7
  import { getLogger } from "@logtape/logtape";
8
8
  import { Activity, CryptographicKey, Object as Object$1, getTypeId } from "@fedify/vocab";
9
9
  import { SpanStatusCode, trace } from "@opentelemetry/api";
@@ -1,7 +1,7 @@
1
1
  import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-CCABdw7r.mjs";
4
+ import { n as version, t as name } from "./deno-Be9xoxps.mjs";
5
5
  import { metrics } from "@opentelemetry/api";
6
6
  import { FetchError } from "@fedify/vocab-runtime";
7
7
  //#region src/federation/metrics.ts