@fedify/fedify 2.4.0-dev.2169 → 2.4.0-dev.2190

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 (103) hide show
  1. package/dist/{builder-B2d_Pt0j.mjs → builder-CFF0YNTp.mjs} +3 -3
  2. package/dist/{circuit-breaker-C5NqoLBJ.mjs → circuit-breaker-XJ53md4S.mjs} +16 -11
  3. package/dist/compat/mod.d.cts +1 -1
  4. package/dist/compat/mod.d.ts +1 -1
  5. package/dist/compat/transformers.test.mjs +1 -1
  6. package/dist/{compound-proof-D28gMFqP.mjs → compound-proof-F2jjkrt0.mjs} +3 -3
  7. package/dist/{context-BykNUf04.d.ts → context-9T--NSo4.d.ts} +55 -1
  8. package/dist/{context-L0Fnib9j.d.cts → context-CDcAgm4l.d.cts} +55 -1
  9. package/dist/{deno-DU8dvnWk.mjs → deno-EopndaNb.mjs} +1 -1
  10. package/dist/{docloader-CghLH7Zi.mjs → docloader-u6zzOExF.mjs} +25 -17
  11. package/dist/federation/builder.test.mjs +1 -1
  12. package/dist/federation/circuit-breaker.test.mjs +50 -1
  13. package/dist/federation/compound-fanout.test.mjs +3 -3
  14. package/dist/federation/handler-compound-proof.test.mjs +3 -3
  15. package/dist/federation/handler.test.mjs +5 -5
  16. package/dist/federation/hashlink-media.test.mjs +2 -2
  17. package/dist/federation/idempotency.test.mjs +2 -2
  18. package/dist/federation/keycache.test.mjs +1 -1
  19. package/dist/federation/metrics.test.mjs +1 -1
  20. package/dist/federation/middleware.test.mjs +137 -13
  21. package/dist/federation/mod.cjs +1 -1
  22. package/dist/federation/mod.d.cts +2 -2
  23. package/dist/federation/mod.d.ts +2 -2
  24. package/dist/federation/mod.js +1 -1
  25. package/dist/federation/outgoing-proof.test.mjs +5 -5
  26. package/dist/federation/portable-actor.test.mjs +3 -3
  27. package/dist/federation/portable-chain.test.mjs +3 -3
  28. package/dist/federation/portable-collection.test.mjs +4 -4
  29. package/dist/federation/portable-gateway-keys.test.mjs +6 -6
  30. package/dist/federation/portable-inbox.test.mjs +35 -5
  31. package/dist/federation/portable-parse-uri.test.mjs +1 -1
  32. package/dist/federation/portable-route-activity.test.d.mts +2 -0
  33. package/dist/federation/portable-route-activity.test.mjs +259 -0
  34. package/dist/federation/portable.test.mjs +3 -3
  35. package/dist/federation/send.test.mjs +3 -3
  36. package/dist/federation/tasks/enqueue.test.mjs +1 -1
  37. package/dist/federation/tasks/tasks.test.mjs +2 -2
  38. package/dist/federation/temporal.test.mjs +1 -1
  39. package/dist/federation/webfinger.test.mjs +1 -1
  40. package/dist/{federation-DrNgkLfJ.cjs → federation-BSS2dUO7.cjs} +38 -21
  41. package/dist/{http-BGpexPO7.js → http-BqN5kyUf.js} +219 -87
  42. package/dist/{http-Cok5-vIC.d.ts → http-BrwKINQn.d.cts} +29 -1
  43. package/dist/{http-5CJBdU8n.cjs → http-CCr2_ipG.cjs} +236 -86
  44. package/dist/{http-DC1cqAQ2.d.cts → http-DGx0KOws.d.ts} +29 -1
  45. package/dist/{http-Bd6rZlJX.mjs → http-Y1ObT1A8.mjs} +180 -92
  46. package/dist/{key-gIhVZO3p.mjs → key-DyXf88bq.mjs} +4 -4
  47. package/dist/{key-owner-evidence-Bo3yaeL5.mjs → key-owner-evidence-APB6NpSk.mjs} +1 -1
  48. package/dist/{keycache-PiOM9pM0.mjs → keycache-Da8A8mZa.mjs} +1 -1
  49. package/dist/{kv-cache-iR55fqlk.js → kv-cache-C02XitwK.js} +24 -16
  50. package/dist/{kv-cache-D0qjSeWY.mjs → kv-cache-C8YjNAi_.mjs} +1 -1
  51. package/dist/{kv-cache-YRi_g8GB.cjs → kv-cache-DUXluAc0.cjs} +23 -15
  52. package/dist/{ld-BsTkRrwF.mjs → ld-oQj6j_hP.mjs} +3 -3
  53. package/dist/{metrics-jUDFfVgi.mjs → metrics-zMe834ID.mjs} +1 -1
  54. package/dist/{middleware-Coqgp-K0.mjs → middleware-Bdu2-OPD.mjs} +37 -25
  55. package/dist/{middleware-DlxMxVp5.js → middleware-CaR2MRWY.js} +39 -22
  56. package/dist/{middleware-Dq-OCmb6.mjs → middleware-Ck9yghxs.mjs} +1 -1
  57. package/dist/{mod-DK0y9fd-.d.ts → mod-B3OBLj5g.d.ts} +1 -1
  58. package/dist/{mod-B5z_q9TH.d.ts → mod-C5Mwx5ty.d.ts} +5 -1
  59. package/dist/{mod--jv6vyrP.d.ts → mod-Ck9_lSJB.d.ts} +1 -1
  60. package/dist/{mod-BLOXGs6m.d.cts → mod-D58KCqUg.d.cts} +1 -1
  61. package/dist/{mod-CPPqkyV_.d.cts → mod-DHTuu3l8.d.cts} +1 -1
  62. package/dist/{mod-BA_F2x53.d.cts → mod-Dp3Ij7sw.d.cts} +5 -1
  63. package/dist/mod.cjs +4 -4
  64. package/dist/mod.d.cts +5 -5
  65. package/dist/mod.d.ts +5 -5
  66. package/dist/mod.js +4 -4
  67. package/dist/nodeinfo/handler.test.mjs +1 -1
  68. package/dist/{owner-D3GoetXS.mjs → owner-Dem4fbwc.mjs} +5 -5
  69. package/dist/{portable-collection-Bkdh_WpL.js → portable-collection-BEqLrMW3.js} +1 -1
  70. package/dist/{portable-collection-C60_acmx.mjs → portable-collection-BL6aZlQ0.mjs} +2 -2
  71. package/dist/{portable-collection-CnlCamup.cjs → portable-collection-DJe_bk50.cjs} +1 -1
  72. package/dist/{portable-key-DyF5AnZo.mjs → portable-key-Duue9Y2Y.mjs} +2 -2
  73. package/dist/{portable-key-id-Bg259c4Z.mjs → portable-key-id-Ca30-hOa.mjs} +45 -1
  74. package/dist/{proof-CQAYqWdj.mjs → proof-C9115DX_.mjs} +5 -5
  75. package/dist/{send-DjkQrxt5.mjs → send-D0hxFFRB.mjs} +3 -3
  76. package/dist/sig/compound-proof-discovery.test.mjs +1 -1
  77. package/dist/sig/compound-proof-producer.test.mjs +1 -1
  78. package/dist/sig/compound-proof-verification.test.mjs +2 -2
  79. package/dist/sig/compound-proof.test.mjs +2 -2
  80. package/dist/sig/http.test.mjs +495 -23
  81. package/dist/sig/key-owner-evidence.test.mjs +2 -2
  82. package/dist/sig/key.test.mjs +1 -1
  83. package/dist/sig/ld.test.mjs +2 -2
  84. package/dist/sig/mod.cjs +2 -2
  85. package/dist/sig/mod.d.cts +2 -2
  86. package/dist/sig/mod.d.ts +2 -2
  87. package/dist/sig/mod.js +2 -2
  88. package/dist/sig/owner.test.mjs +1 -1
  89. package/dist/sig/portable-collection.test.mjs +1 -1
  90. package/dist/sig/portable-key-id.test.d.mts +2 -0
  91. package/dist/sig/portable-key-id.test.mjs +67 -0
  92. package/dist/sig/portable-key.test.mjs +7 -7
  93. package/dist/sig/proof.test.mjs +3 -3
  94. package/dist/{temporal-oRKP803M.mjs → temporal-DKchpKyp.mjs} +1 -1
  95. package/dist/testing/kv.test.mjs +1 -1
  96. package/dist/testing/mod.d.mts +52 -0
  97. package/dist/utils/docloader.test.mjs +93 -2
  98. package/dist/utils/kv-cache.test.mjs +1 -1
  99. package/dist/utils/mod.cjs +1 -1
  100. package/dist/utils/mod.d.cts +1 -1
  101. package/dist/utils/mod.d.ts +1 -1
  102. package/dist/utils/mod.js +1 -1
  103. package/package.json +7 -7
