@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,686 @@
1
+ import { Temporal } from "temporal-polyfill";
2
+ import "urlpattern-polyfill";
3
+ globalThis.addEventListener = () => {};
4
+ import { t as esm_default } from "../esm-BHXXx4Wx.mjs";
5
+ import { r as createRequestContext, t as createInboxContext } from "../context-8Tu9kea0.mjs";
6
+ import { t as MemoryKvStore } from "../kv-DCHkaJ9-.mjs";
7
+ import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
8
+ import "../std__assert-DvMDRPes.mjs";
9
+ import { t as assertRejects } from "../assert_rejects-CTptdYck.mjs";
10
+ import { r as getVerifiedKeyOwnerEvidence } from "../key-owner-evidence-APB6NpSk.mjs";
11
+ import { p as signJsonLd } from "../ld-CzaP-hJl.mjs";
12
+ import { f as verifyRequest, l as signRequest, p as verifyRequestDetailed } from "../http-siciWkmx.mjs";
13
+ import { a as rsaPrivateKey3, i as rsaPrivateKey2, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2 } from "../keys-DIJNHW-Y.mjs";
14
+ import { f as handleInbox, n as FederationImpl, o as createFederation } from "../middleware-D7_LO-Vs.mjs";
15
+ import { n as getKeyOwner, t as doesActorOwnKey } from "../owner-DYgfTFHy.mjs";
16
+ import { t as ActivityListenerSet } from "../activity-listener-C-BSbKI7.mjs";
17
+ import { t as KvKeyCache } from "../keycache-Da8A8mZa.mjs";
18
+ import { Create, CryptographicKey, Person } from "@fedify/vocab";
19
+ import { FetchError, encodeMultibase, exportDidKey, parseIri } from "@fedify/vocab-runtime";
20
+ import { mockDocumentLoader, test } from "@fedify/fixture";
21
+ import serialize from "json-canon";
22
+ //#region src/federation/portable-gateway-keys.test.ts
23
+ const did = await exportDidKey(ed25519PublicKey.publicKey);
24
+ const didKeyId = new URL(`${did}#${did.slice(8)}`);
25
+ const actorId = parseIri(`ap+ef61://${did}/actors/alice`);
26
+ const gateway = "https://example.com";
27
+ const otherGateway = "https://other.example";
28
+ const compatibleActorId = (origin) => `${origin}/.well-known/apgateway/${did}/actors/alice`;
29
+ const gatewayKeyId = new URL(`${compatibleActorId(gateway)}#main-key`);
30
+ const ed25519GatewayKeyPair = await crypto.subtle.generateKey("Ed25519", true, ["sign", "verify"]);
31
+ const rsaGatewayKeyPair = {
32
+ privateKey: rsaPrivateKey2,
33
+ publicKey: rsaPublicKey2.publicKey
34
+ };
35
+ const portableContext = [
36
+ "https://www.w3.org/ns/activitystreams",
37
+ "https://w3id.org/security/data-integrity/v1",
38
+ "https://w3id.org/fep/ef61"
39
+ ];
40
+ const recipient = {
41
+ id: new URL("https://example.com/users/bob"),
42
+ inboxId: new URL("https://example.com/inbox")
43
+ };
44
+ async function sign(document) {
45
+ const proofConfig = {
46
+ "@context": document["@context"],
47
+ type: "DataIntegrityProof",
48
+ cryptosuite: "eddsa-jcs-2022",
49
+ verificationMethod: didKeyId.href,
50
+ proofPurpose: "assertionMethod",
51
+ created: "2023-02-24T23:36:38Z"
52
+ };
53
+ const encoder = new TextEncoder();
54
+ const digest = /* @__PURE__ */ new Uint8Array(64);
55
+ digest.set(new Uint8Array(await crypto.subtle.digest("SHA-256", encoder.encode(serialize(proofConfig)))), 0);
56
+ digest.set(new Uint8Array(await crypto.subtle.digest("SHA-256", encoder.encode(serialize(document)))), 32);
57
+ const signature = await crypto.subtle.sign("Ed25519", ed25519PrivateKey, digest);
58
+ return {
59
+ ...document,
60
+ proof: {
61
+ ...proofConfig,
62
+ proofValue: new TextDecoder().decode(encodeMultibase("base58btc", new Uint8Array(signature)))
63
+ }
64
+ };
65
+ }
66
+ function createTestFederation(mapper) {
67
+ const federation = new FederationImpl({
68
+ kv: new MemoryKvStore(),
69
+ manuallyStartQueue: true,
70
+ contextLoaderFactory: () => mockDocumentLoader,
71
+ documentLoaderFactory: () => mockDocumentLoader
72
+ });
73
+ const setters = federation.setActorDispatcher("/users/{identifier}", () => null).setKeyPairsDispatcher(() => [rsaGatewayKeyPair, ed25519GatewayKeyPair]);
74
+ if (mapper != null) setters.mapPortableActorId((_ctx, id) => mapper(id));
75
+ return federation;
76
+ }
77
+ function portableCreate(id = "1") {
78
+ return new Create({
79
+ id: parseIri(`ap+ef61://${did}/activities/${id}`),
80
+ actor: actorId,
81
+ object: new URL("https://example.com/notes/1")
82
+ });
83
+ }
84
+ async function signedPortableCreate(id = "1") {
85
+ const json = await portableCreate(id).toJsonLd({
86
+ format: "compact",
87
+ context: portableContext,
88
+ contextLoader: mockDocumentLoader
89
+ });
90
+ return await Create.fromJsonLd(await sign(json), {
91
+ contextLoader: mockDocumentLoader,
92
+ documentLoader: mockDocumentLoader
93
+ });
94
+ }
95
+ async function capture(run) {
96
+ const bodies = [];
97
+ const requests = [];
98
+ esm_default.spyGlobal();
99
+ try {
100
+ esm_default.post(recipient.inboxId.href, async (cl) => {
101
+ const body = await cl.request.text();
102
+ requests.push(new Request(cl.request.url, {
103
+ method: cl.request.method,
104
+ headers: cl.request.headers,
105
+ body
106
+ }));
107
+ bodies.push(JSON.parse(body));
108
+ return new Response(null, { status: 202 });
109
+ });
110
+ await run();
111
+ } finally {
112
+ esm_default.hardReset();
113
+ }
114
+ return {
115
+ bodies,
116
+ requests
117
+ };
118
+ }
119
+ function signatureKeyId(request) {
120
+ return request.headers.get("Signature-Input")?.match(/keyid="([^"]+)"/)?.[1] ?? request.headers.get("Signature")?.match(/keyId="([^"]+)"/)?.[1];
121
+ }
122
+ test("Context.getActorKeyPairs() derives gateway keys for portable actors", async () => {
123
+ const ctx = createTestFederation((id) => id === "alice" ? actorId : null).createContext(new URL(gateway));
124
+ const keys = await ctx.getActorKeyPairs("alice");
125
+ assertEquals(keys.map((k) => k.keyId.href), [gatewayKeyId.href, `${compatibleActorId(gateway)}#key-2`]);
126
+ for (const key of keys) {
127
+ assertEquals(key.cryptographicKey.id?.href, key.keyId.href);
128
+ assertEquals(key.cryptographicKey.ownerId?.href, actorId.href);
129
+ assertEquals(key.multikey.id?.href, key.keyId.href);
130
+ assertEquals(key.multikey.controllerId?.href, actorId.href);
131
+ }
132
+ assertEquals((await ctx.getActorKeyPairs("bob")).map((k) => [k.keyId.href, k.multikey.id?.href]), [["https://example.com/users/bob#main-key", "https://example.com/users/bob#multikey-1"], ["https://example.com/users/bob#key-2", "https://example.com/users/bob#multikey-2"]]);
133
+ const json = await new Person({
134
+ id: actorId,
135
+ gateways: [new URL(gateway)],
136
+ publicKey: keys[0].cryptographicKey,
137
+ assertionMethods: keys.map((k) => k.multikey)
138
+ }).toJsonLd({
139
+ format: "compact",
140
+ contextLoader: mockDocumentLoader
141
+ });
142
+ const assertionMethod = json.assertionMethod;
143
+ assertEquals(assertionMethod.map((k) => [k.id, k.controller]), [[gatewayKeyId.href, `ap+ef61://${did}/actors/alice`], [`${compatibleActorId(gateway)}#key-2`, `ap+ef61://${did}/actors/alice`]]);
144
+ assertEquals(json.publicKey.id, gatewayKeyId.href);
145
+ });
146
+ test("Context.getActorKeyPairs() accepts compatible IDs as portable actor IDs", async () => {
147
+ const published = new URL(compatibleActorId(otherGateway));
148
+ const [key] = await createTestFederation(() => published).createContext(new URL(gateway)).getActorKeyPairs("alice");
149
+ assertEquals(key.keyId.href, gatewayKeyId.href);
150
+ assertEquals(key.cryptographicKey.ownerId?.href, published.href);
151
+ });
152
+ test("Context.getActorKeyPairs() propagates portable actor ID mapper errors", async () => {
153
+ for (const id of [new URL(`${actorId.href}#me`), new URL("https://example.com/users/alice")]) {
154
+ const ctx = createTestFederation(() => id).createContext(new URL(gateway));
155
+ await assertRejects(() => ctx.getActorKeyPairs("alice"), TypeError);
156
+ }
157
+ const ctx = createTestFederation(() => {
158
+ throw new RangeError("mapper failed");
159
+ }).createContext(new URL(gateway));
160
+ await assertRejects(() => ctx.getActorKeyPairs("alice"), RangeError);
161
+ const federation = new FederationImpl({ kv: new MemoryKvStore() });
162
+ federation.setActorDispatcher("/users/{identifier}", () => null).setKeyPairsDispatcher(() => {
163
+ throw new Error("dispatcher failed");
164
+ }).mapPortableActorId(() => actorId);
165
+ assertEquals(await federation.createContext(new URL(gateway)).getActorKeyPairs("alice"), []);
166
+ });
167
+ test("Context.sendActivity() signs requests with gateway keys for portable actors", async () => {
168
+ const ctx = createTestFederation(() => actorId).createContext(new URL(gateway));
169
+ const activity = await signedPortableCreate();
170
+ const { bodies, requests } = await capture(() => ctx.sendActivity({ identifier: "alice" }, recipient, activity));
171
+ assertEquals(bodies.length, 1);
172
+ assertEquals(signatureKeyId(requests[0]), gatewayKeyId.href);
173
+ const proof = bodies[0].proof;
174
+ assertEquals(Array.isArray(proof), false);
175
+ assertEquals(proof.verificationMethod, didKeyId.href);
176
+ assertEquals(bodies[0].signature, void 0);
177
+ });
178
+ test("Context.sendActivity() refuses portable activities gateway keys would sign", async () => {
179
+ const ctx = createTestFederation(() => actorId).createContext(new URL(gateway));
180
+ await assertRejects(() => ctx.sendActivity({ identifier: "alice" }, recipient, portableCreate()), TypeError);
181
+ const httpActivity = new Create({
182
+ id: new URL("https://example.com/activities/1"),
183
+ actor: actorId,
184
+ object: new URL("https://example.com/notes/1")
185
+ });
186
+ await assertRejects(() => ctx.sendActivity({ identifier: "alice" }, recipient, httpActivity), TypeError);
187
+ await assertRejects(() => ctx.sendActivity({
188
+ keyId: new URL("https://example.com/key"),
189
+ privateKey: ed25519PrivateKey
190
+ }, recipient, portableCreate()), TypeError);
191
+ const { bodies } = await capture(() => ctx.sendActivity([{
192
+ keyId: gatewayKeyId,
193
+ privateKey: rsaPrivateKey2
194
+ }, {
195
+ keyId: didKeyId,
196
+ privateKey: ed25519PrivateKey
197
+ }], recipient, portableCreate()));
198
+ assertEquals(bodies[0].proof.verificationMethod, didKeyId.href);
199
+ });
200
+ test("Context.sendActivity() never signs documents with gateway keys", async () => {
201
+ const ctx = createTestFederation().createContext(new URL(gateway));
202
+ const keys = [{
203
+ keyId: gatewayKeyId,
204
+ privateKey: rsaPrivateKey2
205
+ }, {
206
+ keyId: new URL(`${compatibleActorId(gateway)}#key-2`),
207
+ privateKey: ed25519GatewayKeyPair.privateKey
208
+ }];
209
+ const activity = new Create({
210
+ id: new URL("https://example.com/activities/1"),
211
+ actor: new URL("https://example.com/users/bob"),
212
+ object: new URL("https://example.com/notes/1")
213
+ });
214
+ const { bodies, requests } = await capture(() => ctx.sendActivity(keys, recipient, activity));
215
+ assertEquals(signatureKeyId(requests[0]), gatewayKeyId.href);
216
+ assertEquals(bodies[0].proof, void 0);
217
+ assertEquals(bodies[0].signature, void 0);
218
+ });
219
+ test("Context.sendActivity() never signs documents with keys at ap: URIs", async () => {
220
+ const ctx = createTestFederation().createContext(new URL(gateway));
221
+ const keys = [{
222
+ keyId: parseIri(`${actorId.href}#main-key`),
223
+ privateKey: rsaPrivateKey2
224
+ }, {
225
+ keyId: parseIri(`${actorId.href}#key-2`),
226
+ privateKey: ed25519GatewayKeyPair.privateKey
227
+ }];
228
+ const activity = new Create({
229
+ id: new URL("https://example.com/activities/1"),
230
+ actor: new URL("https://example.com/users/bob"),
231
+ object: new URL("https://example.com/notes/1")
232
+ });
233
+ const { bodies } = await capture(() => ctx.sendActivity(keys, recipient, activity));
234
+ assertEquals(bodies[0].proof, void 0);
235
+ assertEquals(bodies[0].signature, void 0);
236
+ });
237
+ test("Context.getDocumentLoader() signs requests with gateway keys for portable actors", async () => {
238
+ const documentLoader = await createTestFederation(() => actorId).createContext(new URL(gateway)).getDocumentLoader({ identifier: "alice" });
239
+ let keyId;
240
+ esm_default.spyGlobal();
241
+ try {
242
+ esm_default.get("https://example.com/notes/1", (cl) => {
243
+ keyId = signatureKeyId(cl.request);
244
+ return new Response(JSON.stringify({ id: cl.request.url }), { headers: { "Content-Type": "application/activity+json" } });
245
+ });
246
+ await documentLoader("https://example.com/notes/1");
247
+ } finally {
248
+ esm_default.hardReset();
249
+ }
250
+ assertEquals(keyId, gatewayKeyId.href);
251
+ });
252
+ async function actorDocument(gateways, id = actorId, signed = true) {
253
+ const keys = await createTestFederation(() => id).createContext(new URL(gateway)).getActorKeyPairs("alice");
254
+ const json = await new Person({
255
+ id,
256
+ inbox: parseIri(`${id.href}/inbox`),
257
+ gateways: gateways.map((g) => new URL(g)),
258
+ publicKey: keys[0].cryptographicKey,
259
+ assertionMethods: keys.map((k) => k.multikey)
260
+ }).toJsonLd({
261
+ format: "compact",
262
+ contextLoader: mockDocumentLoader
263
+ });
264
+ json["@context"] = [...json["@context"], "https://w3id.org/security/data-integrity/v1"];
265
+ return signed ? await sign(json) : json;
266
+ }
267
+ function createLoader(responses) {
268
+ return (url) => {
269
+ const document = responses[url.replace(/#.*$/, "")];
270
+ if (document == null) return mockDocumentLoader(url);
271
+ return Promise.resolve({
272
+ contextUrl: null,
273
+ documentUrl: url,
274
+ document: structuredClone(document)
275
+ });
276
+ };
277
+ }
278
+ async function deliver(body, gateways, { kv = new MemoryKvStore(), httpSignature = true, actor, documentLoader, keyId = gatewayKeyId } = {}) {
279
+ documentLoader ??= createLoader({ [compatibleActorId(gateway)]: actor ?? await actorDocument(gateways) });
280
+ const unsigned = new Request("https://local.example/inbox", {
281
+ method: "POST",
282
+ body: JSON.stringify(body),
283
+ headers: { "Content-Type": "application/activity+json" }
284
+ });
285
+ const request = httpSignature ? await signRequest(unsigned, rsaPrivateKey2, keyId) : unsigned;
286
+ const context = createRequestContext({
287
+ federation: createFederation({ kv: new MemoryKvStore() }),
288
+ request,
289
+ url: new URL(request.url),
290
+ data: void 0,
291
+ documentLoader,
292
+ contextLoader: mockDocumentLoader
293
+ });
294
+ let dispatched = 0;
295
+ const inboxListeners = new ActivityListenerSet();
296
+ inboxListeners.add(Create, () => {
297
+ dispatched++;
298
+ });
299
+ const response = await handleInbox(request, {
300
+ recipient: null,
301
+ context,
302
+ inboxContextFactory() {
303
+ return createInboxContext({
304
+ ...context,
305
+ clone: void 0
306
+ });
307
+ },
308
+ kv,
309
+ kvPrefixes: {
310
+ activityIdempotence: ["_fedify", "activityIdempotence"],
311
+ publicKey: ["_fedify", "publicKey"],
312
+ acceptSignatureNonce: ["_fedify", "acceptSignatureNonce"]
313
+ },
314
+ actorDispatcher() {
315
+ return null;
316
+ },
317
+ inboxListeners,
318
+ onNotFound() {
319
+ return new Response("Not found", { status: 404 });
320
+ },
321
+ signatureTimeWindow: { minutes: 5 },
322
+ skipSignatureVerification: false
323
+ });
324
+ lastResponseText = await response.text();
325
+ return {
326
+ status: response.status,
327
+ dispatched
328
+ };
329
+ }
330
+ let lastResponseText = "";
331
+ async function createJson(id) {
332
+ return await portableCreate(id).toJsonLd({
333
+ format: "compact",
334
+ context: portableContext,
335
+ contextLoader: mockDocumentLoader
336
+ });
337
+ }
338
+ test("handleInbox() requires proofs of portable actors despite gateway signatures", async () => {
339
+ assertEquals(await deliver(await createJson("unsigned"), [gateway]), {
340
+ status: 401,
341
+ dispatched: 0
342
+ });
343
+ assertEquals(await deliver({
344
+ ...await sign(await createJson("tampered")),
345
+ object: "https://remote.example/notes/2"
346
+ }, [gateway]), {
347
+ status: 401,
348
+ dispatched: 0
349
+ });
350
+ assertEquals(await deliver(await sign(await createJson("signed")), [gateway]), {
351
+ status: 202,
352
+ dispatched: 1
353
+ });
354
+ });
355
+ const apKeyId = parseIri(`${actorId.href}?@gateway=${encodeURIComponent(gateway)}#main-key`);
356
+ async function apActorDocument() {
357
+ const json = await new Person({
358
+ id: actorId,
359
+ inbox: parseIri(`${actorId.href}/inbox`),
360
+ gateways: [new URL(gateway)],
361
+ publicKey: new CryptographicKey({
362
+ id: parseIri(`${actorId.href}#main-key`),
363
+ owner: actorId,
364
+ publicKey: rsaPublicKey2.publicKey
365
+ })
366
+ }).toJsonLd({
367
+ format: "compact",
368
+ contextLoader: mockDocumentLoader
369
+ });
370
+ json["@context"] = [...json["@context"], "https://w3id.org/security/data-integrity/v1"];
371
+ return await sign(json);
372
+ }
373
+ test("handleInbox() requires proofs of portable actors despite signatures with keys at ap: key IDs", async () => {
374
+ const fetched = [];
375
+ const inner = createLoader({ [compatibleActorId(gateway)]: await apActorDocument() });
376
+ const documentLoader = (url, options) => {
377
+ fetched.push(url);
378
+ return inner(url, options);
379
+ };
380
+ assertEquals(await deliver(await createJson("unsigned"), [gateway], {
381
+ documentLoader,
382
+ keyId: apKeyId
383
+ }), {
384
+ status: 401,
385
+ dispatched: 0
386
+ });
387
+ assertEquals(fetched.some((url) => url.startsWith(compatibleActorId(gateway))), true);
388
+ assertEquals(await deliver(await sign(await createJson("signed")), [gateway], {
389
+ documentLoader,
390
+ keyId: apKeyId
391
+ }), {
392
+ status: 202,
393
+ dispatched: 1
394
+ });
395
+ });
396
+ test("RequestContext.getSignedKeyOwner() resolves keys at ap: key IDs", async () => {
397
+ const documentLoader = createLoader({ [compatibleActorId(gateway)]: await apActorDocument() });
398
+ const federation = new FederationImpl({
399
+ kv: new MemoryKvStore(),
400
+ contextLoaderFactory: () => mockDocumentLoader,
401
+ documentLoaderFactory: () => documentLoader
402
+ });
403
+ const request = await signRequest(new Request("https://local.example/notes/1", { headers: { Accept: "application/activity+json" } }), rsaPrivateKey2, apKeyId);
404
+ const ctx = federation.createContext(request, void 0);
405
+ assertEquals((await ctx.getSignedKey())?.id?.href, apKeyId.href);
406
+ assertEquals((await ctx.getSignedKeyOwner())?.id?.href, actorId.href);
407
+ });
408
+ test("handleInbox() fetches an unreachable gateway key once", async () => {
409
+ const fetched = [];
410
+ const documentLoader = (url) => {
411
+ if (url.replace(/#.*$/, "") !== compatibleActorId(gateway)) return mockDocumentLoader(url);
412
+ fetched.push(url);
413
+ return Promise.reject(new FetchError(url, "HTTP 503", new Response(null, { status: 503 })));
414
+ };
415
+ const kv = new MemoryKvStore();
416
+ for (let i = 0; i < 3; i++) {
417
+ const { status } = await deliver(await createJson(`unreachable-${i}`), [gateway], {
418
+ kv,
419
+ documentLoader
420
+ });
421
+ assertEquals(status, 401);
422
+ }
423
+ assertEquals(fetched.length, 1);
424
+ });
425
+ test("handleInbox() does not need gateway signatures for portable actors", async () => {
426
+ assertEquals(await deliver(await sign(await createJson("signed")), [otherGateway]), {
427
+ status: 202,
428
+ dispatched: 1
429
+ });
430
+ assertEquals(await deliver(await createJson("unsigned"), [otherGateway]), {
431
+ status: 401,
432
+ dispatched: 0
433
+ });
434
+ });
435
+ test("handleInbox() does not take Linked Data Signatures for portable actors", async () => {
436
+ const kv = new MemoryKvStore();
437
+ await new KvKeyCache(kv, ["_fedify", "publicKey"]).set(rsaPublicKey2.id, new CryptographicKey({
438
+ id: rsaPublicKey2.id,
439
+ owner: actorId,
440
+ publicKey: rsaPublicKey2.publicKey
441
+ }));
442
+ const ldSign = (document) => signJsonLd(document, rsaPrivateKey2, rsaPublicKey2.id, { contextLoader: mockDocumentLoader });
443
+ assertEquals(await deliver(await ldSign(await createJson("ld-only")), [gateway], {
444
+ kv,
445
+ httpSignature: false
446
+ }), {
447
+ status: 401,
448
+ dispatched: 0
449
+ });
450
+ assertEquals(lastResponseText, "Failed to verify the request signature.");
451
+ });
452
+ const otherDid = await exportDidKey((await crypto.subtle.generateKey("Ed25519", true, ["sign", "verify"])).publicKey);
453
+ function compatibleCreateJson(id, activityDid = did) {
454
+ return {
455
+ "@context": portableContext,
456
+ id: `${gateway}/.well-known/apgateway/${activityDid}/activities/${id}`,
457
+ type: "Create",
458
+ actor: compatibleActorId(gateway),
459
+ object: "https://example.com/notes/1"
460
+ };
461
+ }
462
+ test("handleInbox() does not trust compatible-ID actors by web origin", async () => {
463
+ const id = new URL(compatibleActorId(gateway));
464
+ for (const signed of [true, false]) {
465
+ const actor = await actorDocument([gateway], id, signed);
466
+ assertEquals(await deliver(compatibleCreateJson(`unsigned-${signed}`), [gateway], { actor }), {
467
+ status: 401,
468
+ dispatched: 0
469
+ }, `actor document signed: ${signed}`);
470
+ }
471
+ assertEquals(await deliver(await sign(compatibleCreateJson("signed")), [gateway], { actor: await actorDocument([gateway], id) }), {
472
+ status: 202,
473
+ dispatched: 1
474
+ });
475
+ });
476
+ test("handleInbox() requires proofs of compatible-ID actors", async () => {
477
+ assertEquals(await deliver(compatibleCreateJson("unsigned"), [gateway]), {
478
+ status: 401,
479
+ dispatched: 0
480
+ });
481
+ assertEquals(await deliver(await sign(compatibleCreateJson("signed")), [gateway]), {
482
+ status: 202,
483
+ dispatched: 1
484
+ });
485
+ assertEquals(await deliver(await sign(compatibleCreateJson("unsigned-request")), [gateway], { httpSignature: false }), {
486
+ status: 202,
487
+ dispatched: 1
488
+ });
489
+ });
490
+ test("handleInbox() checks the DIDs of compatible activity IDs", async () => {
491
+ for (const actor of [actorId.href, compatibleActorId(gateway)]) assertEquals(await deliver(await sign({
492
+ ...compatibleCreateJson("forged", otherDid),
493
+ actor
494
+ }), [gateway]), {
495
+ status: 401,
496
+ dispatched: 0
497
+ }, actor);
498
+ assertEquals(await deliver(await sign({
499
+ ...compatibleCreateJson("forged-ap"),
500
+ id: `ap://${otherDid}/activities/forged-ap`
501
+ }), [gateway]), {
502
+ status: 401,
503
+ dispatched: 0
504
+ });
505
+ assertEquals(await deliver(await sign({
506
+ ...compatibleCreateJson("malformed"),
507
+ id: `https://user@example.com/.well-known/apgateway/${did}/activities/2`
508
+ }), [gateway]), {
509
+ status: 401,
510
+ dispatched: 0
511
+ });
512
+ });
513
+ function countingLoader(responses) {
514
+ const inner = createLoader(responses);
515
+ let count = 0;
516
+ return {
517
+ documentLoader(url, options) {
518
+ if (url.replace(/#.*$/, "") === compatibleActorId(gateway)) count++;
519
+ return inner(url, options);
520
+ },
521
+ fetched: () => count
522
+ };
523
+ }
524
+ async function signedRequest(keyId = gatewayKeyId, privateKey = rsaPrivateKey2, spec) {
525
+ return await signRequest(new Request("https://local.example/notes/1", { headers: { Accept: "application/activity+json" } }), privateKey, keyId, spec == null ? {} : { spec });
526
+ }
527
+ test("handleInbox() fetches the actor of a gateway key once", async () => {
528
+ for (const [keyId, actor] of [[gatewayKeyId, await actorDocument([gateway])], [apKeyId, await apActorDocument()]]) {
529
+ const { documentLoader, fetched } = countingLoader({ [compatibleActorId(gateway)]: actor });
530
+ const kv = new MemoryKvStore();
531
+ for (let i = 0; i < 2; i++) {
532
+ assertEquals(await deliver(await createJson(`once-${i}`), [gateway], {
533
+ kv,
534
+ documentLoader,
535
+ keyId
536
+ }), {
537
+ status: 401,
538
+ dispatched: 0
539
+ });
540
+ assertEquals(fetched(), 1);
541
+ }
542
+ }
543
+ });
544
+ test("RequestContext.getSignedKeyOwner() does not fetch the actor of a gateway key again", async () => {
545
+ for (const [keyId, actor, gateways] of [[
546
+ gatewayKeyId,
547
+ await actorDocument([gateway, otherGateway]),
548
+ [`${gateway}/`, `${otherGateway}/`]
549
+ ], [
550
+ apKeyId,
551
+ await apActorDocument(),
552
+ [`${gateway}/`]
553
+ ]]) {
554
+ const { documentLoader, fetched } = countingLoader({ [compatibleActorId(gateway)]: actor });
555
+ const ctx = new FederationImpl({
556
+ kv: new MemoryKvStore(),
557
+ contextLoaderFactory: () => mockDocumentLoader,
558
+ documentLoaderFactory: () => documentLoader
559
+ }).createContext(await signedRequest(keyId), void 0);
560
+ assertEquals((await ctx.getSignedKey())?.id?.href, keyId.href);
561
+ assertEquals(fetched(), 1);
562
+ const owner = await ctx.getSignedKeyOwner();
563
+ assertEquals(owner?.id?.href, actorId.href);
564
+ assertEquals(owner?.gateways.map((g) => g.href), [...gateways]);
565
+ assertEquals(fetched(), 1);
566
+ }
567
+ });
568
+ test("getKeyOwner() reuses the actor of a cached gateway key", async () => {
569
+ for (const spec of [void 0, "rfc9421"]) {
570
+ const { documentLoader, fetched } = countingLoader({ [compatibleActorId(gateway)]: await actorDocument([gateway]) });
571
+ const options = {
572
+ documentLoader,
573
+ contextLoader: mockDocumentLoader,
574
+ keyCache: new KvKeyCache(new MemoryKvStore(), ["pk"])
575
+ };
576
+ const fresh = await verifyRequest(await signedRequest(gatewayKeyId, rsaPrivateKey2, spec), options);
577
+ const cached = await verifyRequest(await signedRequest(gatewayKeyId, rsaPrivateKey2, spec), options);
578
+ assertEquals(fetched(), 1);
579
+ for (const key of [fresh, cached]) assertEquals((await getKeyOwner(key, options))?.id?.href, actorId.href);
580
+ assertEquals(fetched(), 1);
581
+ }
582
+ });
583
+ test("doesActorOwnKey() checks the actor of a verified gateway key without fetching", async () => {
584
+ const { documentLoader, fetched } = countingLoader({ [compatibleActorId(gateway)]: await actorDocument([gateway]) });
585
+ const options = {
586
+ documentLoader,
587
+ contextLoader: mockDocumentLoader
588
+ };
589
+ const key = await verifyRequest(await signedRequest(), options);
590
+ assertEquals(fetched(), 1);
591
+ assertEquals(await doesActorOwnKey(portableCreate(), key, options), true);
592
+ assertEquals(await doesActorOwnKey(new Create({
593
+ id: parseIri(`ap+ef61://${otherDid}/activities/1`),
594
+ actor: parseIri(`ap+ef61://${otherDid}/actors/alice`)
595
+ }), key, options), false);
596
+ assertEquals(fetched(), 1);
597
+ });
598
+ test("doesActorOwnKey() and getKeyOwner() fully check other gateway keys", async () => {
599
+ const { documentLoader, fetched } = countingLoader({ [compatibleActorId(gateway)]: await actorDocument([gateway]) });
600
+ const options = {
601
+ documentLoader,
602
+ contextLoader: mockDocumentLoader
603
+ };
604
+ const verified = await verifyRequest(await signedRequest(), options);
605
+ assertEquals(fetched(), 1);
606
+ const others = [new CryptographicKey({
607
+ id: gatewayKeyId,
608
+ owner: actorId,
609
+ publicKey: rsaPublicKey2.publicKey
610
+ }), verified.clone({})];
611
+ let expected = 1;
612
+ for (const key of others) {
613
+ assertEquals(await doesActorOwnKey(portableCreate(), key, options), true);
614
+ assertEquals(fetched(), ++expected);
615
+ assertEquals((await getKeyOwner(key, options))?.id?.href, actorId.href);
616
+ assertEquals(fetched(), ++expected);
617
+ }
618
+ const mutated = await verifyRequest(await signedRequest(), options);
619
+ mutated.ownerId.href = parseIri(`ap+ef61://${otherDid}/actors/alice`).href;
620
+ assertEquals(getVerifiedKeyOwnerEvidence(mutated), void 0);
621
+ });
622
+ test("verifyRequest() does not vouch for the owners of keys that fail to verify signatures", async () => {
623
+ const documentLoader = createLoader({ [compatibleActorId(gateway)]: await actorDocument([gateway]) });
624
+ const cachedKeys = [];
625
+ const keyCache = new class extends KvKeyCache {
626
+ compatibleKeyScope(scope) {
627
+ const scoped = super.compatibleKeyScope(scope);
628
+ return {
629
+ ...scoped,
630
+ set(keyId, key, options) {
631
+ if (key instanceof CryptographicKey) cachedKeys.push(key);
632
+ return scoped.set(keyId, key, options);
633
+ }
634
+ };
635
+ }
636
+ }(new MemoryKvStore(), ["pk"]);
637
+ assertEquals((await verifyRequestDetailed(await signedRequest(gatewayKeyId, rsaPrivateKey3), {
638
+ documentLoader,
639
+ contextLoader: mockDocumentLoader,
640
+ keyCache
641
+ })).verified, false);
642
+ assertEquals(cachedKeys.length, 1);
643
+ assertEquals(getVerifiedKeyOwnerEvidence(cachedKeys[0]), void 0);
644
+ });
645
+ test("doesActorOwnKey() fully checks cached gateway keys cached without their actors", async () => {
646
+ const { documentLoader, fetched } = countingLoader({ [compatibleActorId(gateway)]: await actorDocument([gateway]) });
647
+ const kv = new MemoryKvStore();
648
+ const options = {
649
+ documentLoader,
650
+ contextLoader: mockDocumentLoader,
651
+ keyCache: new KvKeyCache(kv, ["pk"])
652
+ };
653
+ await verifyRequest(await signedRequest(), options);
654
+ assertEquals(fetched(), 1);
655
+ const entryKey = [
656
+ "pk",
657
+ "__compatible",
658
+ "httpSignature",
659
+ gatewayKeyId.href
660
+ ];
661
+ const { owner: _, ...entry } = await kv.get(entryKey) ?? {};
662
+ await kv.set(entryKey, entry);
663
+ const key = await verifyRequest(await signedRequest(), options);
664
+ assertEquals(fetched(), 1);
665
+ assertEquals(getVerifiedKeyOwnerEvidence(key), void 0);
666
+ assertEquals(await doesActorOwnKey(portableCreate(), key, options), true);
667
+ assertEquals(fetched(), 2);
668
+ });
669
+ test("Owners of verified gateway keys expire with their cache entries", async () => {
670
+ const documentLoader = createLoader({ [compatibleActorId(gateway)]: await actorDocument([gateway]) });
671
+ const now = Temporal.Now.instant();
672
+ const keyCache = new KvKeyCache(new MemoryKvStore(), ["pk"], {
673
+ keyTtl: Temporal.Duration.from({ minutes: 5 }),
674
+ now: () => now
675
+ });
676
+ const key = await verifyRequest(await signedRequest(), {
677
+ documentLoader,
678
+ contextLoader: mockDocumentLoader,
679
+ keyCache
680
+ });
681
+ const deadline = now.add({ minutes: 5 });
682
+ assertEquals(getVerifiedKeyOwnerEvidence(key, deadline.subtract({ seconds: 1 }))?.expires, deadline);
683
+ assertEquals(getVerifiedKeyOwnerEvidence(key, deadline), void 0);
684
+ });
685
+ //#endregion
686
+ export {};
@@ -0,0 +1,2 @@
1
+ import { Temporal } from "temporal-polyfill";
2
+ import { URLPattern } from "urlpattern-polyfill";