@fedify/fedify 2.4.0-dev.2108 → 2.4.0-dev.2122
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{builder-BRlYEsrV.mjs → builder-BCXywsZF.mjs} +4 -4
- package/dist/compat/mod.d.cts +1 -1
- package/dist/compat/mod.d.ts +1 -1
- package/dist/compat/transformers.test.mjs +1 -1
- package/dist/{compound-proof-CHGINiJg.mjs → compound-proof-Fxd45Oy5.mjs} +3 -3
- package/dist/{context-Cqx9M_zO.d.cts → context-SMNOiiY6.d.cts} +1 -1
- package/dist/{context-B4F4kGJq.d.ts → context-tOiJfBoD.d.ts} +1 -1
- package/dist/{deno-CC4RKEEp.mjs → deno-BZrnW0Qz.mjs} +1 -1
- package/dist/{docloader-D3_m98fe.mjs → docloader-3BHaS20Z.mjs} +2 -2
- package/dist/federation/builder.test.mjs +1 -1
- package/dist/federation/compound-fanout.test.mjs +3 -3
- package/dist/federation/handler-compound-proof.test.mjs +3 -3
- package/dist/federation/handler.test.mjs +5 -5
- package/dist/federation/hashlink-media.test.mjs +2 -2
- package/dist/federation/idempotency.test.mjs +2 -2
- package/dist/federation/keycache.test.mjs +67 -1
- package/dist/federation/metrics.test.mjs +1 -1
- package/dist/federation/middleware.test.mjs +7 -7
- package/dist/federation/mod.cjs +1 -1
- package/dist/federation/mod.d.cts +2 -2
- package/dist/federation/mod.d.ts +2 -2
- package/dist/federation/mod.js +1 -1
- package/dist/federation/outgoing-proof.test.mjs +5 -5
- package/dist/federation/portable-actor.test.mjs +52 -5
- package/dist/federation/portable-gateway-keys.test.mjs +24 -7
- package/dist/federation/portable-inbox.test.mjs +4 -4
- package/dist/federation/portable.test.mjs +127 -30
- package/dist/federation/send.test.mjs +3 -3
- package/dist/federation/tasks/enqueue.test.mjs +1 -1
- package/dist/federation/tasks/tasks.test.mjs +2 -2
- package/dist/federation/temporal.test.mjs +1 -1
- package/dist/federation/webfinger.test.mjs +1 -1
- package/dist/{federation-dqo6ZzgT.cjs → federation-Ml6NrgRV.cjs} +98 -18
- package/dist/{http-B7wEqv49.cjs → http-4bmgw-mA.cjs} +140 -55
- package/dist/{http-CHEYA5fH.mjs → http-B9BQDNIy.mjs} +6 -12
- package/dist/{http-CaUBCHYJ.js → http-BETASGl0.js} +140 -55
- package/dist/{http-xlG-jRdB.d.cts → http-BrQgkJay.d.ts} +12 -7
- package/dist/{http-n2v6DPxu.d.ts → http-D76Py0M4.d.cts} +12 -7
- package/dist/{key-B8u1vdvF.mjs → key-PyYvun0F.mjs} +107 -16
- package/dist/{keycache-BUDHB5rY.mjs → keycache-BInaECGj.mjs} +68 -0
- package/dist/{kv-cache-ibncHN1D.js → kv-cache-C4mv2ZFQ.js} +1 -1
- package/dist/{kv-cache-CTdvf_Iq.mjs → kv-cache-CPqUxRL3.mjs} +1 -1
- package/dist/{kv-cache-0pYMPrkL.cjs → kv-cache-D2Bz6t_c.cjs} +1 -1
- package/dist/{ld-BTMJHEGV.mjs → ld-u4JOD4iO.mjs} +4 -7
- package/dist/{metrics-CF1TEvdS.mjs → metrics-DCLoLvLz.mjs} +1 -1
- package/dist/{middleware-DCMTJT3_.mjs → middleware-B7bmtONC.mjs} +1 -1
- package/dist/{middleware-B-rZ7oE-.mjs → middleware-CgOqRkK-.mjs} +49 -37
- package/dist/{middleware-Dl-Y_ZlC.js → middleware-DWlAGlyP.js} +106 -26
- package/dist/{mod-4s_XgF17.d.ts → mod-COWgSo2v.d.ts} +1 -1
- package/dist/{mod-Coacostr.d.ts → mod-CTh7ijx5.d.ts} +1 -1
- package/dist/{mod-xGFCHqq_.d.cts → mod-Cr79cQlc.d.cts} +1 -1
- package/dist/{mod-CVZrXSdm.d.ts → mod-Cwdt_CWU.d.ts} +1 -1
- package/dist/{mod-C8LieBzF.d.cts → mod-DMdGFMEl.d.cts} +1 -1
- package/dist/{mod-BuFmStNT.d.cts → mod-DnElMslb.d.cts} +1 -1
- package/dist/mod.cjs +5 -5
- package/dist/mod.d.cts +5 -5
- package/dist/mod.d.ts +5 -5
- package/dist/mod.js +5 -5
- package/dist/nodeinfo/handler.test.mjs +1 -1
- package/dist/{owner-CY--JSqO.mjs → owner-CPFM-sqo.mjs} +4 -4
- package/dist/{owner-CGnHPl4k.cjs → owner-DkA0Lz7v.cjs} +1 -1
- package/dist/{owner-M8dOKEl6.js → owner-wNinn7RK.js} +1 -1
- package/dist/{portable-key-C0-ghkgM.mjs → portable-key-DyylakVV.mjs} +5 -4
- package/dist/{portable-key-id-Bva5iT3W.mjs → portable-key-id-T7lecIbt.mjs} +3 -2
- package/dist/{proof-DDw0i9b1.mjs → proof-BCOg7Njv.mjs} +32 -31
- package/dist/{send-CU3sHGPt.mjs → send-C8eOPat3.mjs} +3 -3
- package/dist/sig/compound-proof-discovery.test.mjs +1 -1
- package/dist/sig/compound-proof-producer.test.mjs +1 -1
- package/dist/sig/compound-proof-verification.test.mjs +2 -2
- package/dist/sig/compound-proof.test.mjs +2 -2
- package/dist/sig/http.test.mjs +2 -2
- package/dist/sig/key.test.mjs +1 -1
- package/dist/sig/ld.test.mjs +2 -2
- package/dist/sig/mod.cjs +3 -3
- package/dist/sig/mod.d.cts +2 -2
- package/dist/sig/mod.d.ts +2 -2
- package/dist/sig/mod.js +3 -3
- package/dist/sig/owner.test.mjs +1 -1
- package/dist/sig/portable-collection.test.mjs +2 -2
- package/dist/sig/portable-key.test.mjs +301 -23
- package/dist/sig/proof.test.mjs +3 -3
- package/dist/{sig-fycq7sOr.js → sig-C1xOA4ml.js} +2 -2
- package/dist/{sig-C2CORtvg.cjs → sig-Cb9VSeiF.cjs} +2 -2
- package/dist/{temporal-NHiSpGcW.mjs → temporal-vjHOhBpy.mjs} +1 -1
- package/dist/testing/kv.test.mjs +1 -1
- package/dist/utils/docloader.test.mjs +2 -2
- package/dist/utils/kv-cache.test.mjs +1 -1
- package/dist/utils/mod.cjs +1 -1
- package/dist/utils/mod.d.cts +1 -1
- package/dist/utils/mod.d.ts +1 -1
- package/dist/utils/mod.js +1 -1
- package/package.json +7 -7
|
@@ -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-
|
|
12
|
-
import { l as signRequest } from "../http-
|
|
11
|
+
import { i as signObject, s as verifyPortableObjectProof } from "../proof-BCOg7Njv.mjs";
|
|
12
|
+
import { l as signRequest } from "../http-B9BQDNIy.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-
|
|
14
|
+
import { b as PORTABLE_OBJECT_CONTENT_TYPE, o as createFederation } from "../middleware-CgOqRkK-.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
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
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
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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-
|
|
13
|
+
import { d as verifyRequest } from "../http-B9BQDNIy.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-
|
|
16
|
-
import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "../send-
|
|
15
|
+
import { t as doesActorOwnKey } from "../owner-CPFM-sqo.mjs";
|
|
16
|
+
import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "../send-C8eOPat3.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";
|
|
@@ -4,7 +4,7 @@ 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
6
|
import "../../testing-BWq1dCzx.mjs";
|
|
7
|
-
import { o as createFederation } from "../../middleware-
|
|
7
|
+
import { o as createFederation } from "../../middleware-CgOqRkK-.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";
|
|
@@ -4,8 +4,8 @@ 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
6
|
import "../../testing-BWq1dCzx.mjs";
|
|
7
|
-
import { o as createFederation } from "../../middleware-
|
|
8
|
-
import { r as createFederationBuilder } from "../../builder-
|
|
7
|
+
import { o as createFederation } from "../../middleware-CgOqRkK-.mjs";
|
|
8
|
+
import { r as createFederationBuilder } from "../../builder-BCXywsZF.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-
|
|
7
|
+
import { t as hasMalformedKnownTemporalLiteral } from "../temporal-vjHOhBpy.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 () => {
|
|
@@ -6,7 +6,7 @@ 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-
|
|
9
|
+
import { o as createFederation, s as handleWebFinger } from "../middleware-CgOqRkK-.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-
|
|
4
|
+
const require_http = require("./http-4bmgw-mA.cjs");
|
|
5
5
|
const require_types = require("./types-Be2zbyeG.cjs");
|
|
6
|
-
const require_owner = require("./owner-
|
|
7
|
-
const require_kv_cache = require("./kv-cache-
|
|
6
|
+
const require_owner = require("./owner-DkA0Lz7v.cjs");
|
|
7
|
+
const require_kv_cache = require("./kv-cache-D2Bz6t_c.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 (
|
|
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,
|
|
@@ -2236,6 +2236,11 @@ async function routeActivity({ context: ctx, json, originalJson, normalizedActiv
|
|
|
2236
2236
|
//#region src/federation/keycache.ts
|
|
2237
2237
|
const Temporal$9 = require_http.Temporal;
|
|
2238
2238
|
const KEY_CACHE_GENERATION = "2";
|
|
2239
|
+
const COMPATIBLE_KEY_SEGMENT = "__compatible";
|
|
2240
|
+
const MAX_COMPATIBLE_KEY_TTL = Temporal$9.Duration.from({ hours: 1 });
|
|
2241
|
+
function isCompatibleKeyEntry(value) {
|
|
2242
|
+
return value != null && typeof value === "object" && "key" in value && "expires" in value && typeof value.expires === "number";
|
|
2243
|
+
}
|
|
2239
2244
|
var KvKeyCache = class {
|
|
2240
2245
|
kv;
|
|
2241
2246
|
prefix;
|
|
@@ -2298,6 +2303,69 @@ var KvKeyCache = class {
|
|
|
2298
2303
|
const serialized = await key.toJsonLd(this.options);
|
|
2299
2304
|
await this.kv.set(this.#entryKey(keyId), serialized, { ttl: this.keyTtl });
|
|
2300
2305
|
}
|
|
2306
|
+
/**
|
|
2307
|
+
* Returns the namespace of keys at FEP-ef61 compatible identifiers looked
|
|
2308
|
+
* up for the given purpose. Its entries carry the time they expire, which
|
|
2309
|
+
* is checked when they are read, so that an entry is never used after
|
|
2310
|
+
* the proof that vouched for its key expired, however late the underlying
|
|
2311
|
+
* store evicts it.
|
|
2312
|
+
* @param scope The purpose of the lookups.
|
|
2313
|
+
* @returns The namespace.
|
|
2314
|
+
* @internal
|
|
2315
|
+
*/
|
|
2316
|
+
compatibleKeyScope(scope) {
|
|
2317
|
+
const now = this.options.now ?? (() => Temporal$9.Now.instant());
|
|
2318
|
+
const entryKey = (keyId) => [
|
|
2319
|
+
...this.prefix,
|
|
2320
|
+
COMPATIBLE_KEY_SEGMENT,
|
|
2321
|
+
scope,
|
|
2322
|
+
keyId.href
|
|
2323
|
+
];
|
|
2324
|
+
const isExpired = (entry) => entry.expires <= now().epochMilliseconds;
|
|
2325
|
+
const keyTtl = Math.min(this.keyTtl.total("millisecond"), MAX_COMPATIBLE_KEY_TTL.total("millisecond"));
|
|
2326
|
+
const unavailableKeyTtl = this.unavailableKeyTtl.total("millisecond");
|
|
2327
|
+
return {
|
|
2328
|
+
get: async (keyId) => {
|
|
2329
|
+
const entry = await this.kv.get(entryKey(keyId));
|
|
2330
|
+
if (entry === void 0) return void 0;
|
|
2331
|
+
if (!isCompatibleKeyEntry(entry) || isExpired(entry)) {
|
|
2332
|
+
await this.kv.delete(entryKey(keyId));
|
|
2333
|
+
return;
|
|
2334
|
+
}
|
|
2335
|
+
if (entry.key === null) return null;
|
|
2336
|
+
let key;
|
|
2337
|
+
try {
|
|
2338
|
+
key = await _fedify_vocab.CryptographicKey.fromJsonLd(entry.key, this.options);
|
|
2339
|
+
} catch {
|
|
2340
|
+
try {
|
|
2341
|
+
key = await _fedify_vocab.Multikey.fromJsonLd(entry.key, this.options);
|
|
2342
|
+
} catch {
|
|
2343
|
+
await this.kv.delete(entryKey(keyId));
|
|
2344
|
+
return;
|
|
2345
|
+
}
|
|
2346
|
+
}
|
|
2347
|
+
return isExpired(entry) ? void 0 : key;
|
|
2348
|
+
},
|
|
2349
|
+
set: async (keyId, key, options) => {
|
|
2350
|
+
const serialized = key == null ? null : await key.toJsonLd(this.options);
|
|
2351
|
+
const current = now();
|
|
2352
|
+
let expires = current.epochMilliseconds + (key == null ? unavailableKeyTtl : keyTtl);
|
|
2353
|
+
if (options?.expires != null) expires = Math.min(expires, options.expires.epochMilliseconds);
|
|
2354
|
+
const ttl = expires - current.epochMilliseconds;
|
|
2355
|
+
if (ttl <= 0) {
|
|
2356
|
+
await this.kv.delete(entryKey(keyId));
|
|
2357
|
+
return;
|
|
2358
|
+
}
|
|
2359
|
+
await this.kv.set(entryKey(keyId), {
|
|
2360
|
+
key: serialized,
|
|
2361
|
+
expires
|
|
2362
|
+
}, { ttl: Temporal$9.Duration.from({ milliseconds: ttl }) });
|
|
2363
|
+
},
|
|
2364
|
+
delete: async (keyId) => {
|
|
2365
|
+
await this.kv.delete(entryKey(keyId));
|
|
2366
|
+
}
|
|
2367
|
+
};
|
|
2368
|
+
}
|
|
2301
2369
|
async getFetchError(keyId) {
|
|
2302
2370
|
const cached = await this.kv.get(this.#getFetchErrorKey(keyId));
|
|
2303
2371
|
if (cached == null || typeof cached !== "object") return void 0;
|
|
@@ -3468,6 +3536,11 @@ async function handleObject(request, { values, context, objectDispatcher, author
|
|
|
3468
3536
|
* dispatcher, or the actor dispatcher adapted to one. The object is served
|
|
3469
3537
|
* only if its ID canonically matches the requested portable ID and it
|
|
3470
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`.
|
|
3471
3544
|
* @template TContextData The context data to pass to the context.
|
|
3472
3545
|
* @param request The HTTP request.
|
|
3473
3546
|
* @param parameters The parameters for handling the portable object.
|
|
@@ -3515,17 +3588,27 @@ async function handlePortableObject(request, { values, context, objectDispatcher
|
|
|
3515
3588
|
logger.debug("The serialized object does not match the requested portable object {portableId}.", { portableId: canonicalId });
|
|
3516
3589
|
return await onNotFound(request);
|
|
3517
3590
|
}
|
|
3518
|
-
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
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
|
+
}
|
|
3524
3604
|
}
|
|
3525
|
-
return new Response(request.method === "HEAD" ? null : JSON.stringify(jsonLd), {
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
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
|
+
});
|
|
3529
3612
|
}
|
|
3530
3613
|
function isRequestedPortableObject(object, canonicalId) {
|
|
3531
3614
|
return object.id != null && require_http.getCanonicalPortableId(object.id) === canonicalId;
|
|
@@ -8320,10 +8403,7 @@ var FederationImpl = class extends FederationBuilderImpl {
|
|
|
8320
8403
|
return await handlePortableObject(request, {
|
|
8321
8404
|
values: { identifier },
|
|
8322
8405
|
context,
|
|
8323
|
-
objectDispatcher: actorDispatcher == null ? void 0 :
|
|
8324
|
-
const actor = await actorDispatcher(ctx, identifier);
|
|
8325
|
-
return actor instanceof _fedify_vocab.Tombstone ? null : actor;
|
|
8326
|
-
},
|
|
8406
|
+
objectDispatcher: actorDispatcher == null ? void 0 : (ctx) => actorDispatcher(ctx, identifier),
|
|
8327
8407
|
authorizePredicate: authorizePredicate == null ? void 0 : (ctx) => authorizePredicate(ctx, identifier),
|
|
8328
8408
|
canonicalId: portable.canonicalId,
|
|
8329
8409
|
onUnauthorized,
|