@fedify/fedify 2.4.0-pr.936.41 → 2.5.0-dev.2271
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/README.md +50 -39
- package/dist/{assert-CJ1KXcvv.mjs → assert-D1Eafmd_.mjs} +1 -1
- package/dist/{assert_equals-BV7oLMxm.mjs → assert_equals-Z6IcEbWU.mjs} +2 -28
- package/dist/assert_exists-cffSpJ7X.mjs +28 -0
- package/dist/assert_false-CPdRnc3E.mjs +23 -0
- package/dist/assert_greater_or_equal-BlafOF4-.mjs +31 -0
- package/dist/{assert_instance_of-Q9KTBnrf.mjs → assert_instance_of-mrXWWNY-.mjs} +1 -1
- package/dist/{assert_not_equals-EPJPoxuf.mjs → assert_not_equals-CpPK2t89.mjs} +2 -1
- package/dist/assert_rejects-CTptdYck.mjs +39 -0
- package/dist/{assert_strict_equals-Ec20AUwa.mjs → assert_strict_equals-CYvBmkpC.mjs} +2 -1
- package/dist/{assert_throws-NiFfqbnH.mjs → assert_throws-BYITOvPO.mjs} +2 -1
- package/dist/assertion_error-B-cx0fFA.mjs +31 -0
- package/dist/audience-CHQ-FlLD.mjs +95 -0
- package/dist/{builder-DonuxRl_.mjs → builder-Bda6LK7A.mjs} +169 -14
- package/dist/{circuit-breaker-CBGyz5F7.mjs → circuit-breaker-XJ53md4S.mjs} +224 -17
- package/dist/circuit-breaker-test-utils-DW6YC_Pe.mjs +14 -0
- package/dist/{client-CjF5IYQS.mjs → client-CEnNz8O7.mjs} +144 -8
- package/dist/{codec-BBga-FWI.mjs → codec-C6rTrpIp.mjs} +26 -3
- package/dist/{collection-BIhlldqK.mjs → collection-CnnP1UAv.mjs} +1 -1
- package/dist/compat/mod.cjs +1 -1
- package/dist/compat/mod.d.cts +1 -1
- package/dist/compat/mod.d.ts +1 -1
- package/dist/compat/mod.js +1 -1
- package/dist/compat/outgoing-jsonld.test.mjs +71 -3
- package/dist/compat/public-audience.test.mjs +14 -5
- package/dist/compat/secured-document.test.d.mts +2 -0
- package/dist/compat/secured-document.test.mjs +58 -0
- package/dist/compat/transformers.test.mjs +5 -5
- package/dist/compound-proof-B-9mXxFW.mjs +989 -0
- package/dist/{context-Zii1f5xS.d.ts → context-7NyiqIcg.d.ts} +1600 -25
- package/dist/{context-BJXxPElm.mjs → context-8Tu9kea0.mjs} +21 -63
- package/dist/{context-lxX6I7as.d.cts → context-tsYPJ2Ew.d.cts} +1600 -25
- package/dist/{deno-BHzKn9Ig.mjs → deno-oR5aJKpU.mjs} +1 -1
- package/dist/dist-DJQM4u7i.mjs +189 -0
- package/dist/docloader-DBoYbvYW.mjs +87 -0
- package/dist/{esm-yYQfPhIY.mjs → esm-BHXXx4Wx.mjs} +13 -6
- package/dist/federation/audience.test.d.mts +2 -0
- package/dist/federation/audience.test.mjs +130 -0
- package/dist/federation/builder.test.mjs +54 -5
- package/dist/federation/circuit-breaker.test.mjs +838 -7
- package/dist/federation/collection.test.mjs +3 -3
- package/dist/federation/compound-fanout.test.d.mts +2 -0
- package/dist/federation/compound-fanout.test.mjs +228 -0
- package/dist/federation/handler-compound-proof.test.d.mts +2 -0
- package/dist/federation/handler-compound-proof.test.mjs +384 -0
- package/dist/federation/handler.test.mjs +385 -15
- package/dist/federation/hashlink-media.test.d.mts +2 -0
- package/dist/federation/hashlink-media.test.mjs +422 -0
- package/dist/federation/idempotency.test.mjs +6 -6
- package/dist/federation/inbox-report.test.d.mts +2 -0
- package/dist/federation/inbox-report.test.mjs +475 -0
- package/dist/federation/inbox.test.mjs +3 -3
- package/dist/federation/keycache.test.mjs +215 -14
- package/dist/federation/kv.test.mjs +3 -3
- package/dist/federation/metrics.test.mjs +5 -5
- package/dist/federation/middleware.test.mjs +967 -35
- package/dist/federation/mod.cjs +18 -261
- package/dist/federation/mod.d.cts +5 -5
- package/dist/federation/mod.d.ts +5 -5
- package/dist/federation/mod.js +279 -1
- package/dist/federation/mq.test.mjs +123 -47
- package/dist/federation/negotiation.test.mjs +4 -4
- package/dist/federation/outgoing-proof.test.d.mts +2 -0
- package/dist/federation/outgoing-proof.test.mjs +931 -0
- package/dist/federation/portable-actor.test.d.mts +2 -0
- package/dist/federation/portable-actor.test.mjs +496 -0
- package/dist/federation/portable-chain.test.d.mts +2 -0
- package/dist/federation/portable-chain.test.mjs +370 -0
- package/dist/federation/portable-collection.test.d.mts +2 -0
- package/dist/federation/portable-collection.test.mjs +832 -0
- package/dist/federation/portable-delivery.test.d.mts +2 -0
- package/dist/federation/portable-delivery.test.mjs +746 -0
- package/dist/federation/portable-gateway-keys.test.d.mts +2 -0
- package/dist/federation/portable-gateway-keys.test.mjs +686 -0
- package/dist/federation/portable-inbox.test.d.mts +2 -0
- package/dist/federation/portable-inbox.test.mjs +1535 -0
- package/dist/federation/portable-parse-uri.test.d.mts +2 -0
- package/dist/federation/portable-parse-uri.test.mjs +348 -0
- package/dist/federation/portable-route-activity.test.d.mts +2 -0
- package/dist/federation/portable-route-activity.test.mjs +259 -0
- package/dist/federation/portable.test.d.mts +2 -0
- package/dist/federation/portable.test.mjs +1072 -0
- package/dist/federation/retry.test.mjs +4 -3
- package/dist/federation/router.test.mjs +9 -159
- package/dist/federation/send.test.mjs +245 -27
- package/dist/federation/tasks/codec.test.mjs +4 -4
- package/dist/federation/tasks/enqueue.test.mjs +42 -4
- package/dist/federation/tasks/tasks.test.mjs +7 -6
- package/dist/federation/temporal.test.mjs +4 -4
- package/dist/federation/webfinger.test.mjs +275 -12
- package/dist/{middleware-FrSMAYEx.cjs → federation-CkZiAcWY.cjs} +8640 -3722
- package/dist/http-B5JT115n.cjs +10630 -0
- package/dist/http-DydWCB7R.js +6527 -0
- package/dist/{http-CPVMUgsW.mjs → http-siciWkmx.mjs} +397 -168
- package/dist/key-BspPiXqn.mjs +951 -0
- package/dist/key-C1nv7_jD.d.ts +1125 -0
- package/dist/key-DYxuyd-s.d.cts +1125 -0
- package/dist/key-owner-evidence-APB6NpSk.mjs +108 -0
- package/dist/keycache-Da8A8mZa.mjs +200 -0
- package/dist/{kv-D6hNiMTK.d.ts → kv-B2mzK5tl.d.cts} +3 -2
- package/dist/{kv-gJ8LYbxX.d.cts → kv-B2mzK5tl.d.ts} +3 -2
- package/dist/kv-DBRWVx3Q.mjs +104 -0
- package/dist/{kv-cache-KyGqJqSp.mjs → kv-cache-BF1hnW65.mjs} +1 -1
- package/dist/{kv-cache-5bnX3-Jq.js → kv-cache-CTQr1tbn.js} +54 -16
- package/dist/{kv-cache-DQ29C4XL.cjs → kv-cache-DCac1nEU.cjs} +55 -19
- package/dist/{ld-Cj9F_5AP.mjs → ld-CzaP-hJl.mjs} +44 -12
- package/dist/map-local-context-conflict-DOYEEJ3u.mjs +204 -0
- package/dist/map-local-create-note-B0SeXJms.mjs +214 -0
- package/dist/{metrics-DMk-nTxI.mjs → metrics-CYg9yxBL.mjs} +3 -2
- package/dist/metrics-DVKYY6K1.d.cts +36 -0
- package/dist/metrics-DoRtI0qU.d.ts +36 -0
- package/dist/{middleware-Y-kpsp6S.js → middleware-CsDDXKuL.js} +5493 -1103
- package/dist/{middleware-CvwTUgnP.mjs → middleware-D7_LO-Vs.mjs} +3157 -272
- package/dist/{middleware-CEctxBN6.mjs → middleware-DWjrfpcz.mjs} +1 -1
- package/dist/{mod-gOlPDuT8.d.cts → mod-BojP2Bw3.d.cts} +1 -1
- package/dist/mod-CavhAgLw.d.cts +320 -0
- package/dist/mod-CtpF2BU5.d.ts +320 -0
- package/dist/{mod-BtEaIEcD.d.cts → mod-DEZXwV6J.d.ts} +10 -4
- package/dist/{mod-UMmveiO0.d.ts → mod-Dw-nPxQh.d.cts} +10 -4
- package/dist/{mod-DLc2Ot6f.d.ts → mod-Xj5P4s4d.d.ts} +1 -1
- package/dist/mod.cjs +40 -39
- package/dist/mod.d.cts +10 -11
- package/dist/mod.d.ts +10 -11
- package/dist/mod.js +9 -9
- package/dist/{mq-llHKivST.mjs → mq-Dm1Wmtm-.mjs} +6 -0
- package/dist/nodeinfo/client.test.mjs +139 -4
- package/dist/nodeinfo/handler.test.mjs +5 -5
- package/dist/nodeinfo/mod.cjs +1 -1
- package/dist/nodeinfo/mod.d.cts +1 -1
- package/dist/nodeinfo/mod.d.ts +1 -1
- package/dist/nodeinfo/mod.js +1 -1
- package/dist/nodeinfo/types.test.mjs +4 -4
- package/dist/otel/exporter.test.mjs +3 -3
- package/dist/otel/mod.cjs +0 -1
- package/dist/otel/mod.d.cts +1 -1
- package/dist/otel/mod.d.ts +1 -1
- package/dist/{outgoing-jsonld-URuqsICz.mjs → outgoing-jsonld-4IJqPYos.mjs} +12 -10
- package/dist/{owner-DEvZuyOE.d.cts → owner-BF0L9cMt.d.cts} +11 -0
- package/dist/{owner-CnngXDNJ.d.ts → owner-BSzr5-I8.d.ts} +11 -0
- package/dist/owner-DYgfTFHy.mjs +267 -0
- package/dist/portable-collection-BpsVsRov.js +670 -0
- package/dist/portable-collection-C74P1vtC.mjs +418 -0
- package/dist/portable-collection-DZd2bm7M.cjs +686 -0
- package/dist/portable-key-BIMLrAP4.mjs +549 -0
- package/dist/portable-key-id-Ca30-hOa.mjs +248 -0
- package/dist/proof-Dxy6ByqI.mjs +1299 -0
- package/dist/{public-audience-Cr5MsnMq.mjs → public-audience--klBIOk1.mjs} +15 -10
- package/dist/router-u5O5KCQr.mjs +156 -0
- package/dist/runtime/mod.cjs +1 -1
- package/dist/runtime/mod.js +1 -1
- package/dist/secured-document-CxzZZ9nL.mjs +35 -0
- package/dist/send-C4dDpAxm.mjs +495 -0
- package/dist/sig/accept.test.mjs +1 -1
- package/dist/sig/compound-proof-discovery.test.d.mts +2 -0
- package/dist/sig/compound-proof-discovery.test.mjs +331 -0
- package/dist/sig/compound-proof-producer.test.d.mts +2 -0
- package/dist/sig/compound-proof-producer.test.mjs +107 -0
- package/dist/sig/compound-proof-verification.test.d.mts +2 -0
- package/dist/sig/compound-proof-verification.test.mjs +833 -0
- package/dist/sig/compound-proof.test.d.mts +2 -0
- package/dist/sig/compound-proof.test.mjs +411 -0
- package/dist/sig/http.test.mjs +702 -31
- package/dist/sig/key-owner-evidence.test.d.mts +2 -0
- package/dist/sig/key-owner-evidence.test.mjs +100 -0
- package/dist/sig/key.test.mjs +220 -8
- package/dist/sig/ld.test.mjs +65 -7
- package/dist/sig/mitra-receive-interop.test.d.mts +2 -0
- package/dist/sig/mitra-receive-interop.test.mjs +260 -0
- package/dist/sig/mitra-send-interop.test.d.mts +2 -0
- package/dist/sig/mitra-send-interop.test.mjs +461 -0
- package/dist/sig/mod.cjs +18 -16
- package/dist/sig/mod.d.cts +4 -4
- package/dist/sig/mod.d.ts +4 -4
- package/dist/sig/mod.js +3 -3
- package/dist/sig/owner.test.mjs +108 -9
- package/dist/sig/portable-collection.test.d.mts +2 -0
- package/dist/sig/portable-collection.test.mjs +697 -0
- package/dist/sig/portable-interop.test.d.mts +2 -0
- package/dist/sig/portable-interop.test.mjs +156 -0
- package/dist/sig/portable-key-id.test.d.mts +2 -0
- package/dist/sig/portable-key-id.test.mjs +67 -0
- package/dist/sig/portable-key.test.d.mts +2 -0
- package/dist/sig/portable-key.test.mjs +1439 -0
- package/dist/sig/proof.test.mjs +1753 -16
- package/dist/sig/tootik-request-interop.test.d.mts +2 -0
- package/dist/sig/tootik-request-interop.test.mjs +190 -0
- package/dist/sig/tootik-send-interop.test.d.mts +2 -0
- package/dist/sig/tootik-send-interop.test.mjs +331 -0
- package/dist/sig/verification.test.d.mts +2 -0
- package/dist/sig/verification.test.mjs +310 -0
- package/dist/std__assert-DvMDRPes.mjs +8 -0
- package/dist/{testing-CO7ftO8A.mjs → tasks-IG3pHfvZ.mjs} +2 -2
- package/dist/{temporal-fsXO5IvD.mjs → temporal-D0KJI4NF.mjs} +3 -3
- package/dist/testing/context.test.d.mts +2 -0
- package/dist/testing/context.test.mjs +19 -0
- package/dist/testing/kv.test.d.mts +2 -0
- package/dist/testing/kv.test.mjs +101 -0
- package/dist/testing/mod.d.mts +1617 -48
- package/dist/testing/mod.mjs +6 -3
- package/dist/testing-t8xDeJZ5.mjs +8 -0
- package/dist/{transformers-DGwl-hD1.cjs → transformers-BalCrSsr.cjs} +0 -1
- package/dist/{types-DC1pNgJA.js → types-_S2_MeLF.js} +144 -8
- package/dist/{types-CPym83zc.cjs → types-poIao40f.cjs} +160 -7
- package/dist/utils/docloader.test.mjs +530 -8
- package/dist/utils/kv-cache.test.mjs +2 -2
- 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/dist/verification-C3954hQh.mjs +5 -0
- package/dist/verification-Dpfcw8ml.mjs +105 -0
- package/dist/vocab/cjs.test.mjs +1 -1
- package/package.json +8 -8
- package/dist/assert_rejects-EgstcQy5.mjs +0 -82
- package/dist/docloader-e1khNUKy.mjs +0 -49
- package/dist/http-CsmQ-BQ4.js +0 -2483
- package/dist/http-VyDTd4G3.d.cts +0 -553
- package/dist/http-hlIjmewS.cjs +0 -2694
- package/dist/http-lf8Hsd91.d.ts +0 -553
- package/dist/key-DfPsX8hv.mjs +0 -453
- package/dist/keycache-_DHqz5z_.mjs +0 -97
- package/dist/metrics-CoUJLQhZ.d.cts +0 -276
- package/dist/metrics-Dc4jR-IC.d.ts +0 -276
- package/dist/mod-B0hW12_O.d.cts +0 -307
- package/dist/mod-COIAjwRS.d.ts +0 -307
- package/dist/owner-Br9YYzn_.mjs +0 -120
- package/dist/proof-B4ngoaNQ.mjs +0 -316
- package/dist/proof-B6E0XUXJ.js +0 -1415
- package/dist/proof-NN4xCQGL.cjs +0 -1562
- package/dist/send-CxHKUNzv.mjs +0 -239
- package/dist/std__assert-B2NnY9bq.mjs +0 -79
- package/dist/temporal-polyfill-BCdTHexd.cjs +0 -3565
- /package/dist/{accept-8PiolDP7.mjs → accept-CPVqki78.mjs} +0 -0
- /package/dist/{activity-listener-BtTSxZWA.mjs → activity-listener-C-BSbKI7.mjs} +0 -0
- /package/dist/{client-CAM_bQXx.d.cts → client-D-XP_Ccb.d.cts} +0 -0
- /package/dist/{client-CSddvgWN.d.ts → client-D-XP_Ccb.d.ts} +0 -0
- /package/dist/{keys-Dbyd7bhl.mjs → keys-DIJNHW-Y.mjs} +0 -0
- /package/dist/{kv-CXBAok3k.mjs → kv-DCHkaJ9-.mjs} +0 -0
- /package/dist/{negotiation-RuwrPFqi.mjs → negotiation-CCocWT_N.mjs} +0 -0
- /package/dist/{retry-DgFqeS5k.mjs → retry-zX5xw5e0.mjs} +0 -0
- /package/dist/{chunk-D5IdezsZ.js → rolldown-runtime-BC-Eo5Mj.js} +0 -0
- /package/dist/{chunk-Czpeue4M.cjs → rolldown-runtime-Dmm3t92W.cjs} +0 -0
- /package/dist/{transformers-DXze6FUB.js → transformers-DAtXl_Kc.js} +0 -0
- /package/dist/{types-CrtdVuDP.mjs → types-DE_LV1JB.mjs} +0 -0
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
import { Temporal } from "temporal-polyfill";
|
|
2
|
+
import "urlpattern-polyfill";
|
|
3
|
+
globalThis.addEventListener = () => {};
|
|
4
|
+
import { t as MemoryKvStore } from "../kv-DCHkaJ9-.mjs";
|
|
5
|
+
import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
|
|
6
|
+
import { t as assertFalse } from "../assert_false-CPdRnc3E.mjs";
|
|
7
|
+
import { t as assertInstanceOf } from "../assert_instance_of-mrXWWNY-.mjs";
|
|
8
|
+
import { t as assertThrows } from "../assert_throws-BYITOvPO.mjs";
|
|
9
|
+
import "../std__assert-DvMDRPes.mjs";
|
|
10
|
+
import { t as assert } from "../assert-D1Eafmd_.mjs";
|
|
11
|
+
import { a as signObject, c as verifyPortableObjectProof } from "../proof-Dxy6ByqI.mjs";
|
|
12
|
+
import { f as verifyRequest, l as signRequest } from "../http-siciWkmx.mjs";
|
|
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-D7_LO-Vs.mjs";
|
|
15
|
+
import { n as RouterError } from "../router-u5O5KCQr.mjs";
|
|
16
|
+
import { configure, reset } from "@logtape/logtape";
|
|
17
|
+
import { Note, Person, Tombstone, lookupObject } from "@fedify/vocab";
|
|
18
|
+
import { exportDidKey, formatIri, fromCompatibleEf61Id, toCompatibleEf61Id } from "@fedify/vocab-runtime";
|
|
19
|
+
import { createTestMeterProvider, mockDocumentLoader, test } from "@fedify/fixture";
|
|
20
|
+
//#region src/federation/portable-actor.test.ts
|
|
21
|
+
const did = await exportDidKey(ed25519PublicKey.publicKey);
|
|
22
|
+
const keyId = new URL(`${did}#${did.slice(8)}`);
|
|
23
|
+
const otherKeyPair = await crypto.subtle.generateKey("Ed25519", true, ["sign", "verify"]);
|
|
24
|
+
const otherDid = await exportDidKey(otherKeyPair.publicKey);
|
|
25
|
+
const otherKeyId = new URL(`${otherDid}#${otherDid.slice(8)}`);
|
|
26
|
+
const ORIGIN = "https://example.com";
|
|
27
|
+
const ACCEPT = "application/activity+json";
|
|
28
|
+
function gatewayUrl(path, authority = did) {
|
|
29
|
+
return `${ORIGIN}/.well-known/apgateway/${authority}${path}`;
|
|
30
|
+
}
|
|
31
|
+
function gatewayRequest(path, init = {}) {
|
|
32
|
+
const { authority, ...rest } = init;
|
|
33
|
+
return new Request(gatewayUrl(path, authority), {
|
|
34
|
+
...rest,
|
|
35
|
+
headers: {
|
|
36
|
+
Accept: ACCEPT,
|
|
37
|
+
...rest.headers
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
async function sign(object, privateKey = ed25519PrivateKey, key = keyId) {
|
|
42
|
+
return await signObject(object, privateKey, key, { contextLoader: mockDocumentLoader });
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The DIDs of the users that this server hosts, which the actor dispatcher
|
|
46
|
+
* looks up by identifier, like an application would in its database.
|
|
47
|
+
*/
|
|
48
|
+
const users = {
|
|
49
|
+
alice: did,
|
|
50
|
+
instance: did,
|
|
51
|
+
unsigned: did,
|
|
52
|
+
"wrong-key": did,
|
|
53
|
+
tampered: did,
|
|
54
|
+
https: did,
|
|
55
|
+
tombstone: did,
|
|
56
|
+
"signed-tombstone": did,
|
|
57
|
+
"wrong-key-tombstone": did,
|
|
58
|
+
"mutated-id": did,
|
|
59
|
+
compatible: did,
|
|
60
|
+
"a b/ç": did
|
|
61
|
+
};
|
|
62
|
+
async function portableActor(ctx, identifier, authority) {
|
|
63
|
+
const keys = await ctx.getActorKeyPairs(identifier);
|
|
64
|
+
return new Person({
|
|
65
|
+
id: ctx.getPortableActorUri(identifier, authority),
|
|
66
|
+
preferredUsername: identifier,
|
|
67
|
+
inbox: ctx.getPortableInboxUri(identifier, authority),
|
|
68
|
+
gateways: [new URL(ORIGIN)],
|
|
69
|
+
publicKeys: keys.map((key) => key.cryptographicKey),
|
|
70
|
+
assertionMethods: keys.map((key) => key.multikey)
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
function createTestFederation(options = {}) {
|
|
74
|
+
const federation = createFederation({
|
|
75
|
+
kv: new MemoryKvStore(),
|
|
76
|
+
documentLoaderFactory: () => mockDocumentLoader,
|
|
77
|
+
contextLoaderFactory: () => mockDocumentLoader,
|
|
78
|
+
...options
|
|
79
|
+
});
|
|
80
|
+
const contexts = [];
|
|
81
|
+
const setters = federation.setActorDispatcher("/users/{identifier}", async (ctx, identifier) => {
|
|
82
|
+
contexts.push(ctx);
|
|
83
|
+
const userDid = users[identifier];
|
|
84
|
+
if (userDid == null) return null;
|
|
85
|
+
if (ctx.portableRequest != null && ctx.portableRequest.authority !== userDid) return null;
|
|
86
|
+
switch (identifier) {
|
|
87
|
+
case "https": return new Person({ id: ctx.getActorUri(identifier) });
|
|
88
|
+
case "compatible": {
|
|
89
|
+
const actor = await portableActor(ctx, identifier, userDid);
|
|
90
|
+
return await sign(actor.clone({ id: toCompatibleEf61Id(actor.id, ORIGIN) }));
|
|
91
|
+
}
|
|
92
|
+
case "tombstone": return new Tombstone({ id: ctx.getPortableActorUri(identifier, userDid) });
|
|
93
|
+
case "signed-tombstone": return await sign(new Tombstone({
|
|
94
|
+
id: ctx.getPortableActorUri(identifier, userDid),
|
|
95
|
+
formerType: Person,
|
|
96
|
+
deleted: Temporal.Instant.from("2026-01-01T00:00:00Z")
|
|
97
|
+
}));
|
|
98
|
+
case "wrong-key-tombstone": return await sign(new Tombstone({ id: ctx.getPortableActorUri(identifier, userDid) }), otherKeyPair.privateKey, otherKeyId);
|
|
99
|
+
case "unsigned": return await portableActor(ctx, identifier, userDid);
|
|
100
|
+
case "wrong-key": return await sign(await portableActor(ctx, identifier, userDid), otherKeyPair.privateKey, otherKeyId);
|
|
101
|
+
case "tampered": return (await sign(await portableActor(ctx, identifier, userDid))).clone({ name: "Tampered" });
|
|
102
|
+
case "mutated-id": {
|
|
103
|
+
const signed = await sign(await portableActor(ctx, "alice", userDid));
|
|
104
|
+
const actor = await Person.fromJsonLd(await signed.toJsonLd({ contextLoader: mockDocumentLoader }), {
|
|
105
|
+
contextLoader: mockDocumentLoader,
|
|
106
|
+
documentLoader: mockDocumentLoader
|
|
107
|
+
});
|
|
108
|
+
actor.id.pathname = "/users/mutated-id";
|
|
109
|
+
return actor;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return await sign(await portableActor(ctx, identifier, userDid));
|
|
113
|
+
}).setKeyPairsDispatcher(() => [{
|
|
114
|
+
privateKey: rsaPrivateKey2,
|
|
115
|
+
publicKey: rsaPublicKey2.publicKey
|
|
116
|
+
}]).mapPortableActorId((ctx, identifier) => {
|
|
117
|
+
const userDid = users[identifier];
|
|
118
|
+
return userDid == null ? null : ctx.getPortableActorUri(identifier, userDid);
|
|
119
|
+
}).mapHandle((_ctx, username) => username).mapActorAlias("/actor", "instance");
|
|
120
|
+
federation.setInboxListeners("/users/{identifier}/inbox", "/inbox");
|
|
121
|
+
return {
|
|
122
|
+
federation,
|
|
123
|
+
setters,
|
|
124
|
+
contexts
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
test("Context.getPortableActorUri()", async (t) => {
|
|
128
|
+
const { federation } = createTestFederation();
|
|
129
|
+
const ctx = federation.createContext(new URL(`${ORIGIN}/`), void 0);
|
|
130
|
+
await t.step("builds a portable ID from the actor path", () => {
|
|
131
|
+
const uri = ctx.getPortableActorUri("alice", did);
|
|
132
|
+
assertInstanceOf(uri, URL);
|
|
133
|
+
assertEquals(uri.protocol, "ap+ef61:");
|
|
134
|
+
assertEquals(formatIri(uri), `ap+ef61://${did}/users/alice`);
|
|
135
|
+
assertEquals(uri.href, `ap+ef61://${encodeURIComponent(did)}/users/alice`);
|
|
136
|
+
assertEquals(ctx.getActorUri("alice"), new URL(`${ORIGIN}/users/alice`));
|
|
137
|
+
});
|
|
138
|
+
await t.step("uses the same path as getActorUri()", () => {
|
|
139
|
+
for (const identifier of [
|
|
140
|
+
"alice",
|
|
141
|
+
"instance",
|
|
142
|
+
"a b/ç",
|
|
143
|
+
"100%"
|
|
144
|
+
]) assertEquals(ctx.getPortableActorUri(identifier, did).pathname, ctx.getActorUri(identifier).pathname, identifier);
|
|
145
|
+
assertEquals(formatIri(ctx.getPortableActorUri("instance", did)), `ap+ef61://${did}/actor`);
|
|
146
|
+
});
|
|
147
|
+
await t.step("does not mint a different ID for a dot segment", () => {
|
|
148
|
+
assertThrows(() => ctx.getPortableActorUri("..", did), TypeError);
|
|
149
|
+
});
|
|
150
|
+
await t.step("is serialized in the canonical form", async () => {
|
|
151
|
+
assertEquals((await new Person({ id: ctx.getPortableActorUri("alice", did) }).toJsonLd({ contextLoader: mockDocumentLoader })).id, `ap+ef61://${did}/users/alice`);
|
|
152
|
+
});
|
|
153
|
+
await t.step("converts to and from compatible identifiers", () => {
|
|
154
|
+
const uri = ctx.getPortableActorUri("a b/ç", did);
|
|
155
|
+
const compatible = toCompatibleEf61Id(uri, ORIGIN);
|
|
156
|
+
assertEquals(compatible.href, gatewayUrl(ctx.getActorUri("a b/ç").pathname));
|
|
157
|
+
assertEquals(fromCompatibleEf61Id(compatible)?.href, uri.href);
|
|
158
|
+
});
|
|
159
|
+
await t.step("requires a bare DID authority", () => {
|
|
160
|
+
for (const authority of [
|
|
161
|
+
`${did}/extra`,
|
|
162
|
+
`${did}?query`,
|
|
163
|
+
`${did}#fragment`,
|
|
164
|
+
"did:key:",
|
|
165
|
+
"https://example.com",
|
|
166
|
+
""
|
|
167
|
+
]) assertThrows(() => ctx.getPortableActorUri("alice", authority), TypeError, void 0, authority);
|
|
168
|
+
assertThrows(() => ctx.getPortableActorUri("alice"), TypeError);
|
|
169
|
+
});
|
|
170
|
+
await t.step("requires an authority outside gateway requests", () => {
|
|
171
|
+
const requestCtx = federation.createContext(new Request(`${ORIGIN}/`), void 0);
|
|
172
|
+
assertThrows(() => requestCtx.getPortableActorUri("alice"), TypeError);
|
|
173
|
+
assertEquals(formatIri(requestCtx.getPortableActorUri("alice", did)), `ap+ef61://${did}/users/alice`);
|
|
174
|
+
});
|
|
175
|
+
await t.step("requires an actor dispatcher", () => {
|
|
176
|
+
const emptyCtx = createFederation({ kv: new MemoryKvStore() }).createContext(new URL(`${ORIGIN}/`), void 0);
|
|
177
|
+
assertThrows(() => emptyCtx.getPortableActorUri("alice", did), RouterError);
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
test("Context portable ID helpers require base58-btc did:key DIDs", async (t) => {
|
|
181
|
+
const { federation } = createTestFederation();
|
|
182
|
+
federation.setObjectDispatcher(Note, "/notes/{id}", () => null);
|
|
183
|
+
const ctx = federation.createContext(new URL(`${ORIGIN}/`), void 0);
|
|
184
|
+
const helpers = {
|
|
185
|
+
getPortableActorUri: (authority) => ctx.getPortableActorUri("alice", authority),
|
|
186
|
+
getPortableObjectUri: (authority) => ctx.getPortableObjectUri(Note, { id: "1" }, authority),
|
|
187
|
+
getPortableInboxUri: (authority) => ctx.getPortableInboxUri("alice", authority)
|
|
188
|
+
};
|
|
189
|
+
const base64url = `did:key:u${did.slice(9)}`;
|
|
190
|
+
for (const [name, helper] of Object.entries(helpers)) await t.step(name, () => {
|
|
191
|
+
for (const authority of [
|
|
192
|
+
base64url,
|
|
193
|
+
base64url.replace("did:key:", "DID:KEY:"),
|
|
194
|
+
`did:key:%75${did.slice(9)}`,
|
|
195
|
+
"did:key:z",
|
|
196
|
+
"did:key:z0OIl"
|
|
197
|
+
]) assertThrows(() => helper(authority), TypeError, void 0, authority);
|
|
198
|
+
assertEquals(formatIri(helper(did)).startsWith(`ap+ef61://${did}/`), true);
|
|
199
|
+
assertEquals(formatIri(helper(did.replace("did:key:", "DID:KEY:"))), formatIri(helper(did)));
|
|
200
|
+
assertEquals(formatIri(helper("did:web:example.com")).startsWith("ap+ef61://did:web:example.com/"), true);
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
test("RequestContext.getPortableActorUri() in gateway requests", async () => {
|
|
204
|
+
const { federation, contexts } = createTestFederation();
|
|
205
|
+
assertEquals((await federation.fetch(gatewayRequest("/users/alice"), { contextData: void 0 })).status, 200);
|
|
206
|
+
const ctx = contexts[0];
|
|
207
|
+
assertEquals(ctx.portableRequest?.authority, did);
|
|
208
|
+
assertEquals(formatIri(ctx.getPortableActorUri("alice")), `ap+ef61://${did}/users/alice`);
|
|
209
|
+
assertEquals(formatIri(ctx.getPortableActorUri("alice", otherDid)), `ap+ef61://${otherDid}/users/alice`);
|
|
210
|
+
});
|
|
211
|
+
test("Federation.fetch() serves portable actors through the actor dispatcher", async (t) => {
|
|
212
|
+
const { federation, contexts } = createTestFederation();
|
|
213
|
+
await t.step("serves a portable actor", async () => {
|
|
214
|
+
contexts.length = 0;
|
|
215
|
+
const response = await federation.fetch(gatewayRequest("/users/alice"), { contextData: void 0 });
|
|
216
|
+
assertEquals(response.status, 200);
|
|
217
|
+
assertEquals(response.headers.get("Content-Type"), PORTABLE_OBJECT_CONTENT_TYPE);
|
|
218
|
+
const json = await response.json();
|
|
219
|
+
assertEquals(json.id, `ap+ef61://${did}/users/alice`);
|
|
220
|
+
assertEquals(json.inbox, `ap+ef61://${did}/users/alice/inbox`);
|
|
221
|
+
assertEquals((await verifyPortableObjectProof(json, { contextLoader: mockDocumentLoader })).verified, true);
|
|
222
|
+
assertEquals(contexts.length, 1);
|
|
223
|
+
assertEquals(contexts[0].portableRequest?.authority, did);
|
|
224
|
+
assertEquals(formatIri(contexts[0].portableRequest.id), `ap+ef61://${did}/users/alice`);
|
|
225
|
+
});
|
|
226
|
+
await t.step("serves an actor with a compatible identifier", async () => {
|
|
227
|
+
const response = await federation.fetch(gatewayRequest("/users/compatible"), { contextData: void 0 });
|
|
228
|
+
assertEquals(response.status, 200);
|
|
229
|
+
assertEquals((await response.json()).id, gatewayUrl("/users/compatible"));
|
|
230
|
+
});
|
|
231
|
+
await t.step("serves an actor with reserved characters", async () => {
|
|
232
|
+
const ctx = federation.createContext(new URL(`${ORIGIN}/`), void 0);
|
|
233
|
+
assertEquals((await federation.fetch(gatewayRequest(ctx.getActorUri("a b/ç").pathname), { contextData: void 0 })).status, 200);
|
|
234
|
+
});
|
|
235
|
+
await t.step("serves an actor alias", async () => {
|
|
236
|
+
let response = await federation.fetch(gatewayRequest("/actor"), { contextData: void 0 });
|
|
237
|
+
assertEquals(response.status, 200);
|
|
238
|
+
assertEquals((await response.json()).id, `ap+ef61://${did}/actor`);
|
|
239
|
+
response = await federation.fetch(gatewayRequest("/users/instance"), { contextData: void 0 });
|
|
240
|
+
assertEquals(response.status, 404);
|
|
241
|
+
});
|
|
242
|
+
await t.step("responds with 400 for non-base58-btc did:key DIDs", async () => {
|
|
243
|
+
contexts.length = 0;
|
|
244
|
+
const base64url = `did:key:u${did.slice(9)}`;
|
|
245
|
+
for (const method of ["GET", "POST"]) assertEquals((await federation.fetch(gatewayRequest(method === "GET" ? "/users/alice" : "/users/alice/inbox", {
|
|
246
|
+
method,
|
|
247
|
+
authority: base64url,
|
|
248
|
+
...method === "POST" ? { body: "{}" } : {}
|
|
249
|
+
}), { contextData: void 0 })).status, 400, method);
|
|
250
|
+
assertEquals(contexts.length, 0);
|
|
251
|
+
});
|
|
252
|
+
await t.step("responds to HEAD without a body", async () => {
|
|
253
|
+
const response = await federation.fetch(gatewayRequest("/users/alice", { method: "HEAD" }), { contextData: void 0 });
|
|
254
|
+
assertEquals(response.status, 200);
|
|
255
|
+
assertEquals(response.headers.get("Content-Type"), PORTABLE_OBJECT_CONTENT_TYPE);
|
|
256
|
+
assertEquals(response.body, null);
|
|
257
|
+
});
|
|
258
|
+
await t.step("responds with 406 without a JSON-LD Accept header", async () => {
|
|
259
|
+
let notAcceptable = false;
|
|
260
|
+
assertEquals((await federation.fetch(gatewayRequest("/users/alice", { headers: { Accept: "text/html" } }), {
|
|
261
|
+
contextData: void 0,
|
|
262
|
+
onNotAcceptable() {
|
|
263
|
+
notAcceptable = true;
|
|
264
|
+
return new Response("Not acceptable", { status: 406 });
|
|
265
|
+
}
|
|
266
|
+
})).status, 406);
|
|
267
|
+
assertEquals(notAcceptable, true);
|
|
268
|
+
});
|
|
269
|
+
await t.step("responds with 404 for actors it does not serve", async () => {
|
|
270
|
+
for (const request of [
|
|
271
|
+
gatewayRequest("/users/missing"),
|
|
272
|
+
gatewayRequest("/users/https"),
|
|
273
|
+
gatewayRequest("/users/tombstone"),
|
|
274
|
+
gatewayRequest("/users/tombstone", { method: "HEAD" }),
|
|
275
|
+
gatewayRequest("/users/alice", { authority: otherDid }),
|
|
276
|
+
gatewayRequest("/users/mutated-id")
|
|
277
|
+
]) {
|
|
278
|
+
let notFound = false;
|
|
279
|
+
assertEquals((await federation.fetch(request, {
|
|
280
|
+
contextData: void 0,
|
|
281
|
+
onNotFound() {
|
|
282
|
+
notFound = true;
|
|
283
|
+
return new Response("Not found", { status: 404 });
|
|
284
|
+
}
|
|
285
|
+
})).status, 404, request.url);
|
|
286
|
+
assertEquals(notFound, true, request.url);
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
await t.step("refuses actors that violate the proof policy", async () => {
|
|
290
|
+
for (const identifier of [
|
|
291
|
+
"unsigned",
|
|
292
|
+
"wrong-key",
|
|
293
|
+
"tampered",
|
|
294
|
+
"wrong-key-tombstone"
|
|
295
|
+
]) {
|
|
296
|
+
const response = await federation.fetch(gatewayRequest(`/users/${identifier}`), { contextData: void 0 });
|
|
297
|
+
assertEquals(response.status, 500, identifier);
|
|
298
|
+
assertEquals(await response.text(), "Internal server error.");
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
await t.step("serves a signed tombstone with 410 Gone", async () => {
|
|
302
|
+
const response = await federation.fetch(gatewayRequest("/users/signed-tombstone"), { contextData: void 0 });
|
|
303
|
+
assertEquals(response.status, 410);
|
|
304
|
+
assertEquals(response.headers.get("Content-Type"), PORTABLE_OBJECT_CONTENT_TYPE);
|
|
305
|
+
assertEquals(response.headers.get("Vary"), "Accept");
|
|
306
|
+
const json = await response.json();
|
|
307
|
+
assertEquals(json.type, "Tombstone");
|
|
308
|
+
assertEquals(json.id, `ap+ef61://${did}/users/signed-tombstone`);
|
|
309
|
+
assertEquals(json.deleted, "2026-01-01T00:00:00Z");
|
|
310
|
+
assertEquals((await verifyPortableObjectProof(json, { contextLoader: mockDocumentLoader })).verified, true);
|
|
311
|
+
});
|
|
312
|
+
await t.step("responds to HEAD for a tombstone without a body", async () => {
|
|
313
|
+
const response = await federation.fetch(gatewayRequest("/users/signed-tombstone", { method: "HEAD" }), { contextData: void 0 });
|
|
314
|
+
assertEquals(response.status, 410);
|
|
315
|
+
assertEquals(response.headers.get("Content-Type"), PORTABLE_OBJECT_CONTENT_TYPE);
|
|
316
|
+
assertEquals(response.body, null);
|
|
317
|
+
});
|
|
318
|
+
await t.step("keeps ordinary tombstone requests unchanged", async () => {
|
|
319
|
+
const response = await federation.fetch(new Request(`${ORIGIN}/users/signed-tombstone`, { headers: { Accept: ACCEPT } }), { contextData: void 0 });
|
|
320
|
+
assertEquals(response.status, 410);
|
|
321
|
+
assertEquals(response.headers.get("Content-Type"), "application/activity+json");
|
|
322
|
+
});
|
|
323
|
+
await t.step("keeps ordinary actor requests unchanged", async () => {
|
|
324
|
+
const response = await federation.fetch(new Request(`${ORIGIN}/users/https`, { headers: { Accept: ACCEPT } }), { contextData: void 0 });
|
|
325
|
+
assertEquals(response.status, 200);
|
|
326
|
+
assertEquals((await response.json()).id, `${ORIGIN}/users/https`);
|
|
327
|
+
});
|
|
328
|
+
await t.step("leaves POST requests to the actor path alone", async () => {
|
|
329
|
+
let notFound = false;
|
|
330
|
+
assertEquals((await federation.fetch(gatewayRequest("/users/alice", {
|
|
331
|
+
method: "POST",
|
|
332
|
+
body: "{}"
|
|
333
|
+
}), {
|
|
334
|
+
contextData: void 0,
|
|
335
|
+
onNotFound() {
|
|
336
|
+
notFound = true;
|
|
337
|
+
return new Response("Not found", { status: 404 });
|
|
338
|
+
}
|
|
339
|
+
})).status, 404);
|
|
340
|
+
assertEquals(notFound, true);
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
test("Federation.fetch() authorizes portable actor requests", async (t) => {
|
|
344
|
+
const { federation, setters } = createTestFederation();
|
|
345
|
+
const identifiers = [];
|
|
346
|
+
setters.authorize(async (ctx, identifier) => {
|
|
347
|
+
identifiers.push(identifier);
|
|
348
|
+
assertEquals(ctx.portableRequest?.authority, did);
|
|
349
|
+
return (await ctx.getSignedKeyOwner())?.id?.href === "https://example.com/person2";
|
|
350
|
+
});
|
|
351
|
+
await t.step("allows a signed request", async () => {
|
|
352
|
+
const request = await signRequest(gatewayRequest("/users/alice?x=1"), rsaPrivateKey3, rsaPublicKey3.id);
|
|
353
|
+
assertEquals((await federation.fetch(request, { contextData: void 0 })).status, 200);
|
|
354
|
+
assertEquals(identifiers, ["alice"]);
|
|
355
|
+
});
|
|
356
|
+
await t.step("denies an unsigned request", async () => {
|
|
357
|
+
let unauthorized = false;
|
|
358
|
+
assertEquals((await federation.fetch(gatewayRequest("/users/alice"), {
|
|
359
|
+
contextData: void 0,
|
|
360
|
+
onUnauthorized() {
|
|
361
|
+
unauthorized = true;
|
|
362
|
+
return new Response("Unauthorized", { status: 401 });
|
|
363
|
+
}
|
|
364
|
+
})).status, 401);
|
|
365
|
+
assertEquals(unauthorized, true);
|
|
366
|
+
});
|
|
367
|
+
await t.step("authorizes before serving a tombstone", async () => {
|
|
368
|
+
for (const identifier of ["signed-tombstone", "tombstone"]) {
|
|
369
|
+
identifiers.length = 0;
|
|
370
|
+
let unauthorized = false;
|
|
371
|
+
assertEquals((await federation.fetch(gatewayRequest(`/users/${identifier}`), {
|
|
372
|
+
contextData: void 0,
|
|
373
|
+
onUnauthorized() {
|
|
374
|
+
unauthorized = true;
|
|
375
|
+
return new Response("Unauthorized", { status: 401 });
|
|
376
|
+
}
|
|
377
|
+
})).status, 401, identifier);
|
|
378
|
+
assertEquals(unauthorized, true, identifier);
|
|
379
|
+
assertEquals(identifiers, [identifier]);
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
await t.step("denies a request whose target was tampered with", async () => {
|
|
383
|
+
const signed = await signRequest(gatewayRequest("/users/alice"), rsaPrivateKey3, rsaPublicKey3.id);
|
|
384
|
+
assertEquals((await federation.fetch(new Request(gatewayUrl("/actor"), { headers: signed.headers }), { contextData: void 0 })).status, 401);
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
test("Federation.fetch() serves portable actors found through WebFinger", async () => {
|
|
388
|
+
const { federation } = createTestFederation();
|
|
389
|
+
const webFinger = await federation.fetch(new Request(`${ORIGIN}/.well-known/webfinger?resource=acct:alice@example.com`), { contextData: void 0 });
|
|
390
|
+
assertEquals(webFinger.status, 200);
|
|
391
|
+
const jrd = await webFinger.json();
|
|
392
|
+
assertEquals(jrd.subject, "acct:alice@example.com");
|
|
393
|
+
assertEquals(jrd.links?.[0], {
|
|
394
|
+
rel: "self",
|
|
395
|
+
href: gatewayUrl("/users/alice"),
|
|
396
|
+
type: "application/activity+json"
|
|
397
|
+
});
|
|
398
|
+
const actor = await lookupObject(jrd.links[0].href, {
|
|
399
|
+
documentLoader: federationLoader(federation),
|
|
400
|
+
contextLoader: mockDocumentLoader,
|
|
401
|
+
verifyPortableObject: verifyPortableObjectProof
|
|
402
|
+
});
|
|
403
|
+
assertInstanceOf(actor, Person);
|
|
404
|
+
assertEquals(formatIri(actor.id), `ap+ef61://${did}/users/alice`);
|
|
405
|
+
});
|
|
406
|
+
function federationLoader(federation) {
|
|
407
|
+
return async (url) => {
|
|
408
|
+
const response = await federation.fetch(new Request(url.replace(/#.*$/, ""), { headers: { Accept: ACCEPT } }), { contextData: void 0 });
|
|
409
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}: ${url}`);
|
|
410
|
+
return {
|
|
411
|
+
contextUrl: null,
|
|
412
|
+
documentUrl: url,
|
|
413
|
+
document: await response.json()
|
|
414
|
+
};
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
test("Gateway keys of portable actors dereference to the actor dispatcher", async (t) => {
|
|
418
|
+
for (const [label, options] of [["default origin", {}], ["canonical origin", { origin: ORIGIN }]]) await t.step(label, async () => {
|
|
419
|
+
const { federation } = createTestFederation(options);
|
|
420
|
+
const ctx = federation.createContext(new URL("origin" in options ? "https://internal.example/" : `${ORIGIN}/`), void 0);
|
|
421
|
+
for (const identifier of ["alice", "instance"]) {
|
|
422
|
+
const [keyPair] = await ctx.getActorKeyPairs(identifier);
|
|
423
|
+
assertEquals(keyPair.keyId.href, `${toCompatibleEf61Id(ctx.getPortableActorUri(identifier, did), ORIGIN).href}#main-key`);
|
|
424
|
+
const key = await verifyRequest(await signRequest(new Request("https://recipient.example/inbox", {
|
|
425
|
+
method: "POST",
|
|
426
|
+
body: "{}",
|
|
427
|
+
headers: { "Content-Type": "application/activity+json" }
|
|
428
|
+
}), keyPair.privateKey, keyPair.keyId), {
|
|
429
|
+
documentLoader: federationLoader(federation),
|
|
430
|
+
contextLoader: mockDocumentLoader
|
|
431
|
+
});
|
|
432
|
+
assert(key != null, identifier);
|
|
433
|
+
assertEquals(key.id?.href, keyPair.keyId.href);
|
|
434
|
+
assertEquals(key.ownerId?.href, ctx.getPortableActorUri(identifier, did).href);
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
});
|
|
438
|
+
test("Federation.fetch() records metrics for portable actor requests", async () => {
|
|
439
|
+
const [meterProvider, recorder] = createTestMeterProvider();
|
|
440
|
+
const { federation } = createTestFederation({ meterProvider });
|
|
441
|
+
let response = await federation.fetch(gatewayRequest("/users/alice"), { contextData: void 0 });
|
|
442
|
+
assertEquals(response.status, 200);
|
|
443
|
+
let counts = recorder.getMeasurements("fedify.http.server.request.count");
|
|
444
|
+
assertEquals(counts.length, 1);
|
|
445
|
+
assertEquals(counts[0].attributes["fedify.endpoint"], "actor");
|
|
446
|
+
const template = counts[0].attributes["fedify.route.template"];
|
|
447
|
+
assertEquals(template, "/.well-known/apgateway/{did}/users/{identifier}");
|
|
448
|
+
response = await federation.fetch(gatewayRequest("/users/alice", { headers: { Accept: "text/html" } }), { contextData: void 0 });
|
|
449
|
+
assertEquals(response.status, 406);
|
|
450
|
+
counts = recorder.getMeasurements("fedify.http.server.request.count");
|
|
451
|
+
assertEquals(counts.length, 2);
|
|
452
|
+
assertEquals(counts[1].attributes["fedify.endpoint"], "not_acceptable");
|
|
453
|
+
});
|
|
454
|
+
test("Federation.fetch() warns about recursive actor dispatches in gateway requests", async () => {
|
|
455
|
+
const records = [];
|
|
456
|
+
await reset();
|
|
457
|
+
try {
|
|
458
|
+
await configure({
|
|
459
|
+
sinks: { buffer: (record) => records.push(record) },
|
|
460
|
+
filters: {},
|
|
461
|
+
loggers: [{
|
|
462
|
+
category: [
|
|
463
|
+
"fedify",
|
|
464
|
+
"federation",
|
|
465
|
+
"actor"
|
|
466
|
+
],
|
|
467
|
+
sinks: ["buffer"]
|
|
468
|
+
}, {
|
|
469
|
+
category: ["logtape", "meta"],
|
|
470
|
+
sinks: [],
|
|
471
|
+
lowestLevel: "warning"
|
|
472
|
+
}]
|
|
473
|
+
});
|
|
474
|
+
const { federation } = createTestFederation();
|
|
475
|
+
let response = await federation.fetch(gatewayRequest("/users/alice"), { contextData: void 0 });
|
|
476
|
+
assertEquals(response.status, 200);
|
|
477
|
+
const isRecursionWarning = (record) => record.level === "warning" && record.rawMessage.toString().includes("infinite loop");
|
|
478
|
+
assertFalse(records.some(isRecursionWarning));
|
|
479
|
+
const recursive = createFederation({
|
|
480
|
+
kv: new MemoryKvStore(),
|
|
481
|
+
documentLoaderFactory: () => mockDocumentLoader,
|
|
482
|
+
contextLoaderFactory: () => mockDocumentLoader
|
|
483
|
+
});
|
|
484
|
+
recursive.setActorDispatcher("/users/{identifier}", async (ctx, identifier) => {
|
|
485
|
+
if (identifier === "alice") await ctx.getActor("bob");
|
|
486
|
+
return null;
|
|
487
|
+
});
|
|
488
|
+
response = await recursive.fetch(gatewayRequest("/users/alice"), { contextData: void 0 });
|
|
489
|
+
assertEquals(response.status, 404);
|
|
490
|
+
assert(records.some(isRecursionWarning));
|
|
491
|
+
} finally {
|
|
492
|
+
await reset();
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
//#endregion
|
|
496
|
+
export {};
|