@fedify/fedify 2.4.0-dev.2164 → 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.
- package/dist/{builder-BqRmnx7p.mjs → builder-CFF0YNTp.mjs} +3 -3
- package/dist/{circuit-breaker-C5NqoLBJ.mjs → circuit-breaker-XJ53md4S.mjs} +16 -11
- package/dist/compat/mod.d.cts +1 -1
- package/dist/compat/mod.d.ts +1 -1
- package/dist/compat/transformers.test.mjs +1 -1
- package/dist/{compound-proof-CxDBdtNz.mjs → compound-proof-F2jjkrt0.mjs} +3 -3
- package/dist/{context-BykNUf04.d.ts → context-9T--NSo4.d.ts} +55 -1
- package/dist/{context-L0Fnib9j.d.cts → context-CDcAgm4l.d.cts} +55 -1
- package/dist/{deno-CizGaYeG.mjs → deno-EopndaNb.mjs} +1 -1
- package/dist/{docloader-BBFAItNB.mjs → docloader-u6zzOExF.mjs} +25 -17
- package/dist/federation/builder.test.mjs +1 -1
- package/dist/federation/circuit-breaker.test.mjs +50 -1
- package/dist/federation/compound-fanout.test.mjs +3 -3
- package/dist/federation/handler-compound-proof.test.mjs +3 -3
- package/dist/federation/handler.test.mjs +5 -5
- package/dist/federation/hashlink-media.test.mjs +2 -2
- package/dist/federation/idempotency.test.mjs +2 -2
- package/dist/federation/keycache.test.mjs +1 -1
- package/dist/federation/metrics.test.mjs +1 -1
- package/dist/federation/middleware.test.mjs +137 -13
- package/dist/federation/mod.cjs +1 -1
- package/dist/federation/mod.d.cts +2 -2
- package/dist/federation/mod.d.ts +2 -2
- package/dist/federation/mod.js +1 -1
- package/dist/federation/outgoing-proof.test.mjs +5 -5
- package/dist/federation/portable-actor.test.mjs +3 -3
- package/dist/federation/portable-chain.test.mjs +3 -3
- package/dist/federation/portable-collection.test.mjs +4 -4
- package/dist/federation/portable-gateway-keys.test.mjs +6 -6
- package/dist/federation/portable-inbox.test.mjs +35 -5
- package/dist/federation/portable-parse-uri.test.mjs +1 -1
- package/dist/federation/portable-route-activity.test.d.mts +2 -0
- package/dist/federation/portable-route-activity.test.mjs +259 -0
- package/dist/federation/portable.test.mjs +3 -3
- package/dist/federation/send.test.mjs +3 -3
- package/dist/federation/tasks/enqueue.test.mjs +1 -1
- package/dist/federation/tasks/tasks.test.mjs +2 -2
- package/dist/federation/temporal.test.mjs +1 -1
- package/dist/federation/webfinger.test.mjs +1 -1
- package/dist/{federation-DFCnp94y.cjs → federation-BSS2dUO7.cjs} +38 -21
- package/dist/{http-Baovr6xq.js → http-BqN5kyUf.js} +426 -141
- package/dist/{http-DC1cqAQ2.d.cts → http-BrwKINQn.d.cts} +28 -0
- package/dist/{http-R4g1McwE.cjs → http-CCr2_ipG.cjs} +443 -140
- package/dist/{http-Cok5-vIC.d.ts → http-DGx0KOws.d.ts} +28 -0
- package/dist/{http-DRH0n8TD.mjs → http-Y1ObT1A8.mjs} +180 -92
- package/dist/{key-D5awqp7u.mjs → key-DyXf88bq.mjs} +4 -4
- package/dist/{key-owner-evidence-Bo3yaeL5.mjs → key-owner-evidence-APB6NpSk.mjs} +1 -1
- package/dist/{keycache-PiOM9pM0.mjs → keycache-Da8A8mZa.mjs} +1 -1
- package/dist/{kv-cache-CZiBDLPi.js → kv-cache-C02XitwK.js} +24 -16
- package/dist/{kv-cache-C7RWL7hz.mjs → kv-cache-C8YjNAi_.mjs} +1 -1
- package/dist/{kv-cache-Ct-KWRta.cjs → kv-cache-DUXluAc0.cjs} +23 -15
- package/dist/{ld-CyliEU48.mjs → ld-oQj6j_hP.mjs} +3 -3
- package/dist/{metrics-CFBEehTc.mjs → metrics-zMe834ID.mjs} +1 -1
- package/dist/{middleware-B99JjvFn.mjs → middleware-Bdu2-OPD.mjs} +37 -25
- package/dist/{middleware-CdpTFsa2.js → middleware-CaR2MRWY.js} +39 -22
- package/dist/{middleware-BmqEWsWP.mjs → middleware-Ck9yghxs.mjs} +1 -1
- package/dist/{mod-DK0y9fd-.d.ts → mod-B3OBLj5g.d.ts} +1 -1
- package/dist/{mod-B5z_q9TH.d.ts → mod-C5Mwx5ty.d.ts} +5 -1
- package/dist/{mod--jv6vyrP.d.ts → mod-Ck9_lSJB.d.ts} +1 -1
- package/dist/{mod-BLOXGs6m.d.cts → mod-D58KCqUg.d.cts} +1 -1
- package/dist/{mod-CPPqkyV_.d.cts → mod-DHTuu3l8.d.cts} +1 -1
- package/dist/{mod-BA_F2x53.d.cts → mod-Dp3Ij7sw.d.cts} +5 -1
- package/dist/mod.cjs +4 -4
- package/dist/mod.d.cts +5 -5
- package/dist/mod.d.ts +5 -5
- package/dist/mod.js +4 -4
- package/dist/nodeinfo/handler.test.mjs +1 -1
- package/dist/{owner-DKdtsrEq.mjs → owner-Dem4fbwc.mjs} +5 -5
- package/dist/{portable-collection-uv4CuQXm.js → portable-collection-BEqLrMW3.js} +1 -1
- package/dist/{portable-collection-Cb5cdPIU.mjs → portable-collection-BL6aZlQ0.mjs} +2 -2
- package/dist/{portable-collection-UajqDrSL.cjs → portable-collection-DJe_bk50.cjs} +1 -1
- package/dist/{portable-key-Bv1RdQ0n.mjs → portable-key-Duue9Y2Y.mjs} +209 -56
- package/dist/{portable-key-id-Bg259c4Z.mjs → portable-key-id-Ca30-hOa.mjs} +45 -1
- package/dist/{proof-CRTPWbVh.mjs → proof-C9115DX_.mjs} +5 -5
- package/dist/{send-2dULlZc8.mjs → send-D0hxFFRB.mjs} +3 -3
- package/dist/sig/compound-proof-discovery.test.mjs +1 -1
- package/dist/sig/compound-proof-producer.test.mjs +1 -1
- package/dist/sig/compound-proof-verification.test.mjs +2 -2
- package/dist/sig/compound-proof.test.mjs +2 -2
- package/dist/sig/http.test.mjs +495 -23
- package/dist/sig/key-owner-evidence.test.mjs +2 -2
- package/dist/sig/key.test.mjs +1 -1
- package/dist/sig/ld.test.mjs +2 -2
- package/dist/sig/mod.cjs +2 -2
- package/dist/sig/mod.d.cts +2 -2
- package/dist/sig/mod.d.ts +2 -2
- package/dist/sig/mod.js +2 -2
- package/dist/sig/owner.test.mjs +1 -1
- package/dist/sig/portable-collection.test.mjs +1 -1
- package/dist/sig/portable-key-id.test.d.mts +2 -0
- package/dist/sig/portable-key-id.test.mjs +67 -0
- package/dist/sig/portable-key.test.mjs +253 -7
- package/dist/sig/proof.test.mjs +3 -3
- package/dist/{temporal-Dhsk6aUT.mjs → temporal-DKchpKyp.mjs} +1 -1
- package/dist/testing/kv.test.mjs +1 -1
- package/dist/testing/mod.d.mts +52 -0
- package/dist/utils/docloader.test.mjs +93 -2
- package/dist/utils/kv-cache.test.mjs +1 -1
- package/dist/utils/mod.cjs +1 -1
- package/dist/utils/mod.d.cts +1 -1
- package/dist/utils/mod.d.ts +1 -1
- package/dist/utils/mod.js +1 -1
- package/package.json +6 -6
|
@@ -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-
|
|
12
|
-
import { l as signRequest } from "../http-
|
|
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-
|
|
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 {
|
|
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-
|
|
16
|
-
import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "../send-
|
|
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-
|
|
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-
|
|
8
|
-
import { r as createFederationBuilder } from "../../builder-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
7
|
-
const require_kv_cache = require("./kv-cache-
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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
|
|
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
|
|
10070
|
-
if (!fetched.actorIds.every((actor) => actor
|
|
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
|
});
|