@@ -0,0 +1,259 @@
1
+ import "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 "../std__assert-DvMDRPes.mjs";
8
+ import { t as assert } from "../assert-D1Eafmd_.mjs";
9
+ import { a as signObject } from "../proof-C9115DX_.mjs";
10
+ import { n as ed25519PrivateKey, r as ed25519PublicKey } from "../keys-DIJNHW-Y.mjs";
11
+ import { o as createFederation } from "../middleware-Bdu2-OPD.mjs";
12
+ import { Create, Note } from "@fedify/vocab";
13
+ import { mockDocumentLoader, test } from "@fedify/fixture";
14
+ import { exportDidKey, formatIri, parseIri, toCompatibleEf61Id } from "@fedify/vocab-runtime";
15
+ //#region src/federation/portable-route-activity.test.ts
16
+ const did = await exportDidKey(ed25519PublicKey.publicKey);
17
+ const keyId = new URL(`${did}#${did.slice(8)}`);
18
+ const otherKeyPair = await crypto.subtle.generateKey("Ed25519", true, ["sign", "verify"]);
19
+ const otherDid = await exportDidKey(otherKeyPair.publicKey);
20
+ const otherKeyId = new URL(`${otherDid}#${otherDid.slice(8)}`);
21
+ const GATEWAY = "https://gw.example";
22
+ function portableId(authority, path) {
23
+ return parseIri(`ap+ef61://${authority}${path}`);
24
+ }
25
+ function compatibleId(authority, path, gateway = GATEWAY) {
26
+ return new URL(`${gateway}/.well-known/apgateway/${authority}${path}`);
27
+ }
28
+ async function sign(activity, signer) {
29
+ return await signObject(activity, signer === "did" ? ed25519PrivateKey : otherKeyPair.privateKey, signer === "did" ? keyId : otherKeyId, { contextLoader: mockDocumentLoader });
30
+ }
31
+ /**
32
+ * Routes a `Create` activity with the given ID through
33
+ * `Context.routeActivity()`. Unless `activity` is an `Activity`, the routed
34
+ * activity has no proof, so that it is trusted only if it can be dereferenced
35
+ * by its ID, which serves `served`.
36
+ * @returns Whether the activity was routed, and the IDs of the actors of
37
+ * the activities that reached the inbox listener.
38
+ */
39
+ async function route(activity, { served, actors } = {}) {
40
+ const documents = /* @__PURE__ */ new Map();
41
+ if (served != null && activity instanceof URL) {
42
+ const document = await served.toJsonLd({ contextLoader: mockDocumentLoader });
43
+ documents.set(activity.href, document);
44
+ if (activity.protocol === "ap+ef61:") {
45
+ documents.set(formatIri(activity), document);
46
+ documents.set(toCompatibleEf61Id(activity, GATEWAY).href, document);
47
+ }
48
+ }
49
+ const documentLoader = (url, options) => {
50
+ if (documents.has(url)) return Promise.resolve({
51
+ contextUrl: null,
52
+ documentUrl: url,
53
+ document: documents.get(url)
54
+ });
55
+ return mockDocumentLoader(url, options);
56
+ };
57
+ const federation = createFederation({
58
+ kv: new MemoryKvStore(),
59
+ documentLoaderFactory: () => documentLoader,
60
+ contextLoaderFactory: () => mockDocumentLoader
61
+ });
62
+ const received = [];
63
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, (_ctx, create) => {
64
+ received.push(...create.actorIds.map((id) => id.href));
65
+ });
66
+ return {
67
+ routed: await federation.createContext(new URL("https://example.com/")).routeActivity(null, activity instanceof URL ? new Create({
68
+ id: activity,
69
+ actors: [...actors ?? served?.actorIds ?? []]
70
+ }) : activity),
71
+ received
72
+ };
73
+ }
74
+ async function portableCreate(id, actors, signer = "did") {
75
+ return await sign(new Create({
76
+ id,
77
+ actors: [...actors],
78
+ object: new Note({ content: "Hello" })
79
+ }), signer);
80
+ }
81
+ test("Context.routeActivity() rejects portable activities attributed to other DIDs", async (t) => {
82
+ const cases = [
83
+ [
84
+ "ap+ef61: IDs",
85
+ portableId(did, "/activities/1"),
86
+ [portableId(otherDid, "/actor")]
87
+ ],
88
+ [
89
+ "compatible identifiers on one gateway",
90
+ compatibleId(did, "/activities/1"),
91
+ [compatibleId(otherDid, "/actor")]
92
+ ],
93
+ [
94
+ "compatible activity ID and ap+ef61: actor ID",
95
+ compatibleId(did, "/activities/1"),
96
+ [portableId(otherDid, "/actor")]
97
+ ],
98
+ [
99
+ "ap+ef61: activity ID and compatible actor ID",
100
+ portableId(did, "/activities/1"),
101
+ [compatibleId(otherDid, "/actor")]
102
+ ],
103
+ [
104
+ "one of multiple actors with another DID",
105
+ portableId(did, "/activities/1"),
106
+ [portableId(did, "/actor"), portableId(otherDid, "/actor")]
107
+ ],
108
+ [
109
+ "ap+ef61: activity ID and HTTP(S) actor ID",
110
+ portableId(did, "/activities/1"),
111
+ [new URL("https://example.com/person")]
112
+ ],
113
+ [
114
+ "compatible activity ID and HTTP(S) actor ID on the gateway",
115
+ compatibleId(did, "/activities/1"),
116
+ [new URL(`${GATEWAY}/users/alice`)]
117
+ ]
118
+ ];
119
+ for (const [name, id, actors] of cases) await t.step(name, async () => {
120
+ const served = await portableCreate(id, actors);
121
+ assertEquals(await route(id, { served }), {
122
+ routed: false,
123
+ received: []
124
+ });
125
+ assertEquals(await route(served), {
126
+ routed: false,
127
+ received: []
128
+ });
129
+ });
130
+ });
131
+ test("Context.routeActivity() accepts portable activities of the same DID", async (t) => {
132
+ const cases = [
133
+ [
134
+ "ap+ef61: IDs",
135
+ portableId(did, "/activities/1"),
136
+ [portableId(did, "/actor")]
137
+ ],
138
+ [
139
+ "compatible identifiers on one gateway",
140
+ compatibleId(did, "/activities/1"),
141
+ [compatibleId(did, "/actor")]
142
+ ],
143
+ [
144
+ "compatible identifiers on different gateways",
145
+ compatibleId(did, "/activities/1"),
146
+ [compatibleId(did, "/actor", "https://gw2.example")]
147
+ ],
148
+ [
149
+ "compatible activity ID and ap+ef61: actor ID",
150
+ compatibleId(did, "/activities/1"),
151
+ [portableId(did, "/actor")]
152
+ ],
153
+ [
154
+ "ap+ef61: activity ID and compatible actor ID",
155
+ portableId(did, "/activities/1"),
156
+ [compatibleId(did, "/actor")]
157
+ ],
158
+ [
159
+ "actor ID with a location hint",
160
+ portableId(did, "/activities/1"),
161
+ [portableId(did, `/actor?@gateway=${encodeURIComponent(GATEWAY)}`)]
162
+ ]
163
+ ];
164
+ for (const [name, id, actors] of cases) await t.step(name, async () => {
165
+ const served = await portableCreate(id, actors);
166
+ const expected = {
167
+ routed: true,
168
+ received: actors.map((actor) => actor.href)
169
+ };
170
+ assertEquals(await route(id, { served }), expected);
171
+ assertEquals(await route(served), expected);
172
+ });
173
+ });
174
+ test("Context.routeActivity() compares portable activity IDs canonically", async (t) => {
175
+ const id = portableId(did, "/activities/1");
176
+ const actors = [portableId(did, "/actor")];
177
+ await t.step("compatible ID serving an ap+ef61: ID", async () => {
178
+ const served = await portableCreate(id, actors);
179
+ const { routed } = await route(compatibleId(did, "/activities/1"), { served });
180
+ assert(routed);
181
+ });
182
+ await t.step("ap+ef61: ID serving a compatible ID", async () => {
183
+ const served = await portableCreate(compatibleId(did, "/activities/1"), actors);
184
+ const { routed } = await route(id, { served });
185
+ assert(routed);
186
+ });
187
+ await t.step("ap+ef61: ID with a location hint", async () => {
188
+ const served = await portableCreate(id, actors);
189
+ const { routed } = await route(portableId(did, `/activities/1?@gateway=${encodeURIComponent(GATEWAY)}`), { served });
190
+ assert(routed);
191
+ });
192
+ await t.step("another path of the same DID", async () => {
193
+ const served = await portableCreate(portableId(did, "/activities/2"), actors);
194
+ const { routed } = await route(id, { served });
195
+ assertFalse(routed);
196
+ });
197
+ await t.step("another fragment of the same DID", async () => {
198
+ const served = await portableCreate(portableId(did, "/activities/1#other"), actors);
199
+ const { routed } = await route(id, { served });
200
+ assertFalse(routed);
201
+ });
202
+ });
203
+ test("Context.routeActivity() rejects proofs of other DIDs for portable actors", async () => {
204
+ const id = portableId(did, "/activities/1");
205
+ const actors = [portableId(did, "/actor")];
206
+ assertEquals(await route(await portableCreate(id, actors, "otherDid")), {
207
+ routed: false,
208
+ received: []
209
+ });
210
+ assertEquals(await route(await portableCreate(id, actors)), {
211
+ routed: true,
212
+ received: actors.map((actor) => actor.href)
213
+ });
214
+ });
215
+ test("Context.routeActivity() keeps comparing web origins of HTTP(S) IDs", async (t) => {
216
+ const cases = [
217
+ [
218
+ "same origin",
219
+ new URL("https://remote.example/activities/1"),
220
+ new URL("https://remote.example/users/alice"),
221
+ true
222
+ ],
223
+ [
224
+ "same origin with an explicit default port",
225
+ new URL("https://remote.example/activities/1"),
226
+ new URL("https://remote.example:443/users/alice"),
227
+ true
228
+ ],
229
+ [
230
+ "another origin",
231
+ new URL("https://remote.example/activities/1"),
232
+ new URL("https://other.example/users/alice"),
233
+ false
234
+ ],
235
+ [
236
+ "another port",
237
+ new URL("https://remote.example/activities/1"),
238
+ new URL("https://remote.example:8443/users/alice"),
239
+ false
240
+ ],
241
+ [
242
+ "compatible actor ID on the same host",
243
+ new URL("https://remote.example/activities/1"),
244
+ compatibleId(did, "/actor", "https://remote.example"),
245
+ false
246
+ ]
247
+ ];
248
+ for (const [name, id, actor, expected] of cases) await t.step(name, async () => {
249
+ const served = new Create({
250
+ id,
251
+ actor
252
+ });
253
+ const { routed, received } = await route(id, { served });
254
+ assertEquals(routed, expected);
255
+ assertEquals(received, expected ? [actor.href] : []);
256
+ });
257
+ });
258
+ //#endregion
259
+ export {};
@@ -8,10 +8,10 @@ import { t as assertThrows } from "../assert_throws-BYITOvPO.mjs";
8
8
  import "../std__assert-DvMDRPes.mjs";
