@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.
Files changed (244) hide show
  1. package/README.md +50 -39
  2. package/dist/{assert-CJ1KXcvv.mjs → assert-D1Eafmd_.mjs} +1 -1
  3. package/dist/{assert_equals-BV7oLMxm.mjs → assert_equals-Z6IcEbWU.mjs} +2 -28
  4. package/dist/assert_exists-cffSpJ7X.mjs +28 -0
  5. package/dist/assert_false-CPdRnc3E.mjs +23 -0
  6. package/dist/assert_greater_or_equal-BlafOF4-.mjs +31 -0
  7. package/dist/{assert_instance_of-Q9KTBnrf.mjs → assert_instance_of-mrXWWNY-.mjs} +1 -1
  8. package/dist/{assert_not_equals-EPJPoxuf.mjs → assert_not_equals-CpPK2t89.mjs} +2 -1
  9. package/dist/assert_rejects-CTptdYck.mjs +39 -0
  10. package/dist/{assert_strict_equals-Ec20AUwa.mjs → assert_strict_equals-CYvBmkpC.mjs} +2 -1
  11. package/dist/{assert_throws-NiFfqbnH.mjs → assert_throws-BYITOvPO.mjs} +2 -1
  12. package/dist/assertion_error-B-cx0fFA.mjs +31 -0
  13. package/dist/audience-CHQ-FlLD.mjs +95 -0
  14. package/dist/{builder-DonuxRl_.mjs → builder-Bda6LK7A.mjs} +169 -14
  15. package/dist/{circuit-breaker-CBGyz5F7.mjs → circuit-breaker-XJ53md4S.mjs} +224 -17
  16. package/dist/circuit-breaker-test-utils-DW6YC_Pe.mjs +14 -0
  17. package/dist/{client-CjF5IYQS.mjs → client-CEnNz8O7.mjs} +144 -8
  18. package/dist/{codec-BBga-FWI.mjs → codec-C6rTrpIp.mjs} +26 -3
  19. package/dist/{collection-BIhlldqK.mjs → collection-CnnP1UAv.mjs} +1 -1
  20. package/dist/compat/mod.cjs +1 -1
  21. package/dist/compat/mod.d.cts +1 -1
  22. package/dist/compat/mod.d.ts +1 -1
  23. package/dist/compat/mod.js +1 -1
  24. package/dist/compat/outgoing-jsonld.test.mjs +71 -3
  25. package/dist/compat/public-audience.test.mjs +14 -5
  26. package/dist/compat/secured-document.test.d.mts +2 -0
  27. package/dist/compat/secured-document.test.mjs +58 -0
  28. package/dist/compat/transformers.test.mjs +5 -5
  29. package/dist/compound-proof-B-9mXxFW.mjs +989 -0
  30. package/dist/{context-Zii1f5xS.d.ts → context-7NyiqIcg.d.ts} +1600 -25
  31. package/dist/{context-BJXxPElm.mjs → context-8Tu9kea0.mjs} +21 -63
  32. package/dist/{context-lxX6I7as.d.cts → context-tsYPJ2Ew.d.cts} +1600 -25
  33. package/dist/{deno-BHzKn9Ig.mjs → deno-oR5aJKpU.mjs} +1 -1
  34. package/dist/dist-DJQM4u7i.mjs +189 -0
  35. package/dist/docloader-DBoYbvYW.mjs +87 -0
  36. package/dist/{esm-yYQfPhIY.mjs → esm-BHXXx4Wx.mjs} +13 -6
  37. package/dist/federation/audience.test.d.mts +2 -0
  38. package/dist/federation/audience.test.mjs +130 -0
  39. package/dist/federation/builder.test.mjs +54 -5
  40. package/dist/federation/circuit-breaker.test.mjs +838 -7
  41. package/dist/federation/collection.test.mjs +3 -3
  42. package/dist/federation/compound-fanout.test.d.mts +2 -0
  43. package/dist/federation/compound-fanout.test.mjs +228 -0
  44. package/dist/federation/handler-compound-proof.test.d.mts +2 -0
  45. package/dist/federation/handler-compound-proof.test.mjs +384 -0
  46. package/dist/federation/handler.test.mjs +385 -15
  47. package/dist/federation/hashlink-media.test.d.mts +2 -0
  48. package/dist/federation/hashlink-media.test.mjs +422 -0
  49. package/dist/federation/idempotency.test.mjs +6 -6
  50. package/dist/federation/inbox-report.test.d.mts +2 -0
  51. package/dist/federation/inbox-report.test.mjs +475 -0
  52. package/dist/federation/inbox.test.mjs +3 -3
  53. package/dist/federation/keycache.test.mjs +215 -14
  54. package/dist/federation/kv.test.mjs +3 -3
  55. package/dist/federation/metrics.test.mjs +5 -5
  56. package/dist/federation/middleware.test.mjs +967 -35
  57. package/dist/federation/mod.cjs +18 -261
  58. package/dist/federation/mod.d.cts +5 -5
  59. package/dist/federation/mod.d.ts +5 -5
  60. package/dist/federation/mod.js +279 -1
  61. package/dist/federation/mq.test.mjs +123 -47
  62. package/dist/federation/negotiation.test.mjs +4 -4
  63. package/dist/federation/outgoing-proof.test.d.mts +2 -0
  64. package/dist/federation/outgoing-proof.test.mjs +931 -0
  65. package/dist/federation/portable-actor.test.d.mts +2 -0
  66. package/dist/federation/portable-actor.test.mjs +496 -0
  67. package/dist/federation/portable-chain.test.d.mts +2 -0
  68. package/dist/federation/portable-chain.test.mjs +370 -0
  69. package/dist/federation/portable-collection.test.d.mts +2 -0
  70. package/dist/federation/portable-collection.test.mjs +832 -0
  71. package/dist/federation/portable-delivery.test.d.mts +2 -0
  72. package/dist/federation/portable-delivery.test.mjs +746 -0
  73. package/dist/federation/portable-gateway-keys.test.d.mts +2 -0
  74. package/dist/federation/portable-gateway-keys.test.mjs +686 -0
  75. package/dist/federation/portable-inbox.test.d.mts +2 -0
  76. package/dist/federation/portable-inbox.test.mjs +1535 -0
  77. package/dist/federation/portable-parse-uri.test.d.mts +2 -0
  78. package/dist/federation/portable-parse-uri.test.mjs +348 -0
  79. package/dist/federation/portable-route-activity.test.d.mts +2 -0
  80. package/dist/federation/portable-route-activity.test.mjs +259 -0
  81. package/dist/federation/portable.test.d.mts +2 -0
  82. package/dist/federation/portable.test.mjs +1072 -0
  83. package/dist/federation/retry.test.mjs +4 -3
  84. package/dist/federation/router.test.mjs +9 -159
  85. package/dist/federation/send.test.mjs +245 -27
  86. package/dist/federation/tasks/codec.test.mjs +4 -4
  87. package/dist/federation/tasks/enqueue.test.mjs +42 -4
  88. package/dist/federation/tasks/tasks.test.mjs +7 -6
  89. package/dist/federation/temporal.test.mjs +4 -4
  90. package/dist/federation/webfinger.test.mjs +275 -12
  91. package/dist/{middleware-FrSMAYEx.cjs → federation-CkZiAcWY.cjs} +8640 -3722
  92. package/dist/http-B5JT115n.cjs +10630 -0
  93. package/dist/http-DydWCB7R.js +6527 -0
  94. package/dist/{http-CPVMUgsW.mjs → http-siciWkmx.mjs} +397 -168
  95. package/dist/key-BspPiXqn.mjs +951 -0
  96. package/dist/key-C1nv7_jD.d.ts +1125 -0
  97. package/dist/key-DYxuyd-s.d.cts +1125 -0
  98. package/dist/key-owner-evidence-APB6NpSk.mjs +108 -0
  99. package/dist/keycache-Da8A8mZa.mjs +200 -0
  100. package/dist/{kv-D6hNiMTK.d.ts → kv-B2mzK5tl.d.cts} +3 -2
  101. package/dist/{kv-gJ8LYbxX.d.cts → kv-B2mzK5tl.d.ts} +3 -2
  102. package/dist/kv-DBRWVx3Q.mjs +104 -0
  103. package/dist/{kv-cache-KyGqJqSp.mjs → kv-cache-BF1hnW65.mjs} +1 -1
  104. package/dist/{kv-cache-5bnX3-Jq.js → kv-cache-CTQr1tbn.js} +54 -16
  105. package/dist/{kv-cache-DQ29C4XL.cjs → kv-cache-DCac1nEU.cjs} +55 -19
  106. package/dist/{ld-Cj9F_5AP.mjs → ld-CzaP-hJl.mjs} +44 -12
  107. package/dist/map-local-context-conflict-DOYEEJ3u.mjs +204 -0
  108. package/dist/map-local-create-note-B0SeXJms.mjs +214 -0
  109. package/dist/{metrics-DMk-nTxI.mjs → metrics-CYg9yxBL.mjs} +3 -2
  110. package/dist/metrics-DVKYY6K1.d.cts +36 -0
  111. package/dist/metrics-DoRtI0qU.d.ts +36 -0
  112. package/dist/{middleware-Y-kpsp6S.js → middleware-CsDDXKuL.js} +5493 -1103
  113. package/dist/{middleware-CvwTUgnP.mjs → middleware-D7_LO-Vs.mjs} +3157 -272
  114. package/dist/{middleware-CEctxBN6.mjs → middleware-DWjrfpcz.mjs} +1 -1
  115. package/dist/{mod-gOlPDuT8.d.cts → mod-BojP2Bw3.d.cts} +1 -1
  116. package/dist/mod-CavhAgLw.d.cts +320 -0
  117. package/dist/mod-CtpF2BU5.d.ts +320 -0
  118. package/dist/{mod-BtEaIEcD.d.cts → mod-DEZXwV6J.d.ts} +10 -4
  119. package/dist/{mod-UMmveiO0.d.ts → mod-Dw-nPxQh.d.cts} +10 -4
  120. package/dist/{mod-DLc2Ot6f.d.ts → mod-Xj5P4s4d.d.ts} +1 -1
  121. package/dist/mod.cjs +40 -39
  122. package/dist/mod.d.cts +10 -11
  123. package/dist/mod.d.ts +10 -11
  124. package/dist/mod.js +9 -9
  125. package/dist/{mq-llHKivST.mjs → mq-Dm1Wmtm-.mjs} +6 -0
  126. package/dist/nodeinfo/client.test.mjs +139 -4
  127. package/dist/nodeinfo/handler.test.mjs +5 -5
  128. package/dist/nodeinfo/mod.cjs +1 -1
  129. package/dist/nodeinfo/mod.d.cts +1 -1
  130. package/dist/nodeinfo/mod.d.ts +1 -1
  131. package/dist/nodeinfo/mod.js +1 -1
  132. package/dist/nodeinfo/types.test.mjs +4 -4
  133. package/dist/otel/exporter.test.mjs +3 -3
  134. package/dist/otel/mod.cjs +0 -1
  135. package/dist/otel/mod.d.cts +1 -1
  136. package/dist/otel/mod.d.ts +1 -1
  137. package/dist/{outgoing-jsonld-URuqsICz.mjs → outgoing-jsonld-4IJqPYos.mjs} +12 -10
  138. package/dist/{owner-DEvZuyOE.d.cts → owner-BF0L9cMt.d.cts} +11 -0
  139. package/dist/{owner-CnngXDNJ.d.ts → owner-BSzr5-I8.d.ts} +11 -0
  140. package/dist/owner-DYgfTFHy.mjs +267 -0
  141. package/dist/portable-collection-BpsVsRov.js +670 -0
  142. package/dist/portable-collection-C74P1vtC.mjs +418 -0
  143. package/dist/portable-collection-DZd2bm7M.cjs +686 -0
  144. package/dist/portable-key-BIMLrAP4.mjs +549 -0
  145. package/dist/portable-key-id-Ca30-hOa.mjs +248 -0
  146. package/dist/proof-Dxy6ByqI.mjs +1299 -0
  147. package/dist/{public-audience-Cr5MsnMq.mjs → public-audience--klBIOk1.mjs} +15 -10
  148. package/dist/router-u5O5KCQr.mjs +156 -0
  149. package/dist/runtime/mod.cjs +1 -1
  150. package/dist/runtime/mod.js +1 -1
  151. package/dist/secured-document-CxzZZ9nL.mjs +35 -0
  152. package/dist/send-C4dDpAxm.mjs +495 -0
  153. package/dist/sig/accept.test.mjs +1 -1
  154. package/dist/sig/compound-proof-discovery.test.d.mts +2 -0
  155. package/dist/sig/compound-proof-discovery.test.mjs +331 -0
  156. package/dist/sig/compound-proof-producer.test.d.mts +2 -0
  157. package/dist/sig/compound-proof-producer.test.mjs +107 -0
  158. package/dist/sig/compound-proof-verification.test.d.mts +2 -0
  159. package/dist/sig/compound-proof-verification.test.mjs +833 -0
  160. package/dist/sig/compound-proof.test.d.mts +2 -0
  161. package/dist/sig/compound-proof.test.mjs +411 -0
  162. package/dist/sig/http.test.mjs +702 -31
  163. package/dist/sig/key-owner-evidence.test.d.mts +2 -0
  164. package/dist/sig/key-owner-evidence.test.mjs +100 -0
  165. package/dist/sig/key.test.mjs +220 -8
  166. package/dist/sig/ld.test.mjs +65 -7
  167. package/dist/sig/mitra-receive-interop.test.d.mts +2 -0
  168. package/dist/sig/mitra-receive-interop.test.mjs +260 -0
  169. package/dist/sig/mitra-send-interop.test.d.mts +2 -0
  170. package/dist/sig/mitra-send-interop.test.mjs +461 -0
  171. package/dist/sig/mod.cjs +18 -16
  172. package/dist/sig/mod.d.cts +4 -4
  173. package/dist/sig/mod.d.ts +4 -4
  174. package/dist/sig/mod.js +3 -3
  175. package/dist/sig/owner.test.mjs +108 -9
  176. package/dist/sig/portable-collection.test.d.mts +2 -0
  177. package/dist/sig/portable-collection.test.mjs +697 -0
  178. package/dist/sig/portable-interop.test.d.mts +2 -0
  179. package/dist/sig/portable-interop.test.mjs +156 -0
  180. package/dist/sig/portable-key-id.test.d.mts +2 -0
  181. package/dist/sig/portable-key-id.test.mjs +67 -0
  182. package/dist/sig/portable-key.test.d.mts +2 -0
  183. package/dist/sig/portable-key.test.mjs +1439 -0
  184. package/dist/sig/proof.test.mjs +1753 -16
  185. package/dist/sig/tootik-request-interop.test.d.mts +2 -0
  186. package/dist/sig/tootik-request-interop.test.mjs +190 -0
  187. package/dist/sig/tootik-send-interop.test.d.mts +2 -0
  188. package/dist/sig/tootik-send-interop.test.mjs +331 -0
  189. package/dist/sig/verification.test.d.mts +2 -0
  190. package/dist/sig/verification.test.mjs +310 -0
  191. package/dist/std__assert-DvMDRPes.mjs +8 -0
  192. package/dist/{testing-CO7ftO8A.mjs → tasks-IG3pHfvZ.mjs} +2 -2
  193. package/dist/{temporal-fsXO5IvD.mjs → temporal-D0KJI4NF.mjs} +3 -3
  194. package/dist/testing/context.test.d.mts +2 -0
  195. package/dist/testing/context.test.mjs +19 -0
  196. package/dist/testing/kv.test.d.mts +2 -0
  197. package/dist/testing/kv.test.mjs +101 -0
  198. package/dist/testing/mod.d.mts +1617 -48
  199. package/dist/testing/mod.mjs +6 -3
  200. package/dist/testing-t8xDeJZ5.mjs +8 -0
  201. package/dist/{transformers-DGwl-hD1.cjs → transformers-BalCrSsr.cjs} +0 -1
  202. package/dist/{types-DC1pNgJA.js → types-_S2_MeLF.js} +144 -8
  203. package/dist/{types-CPym83zc.cjs → types-poIao40f.cjs} +160 -7
  204. package/dist/utils/docloader.test.mjs +530 -8
  205. package/dist/utils/kv-cache.test.mjs +2 -2
  206. package/dist/utils/mod.cjs +1 -1
  207. package/dist/utils/mod.d.cts +1 -1
  208. package/dist/utils/mod.d.ts +1 -1
  209. package/dist/utils/mod.js +1 -1
  210. package/dist/verification-C3954hQh.mjs +5 -0
  211. package/dist/verification-Dpfcw8ml.mjs +105 -0
  212. package/dist/vocab/cjs.test.mjs +1 -1
  213. package/package.json +8 -8
  214. package/dist/assert_rejects-EgstcQy5.mjs +0 -82
  215. package/dist/docloader-e1khNUKy.mjs +0 -49
  216. package/dist/http-CsmQ-BQ4.js +0 -2483
  217. package/dist/http-VyDTd4G3.d.cts +0 -553
  218. package/dist/http-hlIjmewS.cjs +0 -2694
  219. package/dist/http-lf8Hsd91.d.ts +0 -553
  220. package/dist/key-DfPsX8hv.mjs +0 -453
  221. package/dist/keycache-_DHqz5z_.mjs +0 -97
  222. package/dist/metrics-CoUJLQhZ.d.cts +0 -276
  223. package/dist/metrics-Dc4jR-IC.d.ts +0 -276
  224. package/dist/mod-B0hW12_O.d.cts +0 -307
  225. package/dist/mod-COIAjwRS.d.ts +0 -307
  226. package/dist/owner-Br9YYzn_.mjs +0 -120
  227. package/dist/proof-B4ngoaNQ.mjs +0 -316
  228. package/dist/proof-B6E0XUXJ.js +0 -1415
  229. package/dist/proof-NN4xCQGL.cjs +0 -1562
  230. package/dist/send-CxHKUNzv.mjs +0 -239
  231. package/dist/std__assert-B2NnY9bq.mjs +0 -79
  232. package/dist/temporal-polyfill-BCdTHexd.cjs +0 -3565
  233. /package/dist/{accept-8PiolDP7.mjs → accept-CPVqki78.mjs} +0 -0
  234. /package/dist/{activity-listener-BtTSxZWA.mjs → activity-listener-C-BSbKI7.mjs} +0 -0
  235. /package/dist/{client-CAM_bQXx.d.cts → client-D-XP_Ccb.d.cts} +0 -0
  236. /package/dist/{client-CSddvgWN.d.ts → client-D-XP_Ccb.d.ts} +0 -0
  237. /package/dist/{keys-Dbyd7bhl.mjs → keys-DIJNHW-Y.mjs} +0 -0
  238. /package/dist/{kv-CXBAok3k.mjs → kv-DCHkaJ9-.mjs} +0 -0
  239. /package/dist/{negotiation-RuwrPFqi.mjs → negotiation-CCocWT_N.mjs} +0 -0
  240. /package/dist/{retry-DgFqeS5k.mjs → retry-zX5xw5e0.mjs} +0 -0
  241. /package/dist/{chunk-D5IdezsZ.js → rolldown-runtime-BC-Eo5Mj.js} +0 -0
  242. /package/dist/{chunk-Czpeue4M.cjs → rolldown-runtime-Dmm3t92W.cjs} +0 -0
  243. /package/dist/{transformers-DXze6FUB.js → transformers-DAtXl_Kc.js} +0 -0
  244. /package/dist/{types-CrtdVuDP.mjs → types-DE_LV1JB.mjs} +0 -0
@@ -0,0 +1,2 @@
1
+ import { Temporal } from "temporal-polyfill";
2
+ import { URLPattern } from "urlpattern-polyfill";
@@ -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 {};
@@ -0,0 +1,2 @@
1
+ import { Temporal } from "temporal-polyfill";
2
+ import { URLPattern } from "urlpattern-polyfill";