9
9
  import { t as assertStrictEquals } from "../assert_strict_equals-CYvBmkpC.mjs";
10
10
  import { t as assert } from "../assert-D1Eafmd_.mjs";
11
- import { a as signObject, c as verifyPortableObjectProof } from "../proof-CQAYqWdj.mjs";
12
- import { l as signRequest } from "../http-Bd6rZlJX.mjs";
11
+ import { a as signObject, c as verifyPortableObjectProof } from "../proof-C9115DX_.mjs";
12
+ import { l as signRequest } from "../http-Y1ObT1A8.mjs";
13
13
  import { a as rsaPrivateKey3, c as rsaPublicKey3, i as rsaPrivateKey2, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2 } from "../keys-DIJNHW-Y.mjs";
14
- import { b as PORTABLE_OBJECT_CONTENT_TYPE, o as createFederation } from "../middleware-Coqgp-K0.mjs";
14
+ import { b as PORTABLE_OBJECT_CONTENT_TYPE, o as createFederation } from "../middleware-Bdu2-OPD.mjs";
15
15
  import { configure, reset } from "@logtape/logtape";
16
16
  import { Collection, Create, Note, Object as Object$1, Person, Tombstone, lookupObject } from "@fedify/vocab";
17
17
  import { mockDocumentLoader, test } from "@fedify/fixture";
@@ -10,10 +10,10 @@ import { t as assertNotEquals } from "../assert_not_equals-CpPK2t89.mjs";
10
10
  import "../std__assert-DvMDRPes.mjs";
11
11
  import { t as assertRejects } from "../assert_rejects-CTptdYck.mjs";
12
12
  import { t as assert } from "../assert-D1Eafmd_.mjs";
13
- import { d as verifyRequest } from "../http-Bd6rZlJX.mjs";
13
+ import { f as verifyRequest } from "../http-Y1ObT1A8.mjs";
14
14
  import { i as rsaPrivateKey2, n as ed25519PrivateKey, s as rsaPublicKey2, t as ed25519Multikey } from "../keys-DIJNHW-Y.mjs";
15
- import { t as doesActorOwnKey } from "../owner-D3GoetXS.mjs";
16
- import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "../send-DjkQrxt5.mjs";
15
+ import { t as doesActorOwnKey } from "../owner-Dem4fbwc.mjs";
16
+ import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "../send-D0hxFFRB.mjs";
17
17
  import { Activity, Application, Endpoints, Group, Person, Service } from "@fedify/vocab";
18
18
  import { createTestMeterProvider, createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
19
19
  import { FetchError, UrlError } from "@fedify/vocab-runtime";
@@ -4,7 +4,7 @@ globalThis.addEventListener = () => {};
4
4
  import { t as MemoryKvStore } from "../../kv-DCHkaJ9-.mjs";
5
5
  import { a as numberSchema, i as makeSchema, n as baseOptions, o as stringSchema, t as MockQueue } from "../../tasks-IG3pHfvZ.mjs";
6
6
  import "../../testing-C1itIjqW.mjs";
7
- import { o as createFederation } from "../../middleware-Coqgp-K0.mjs";
7
+ import { o as createFederation } from "../../middleware-Bdu2-OPD.mjs";
8
8
  import { n as ParallelMessageQueue } from "../../mq-Dm1Wmtm-.mjs";
9
9
  import { configure, reset } from "@logtape/logtape";
10
10
  import { delay } from "es-toolkit";
@@ -4,8 +4,8 @@ globalThis.addEventListener = () => {};
4
4
  import { t as MemoryKvStore } from "../../kv-DCHkaJ9-.mjs";
5
5
  import { a as numberSchema, i as makeSchema, n as baseOptions, o as stringSchema, t as MockQueue } from "../../tasks-IG3pHfvZ.mjs";
6
6
  import "../../testing-C1itIjqW.mjs";
7
- import { o as createFederation } from "../../middleware-Coqgp-K0.mjs";
8
- import { r as createFederationBuilder } from "../../builder-B2d_Pt0j.mjs";
7
+ import { o as createFederation } from "../../middleware-Bdu2-OPD.mjs";
8
+ import { r as createFederationBuilder } from "../../builder-CFF0YNTp.mjs";
9
9
  import { t as TaskCodec } from "../../codec-C6rTrpIp.mjs";
10
10
  import { t as InProcessMessageQueue } from "../../mq-Dm1Wmtm-.mjs";
11
11
  import { W3CTraceContextPropagator } from "@opentelemetry/core";
@@ -4,7 +4,7 @@ globalThis.addEventListener = () => {};
4
4
  import { t as assertFalse } from "../assert_false-CPdRnc3E.mjs";
5
5
  import "../std__assert-DvMDRPes.mjs";
6
6
  import { t as assert } from "../assert-D1Eafmd_.mjs";
7
- import { t as hasMalformedKnownTemporalLiteral } from "../temporal-oRKP803M.mjs";
7
+ import { t as hasMalformedKnownTemporalLiteral } from "../temporal-DKchpKyp.mjs";
8
8
  import { test } from "@fedify/fixture";
9
9
  //#region src/federation/temporal.test.ts
10
10
  test("hasMalformedKnownTemporalLiteral() detects expanded proof timestamps", async () => {
@@ -6,7 +6,7 @@ import { t as MemoryKvStore } from "../kv-DCHkaJ9-.mjs";
6
6
  import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
7
7
  import { t as assertNotEquals } from "../assert_not_equals-CpPK2t89.mjs";
8
8
  import "../std__assert-DvMDRPes.mjs";
9
- import { o as createFederation, s as handleWebFinger } from "../middleware-Coqgp-K0.mjs";
9
+ import { o as createFederation, s as handleWebFinger } from "../middleware-Bdu2-OPD.mjs";
10
10
  import { Image, Link, Person, Tombstone } from "@fedify/vocab";
11
11
  import { createTestMeterProvider, test } from "@fedify/fixture";
12
12
  import { parseIri } from "@fedify/vocab-runtime";
@@ -1,10 +1,10 @@
1
1
  const { URLPattern } = require("urlpattern-polyfill");
2
2
  const require_rolldown_runtime = require("./rolldown-runtime-Dmm3t92W.cjs");
3
3
  const require_transformers = require("./transformers-BalCrSsr.cjs");
4
- const require_http = require("./http-5CJBdU8n.cjs");
4
+ const require_http = require("./http-CCr2_ipG.cjs");
5
5
  const require_types = require("./types-poIao40f.cjs");
6
- const require_portable_collection = require("./portable-collection-CnlCamup.cjs");
7
- const require_kv_cache = require("./kv-cache-YRi_g8GB.cjs");
6
+ const require_portable_collection = require("./portable-collection-DJe_bk50.cjs");
7
+ const require_kv_cache = require("./kv-cache-DUXluAc0.cjs");
8
8
  let _logtape_logtape = require("@logtape/logtape");
9
9
  let _fedify_uri_template = require("@fedify/uri-template");
10
10
  let _fedify_vocab = require("@fedify/vocab");
@@ -886,7 +886,7 @@ var CircuitBreaker = class {
886
886
  };
887
887
  let lastConflictingState;
888
888
  for (let attempt = 0; attempt < 10; attempt++) {
889
- const oldState = await this.#get(remoteHost);
889
+ const { state: oldState, raw: oldValue } = await this.#get(remoteHost);
890
890
  if (oldState == null || oldState.state === "closed") return {
891
891
  type: "send",
892
892
  probe: false
@@ -912,7 +912,7 @@ var CircuitBreaker = class {
912
912
  state: "half-open",
913
913
  halfOpened: now.toString()
914
914
  };
915
- if (await this.#replace(remoteHost, oldState, newState)) return {
915
+ if (await this.#replace(remoteHost, oldValue, newState)) return {
916
916
  type: "send",
917
917
  probe: true
918
918
  };
@@ -934,7 +934,7 @@ var CircuitBreaker = class {
934
934
  state: "half-open",
935
935
  halfOpened: now.toString()
936
936
  };
937
- if (await this.#replace(remoteHost, oldState, newState)) {
937
+ if (await this.#replace(remoteHost, oldValue, newState)) {
938
938
  await this.#notifyStateChange(remoteHost, "open", "half-open");
939
939
  return {
940
940
  type: "send",
@@ -961,9 +961,9 @@ var CircuitBreaker = class {
961
961
  async recordSuccess(remoteHost) {
962
962
  this.#sweepLegacyStates();
963
963
  for (let attempt = 0; attempt < 10; attempt++) {
964
- const oldState = await this.#get(remoteHost);
964
+ const { state: oldState, raw: oldValue } = await this.#get(remoteHost);
965
965
  if (oldState == null) return void 0;
966
- if (await this.#replace(remoteHost, oldState, void 0)) {
966
+ if (await this.#replace(remoteHost, oldValue, void 0)) {
967
967
  if (oldState.state !== "closed") {
968
968
  await this.#notifyStateChange(remoteHost, oldState.state, "closed");
969
969
  return {
@@ -983,7 +983,7 @@ var CircuitBreaker = class {
983
983
  this.#sweepLegacyStates();
984
984
  const now = this.#now();
985
985
  for (let attempt = 0; attempt < 10; attempt++) {
986
- const oldState = await this.#get(remoteHost);
986
+ const { state: oldState, raw: oldValue } = await this.#get(remoteHost);
987
987
  if (oldState?.state === "open") return void 0;
988
988
  const oldFailures = oldState?.failures.map(Temporal$11.Instant.from) ?? [];
989
989
  const failures = this.#options.pruneFailures([...oldFailures, now], now);
@@ -1000,7 +1000,7 @@ var CircuitBreaker = class {
1000
1000
  state: "closed",
1001
1001
  failures: failures.map((t) => t.toString())
1002
1002
  };
1003
- if (await this.#replace(remoteHost, oldState, newState)) {
1003
+ if (await this.#replace(remoteHost, oldValue, newState)) {
1004
1004
  if (transition != null) {
1005
1005
  await this.#notifyStateChange(remoteHost, transition[0], transition[1]);
1006
1006
  return {
@@ -1029,7 +1029,7 @@ var CircuitBreaker = class {
1029
1029
  }
1030
1030
  async getState(remoteHost) {
1031
1031
  this.#sweepLegacyStates();
1032
- return stripStoredCircuitBreakerState(await this.#get(remoteHost));
1032
+ return stripStoredCircuitBreakerState((await this.#get(remoteHost)).state);
1033
1033
  }
1034
1034
  /**
1035
1035
  * The currently running background legacy sweep, if any.
@@ -1149,9 +1149,14 @@ var CircuitBreaker = class {
1149
1149
  return Temporal$11.Instant.compare(expiresAt, retryAt) < 0 ? expiresAt : retryAt;
1150
1150
  }
1151
1151
  async #get(remoteHost) {
1152
- return parseStoredCircuitBreakerKvState(await this.#kv.get(this.#key(remoteHost)));
1152
+ const value = await this.#kv.get(this.#key(remoteHost));
1153
+ const state = parseStoredCircuitBreakerKvState(value);
1154
+ return {
1155
+ state,
1156
+ raw: state == null ? void 0 : value
1157
+ };
1153
1158
  }
1154
- async #replace(remoteHost, oldState, newState) {
1159
+ async #replace(remoteHost, oldValue, newState) {
1155
1160
  const key = this.#key(remoteHost);
1156
1161
  const storedState = newState == null ? void 0 : markCircuitBreakerState(newState);
1157
1162
  if (this.#kv.cas == null) {
@@ -1159,7 +1164,7 @@ var CircuitBreaker = class {
1159
1164
  else await this.#kv.set(key, storedState, this.#setOptions());
1160
1165
  return true;
1161
1166
  }
1162
- return await this.#kv.cas(key, oldState, storedState, storedState == null ? void 0 : this.#setOptions());
1167
+ return await this.#kv.cas(key, oldValue, storedState, storedState == null ? void 0 : this.#setOptions());
1163
1168
  }
1164
1169
  #setOptions() {
1165
1170
  return this.#options.stateTtl == null ? void 0 : { ttl: this.#options.stateTtl };
@@ -4667,7 +4672,7 @@ async function handleInbox(request, options) {
4667
4672
  * @returns A promise that resolves to an HTTP response.
4668
4673
  */
4669
4674
  async function handleInboxInternal(request, parameters, span) {
4670
- const { recipient, context: ctx, inboxContextFactory, kv, kvPrefixes, publicKeyTtl, queue, actorDispatcher, inboxListeners, inboxErrorHandler, unverifiedActivityHandler, onNotFound, signatureTimeWindow, skipSignatureVerification, inboxChallengePolicy, meterProvider, tracerProvider, portableInbox } = parameters;
4675
+ const { recipient, context: ctx, inboxContextFactory, kv, kvPrefixes, publicKeyTtl, queue, actorDispatcher, inboxListeners, inboxErrorHandler, unverifiedActivityHandler, onNotFound, signatureTimeWindow, maxHttpSignatures, skipSignatureVerification, inboxChallengePolicy, meterProvider, tracerProvider, portableInbox } = parameters;
4671
4676
  const logger = (0, _logtape_logtape.getLogger)([
4672
4677
  "fedify",
4673
4678
  "federation",
@@ -4953,6 +4958,7 @@ async function handleInboxInternal(request, parameters, span) {
4953
4958
  contextLoader: ctx.contextLoader,
4954
4959
  documentLoader: ctx.documentLoader,
4955
4960
  timeWindow: signatureTimeWindow,
4961
+ maxSignatures: maxHttpSignatures,
4956
4962
  keyCache,
4957
4963
  meterProvider,
4958
4964
  tracerProvider
@@ -5175,7 +5181,7 @@ async function handleInboxInternal(request, parameters, span) {
5175
5181
  inbox: portableInbox.recipient.canonicalInboxId
5176
5182
  });
5177
5183
  else try {
5178
- await portableInbox.forward(json, activity.id, (0, _fedify_vocab.getTypeId)(activity).href, require_http.listRequestSignatures(request).map((signature) => ({
5184
+ await portableInbox.forward(json, activity.id, (0, _fedify_vocab.getTypeId)(activity).href, require_http.listRequestSignatures(request, maxHttpSignatures).map((signature) => ({
5179
5185
  keyId: signature.keyId,
5180
5186
  coversDelivery: coversDelivery(signature),
5181
5187
  verify: async () => {
@@ -7246,6 +7252,8 @@ function isLinkedDataSignatureJsonLdProcessingError(error) {
7246
7252
  * @returns A new {@link Federation} instance.
7247
7253
  * @throws {TypeError} If benchmark mode and `meterProvider` are both
7248
7254
  * specified.
7255
+ * @throws {RangeError} If {@link FederationOptions.maxHttpSignatures} is not
7256
+ * a positive integer or `Infinity`.
7249
7257
  * @since 0.10.0
7250
7258
  */
7251
7259
  function createFederation(options) {
@@ -7272,6 +7280,7 @@ var FederationImpl = class extends FederationBuilderImpl {
7272
7280
  onOutboxError;
7273
7281
  permanentFailureStatusCodes;
7274
7282
  signatureTimeWindow;
7283
+ maxHttpSignatures;
7275
7284
  skipSignatureVerification;
7276
7285
  outboxRetryPolicy;
7277
7286
  inboxRetryPolicy;
@@ -7300,6 +7309,7 @@ var FederationImpl = class extends FederationBuilderImpl {
7300
7309
  const hasCustomLoaderFactory = options.documentLoaderFactory != null || options.contextLoaderFactory != null;
7301
7310
  const allowPrivateAddress = options.allowPrivateAddress ?? (benchmarkMode && !hasCustomLoaderFactory ? true : false);
7302
7311
  const signatureTimeWindow = options.signatureTimeWindow ?? (benchmarkMode ? false : { hours: 1 });
7312
+ if (options.maxHttpSignatures !== void 0) require_http.validateMaxSignatures(options.maxHttpSignatures, "maxHttpSignatures");
7303
7313
  if (benchmarkMode && options.meterProvider != null) throw new TypeError("benchmarkMode requires Fedify to own the meterProvider; OpenTelemetry metric readers cannot be added after a MeterProvider is constructed.");
7304
7314
  if (benchmarkMode) {
7305
7315
  const relaxations = getBenchmarkRelaxations(allowPrivateAddress, signatureTimeWindow);
@@ -7392,13 +7402,15 @@ var FederationImpl = class extends FederationBuilderImpl {
7392
7402
  }
7393
7403
  const { userAgent } = options;
7394
7404
  this.allowPrivateAddress = allowPrivateAddress;
7405
+ const documentLoaderTimeout = (0, _fedify_vocab_runtime.resolveDocumentLoaderTimeout)(options.documentLoaderTimeout == null ? options.documentLoaderTimeout : Temporal$2.Duration.from(options.documentLoaderTimeout).total("millisecond"));
7395
7406
  const userDocumentLoaderFactory = options.documentLoaderFactory;
7396
7407
  const userContextLoaderFactory = options.contextLoaderFactory;
7397
7408
  const userAuthFactory = options.authenticatedDocumentLoaderFactory;
7398
7409
  const builtinDocumentLoaderFactory = (opts) => require_kv_cache.kvCache({
7399
7410
  loader: (0, _fedify_vocab_runtime.getDocumentLoader)({
7400
7411
  allowPrivateAddress: opts?.allowPrivateAddress ?? allowPrivateAddress,
7401
- userAgent: opts?.userAgent ?? userAgent
7412
+ userAgent: opts?.userAgent ?? userAgent,
7413
+ timeout: opts?.timeout !== void 0 ? opts.timeout : documentLoaderTimeout
7402
7414
  }),
7403
7415
  kv: options.kv,
7404
7416
  prefix: this.kvPrefixes.remoteDocument,
@@ -7408,7 +7420,8 @@ var FederationImpl = class extends FederationBuilderImpl {
7408
7420
  const builtinContextLoaderFactory = (opts) => require_kv_cache.kvCache({
7409
7421
  loader: (0, _fedify_vocab_runtime.getDocumentLoader)({
7410
7422
  allowPrivateAddress: opts?.allowPrivateAddress ?? allowPrivateAddress,
7411
- userAgent: opts?.userAgent ?? userAgent
7423
+ userAgent: opts?.userAgent ?? userAgent,
7424
+ timeout: opts?.timeout !== void 0 ? opts.timeout : documentLoaderTimeout
7412
7425
  }),
7413
7426
  kv: options.kv,
7414
7427
  prefix: this.kvPrefixes.remoteDocument,
@@ -7429,6 +7442,7 @@ var FederationImpl = class extends FederationBuilderImpl {
7429
7442
  this.authenticatedDocumentLoaderFactory = (identity, factoryOpts) => require_http.instrumentDocumentLoader(userAuthFactory != null ? userAuthFactory(identity, factoryOpts) : require_kv_cache.getAuthenticatedDocumentLoader(identity, {
7430
7443
  allowPrivateAddress: factoryOpts?.allowPrivateAddress ?? allowPrivateAddress,
7431
7444
  userAgent: factoryOpts?.userAgent ?? userAgent,
7445
+ timeout: factoryOpts?.timeout !== void 0 ? factoryOpts.timeout : documentLoaderTimeout,
7432
7446
  specDeterminer: new KvSpecDeterminer(this.kv, this.kvPrefixes.httpMessageSignaturesSpec, options.firstKnock, { specTtl: this.httpMessageSignaturesSpecTtl }),
7433
7447
  tracerProvider: this.tracerProvider
7434
7448
  }), {
@@ -7440,6 +7454,7 @@ var FederationImpl = class extends FederationBuilderImpl {
7440
7454
  this.onOutboxError = options.onOutboxError;
7441
7455
  this.permanentFailureStatusCodes = options.permanentFailureStatusCodes ?? [404, 410];
7442
7456
  this.signatureTimeWindow = signatureTimeWindow;
7457
+ this.maxHttpSignatures = options.maxHttpSignatures ?? 3;
7443
7458
  this.skipSignatureVerification = options.skipSignatureVerification ?? false;
7444
7459
  this.inboxChallengePolicy = options.inboxChallengePolicy;
7445
7460
  this.outboxRetryPolicy = options.outboxRetryPolicy ?? createExponentialBackoffPolicy();
@@ -9065,6 +9080,7 @@ var FederationImpl = class extends FederationBuilderImpl {
9065
9080
  unverifiedActivityHandler: this.unverifiedActivityHandler,
9066
9081
  onNotFound,
9067
9082
  signatureTimeWindow: this.signatureTimeWindow,
9083
+ maxHttpSignatures: this.maxHttpSignatures,
9068
9084
  skipSignatureVerification: this.skipSignatureVerification,
9069
9085
  inboxChallengePolicy: this.inboxChallengePolicy,
9070
9086
  meterProvider: this.meterProvider,
@@ -10053,7 +10069,7 @@ var ContextImpl = class ContextImpl {
10053
10069
  activity: await fetched.toJsonLd({ contextLoader })
10054
10070
  });
10055
10071
  return false;
10056
- } else if (fetched.id?.href !== activity.id.href) {
10072
+ } else if (fetched.id == null || !require_http.isSameObjectId(fetched.id, activity.id)) {
10057
10073
  logger.debug("Fetched activity object has a different ID; failed to verify.", {
10058
10074
  recipient,
10059
10075
  activity: await fetched.toJsonLd({ contextLoader })
@@ -10066,8 +10082,8 @@ var ContextImpl = class ContextImpl {
10066
10082
  });
10067
10083
  return false;
10068
10084
  }
10069
- const activityId = fetched.id;
10070
- if (!fetched.actorIds.every((actor) => actor.origin === activityId.origin)) {
10085
+ const activityOrigin = require_http.getAuthenticationOrigin(fetched.id);
10086
+ if (activityOrigin == null || !fetched.actorIds.every((actor) => require_http.getAuthenticationOrigin(actor) === activityOrigin)) {
10071
10087
  logger.debug("Fetched activity object has actors from different origins; unable to verify.", {
10072
10088
  recipient,
10073
10089
  activity: await fetched.toJsonLd({ contextLoader })
@@ -10203,6 +10219,7 @@ var RequestContextImpl = class RequestContextImpl extends ContextImpl {
10203
10219
  contextLoader: options.contextLoader ?? this.contextLoader,
10204
10220
  documentLoader: options.documentLoader ?? this.documentLoader,
10205
10221
  timeWindow: this.federation.signatureTimeWindow,
10222
+ maxSignatures: this.federation.maxHttpSignatures,
10206
10223
  meterProvider: this.meterProvider,
10207
10224
  tracerProvider: options.tracerProvider ?? this.tracerProvider
10208
10225
  });