@fedify/fedify 2.4.0-dev.2157 → 2.4.0-dev.2164
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-CLaS4-yA.mjs → builder-BqRmnx7p.mjs} +2 -2
- 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-D7K94KPz.mjs → compound-proof-CxDBdtNz.mjs} +2 -2
- package/dist/{context-exYu7gDU.d.ts → context-BykNUf04.d.ts} +120 -3
- package/dist/{context-BMggDR-l.d.cts → context-L0Fnib9j.d.cts} +120 -3
- package/dist/{deno-D1gcWWqB.mjs → deno-CizGaYeG.mjs} +1 -1
- package/dist/{docloader-BMqr3hji.mjs → docloader-BBFAItNB.mjs} +2 -2
- package/dist/federation/builder.test.mjs +1 -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 +67 -1
- package/dist/federation/metrics.test.mjs +1 -1
- package/dist/federation/middleware.test.mjs +7 -7
- package/dist/federation/mod.cjs +1 -1
- package/dist/federation/mod.d.cts +3 -3
- package/dist/federation/mod.d.ts +3 -3
- 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 +180 -6
- package/dist/federation/portable-inbox.test.mjs +4 -4
- package/dist/federation/portable-parse-uri.test.d.mts +2 -0
- package/dist/federation/portable-parse-uri.test.mjs +348 -0
- package/dist/federation/portable.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-2e8XrN7q.cjs → federation-DFCnp94y.cjs} +103 -29
- package/dist/{http-Dgtt45-K.js → http-Baovr6xq.js} +161 -7
- package/dist/{http-CjezPjFX.d.cts → http-Cok5-vIC.d.ts} +6 -1
- package/dist/{http-C-x_OLSp.d.ts → http-DC1cqAQ2.d.cts} +6 -1
- package/dist/{http-CGcLv8rh.mjs → http-DRH0n8TD.mjs} +7 -5
- package/dist/{http-BObQoB0j.cjs → http-R4g1McwE.cjs} +185 -11
- package/dist/{key-CPM4r6tP.mjs → key-D5awqp7u.mjs} +58 -7
- package/dist/key-owner-evidence-Bo3yaeL5.mjs +108 -0
- package/dist/{keycache-BInaECGj.mjs → keycache-PiOM9pM0.mjs} +46 -21
- package/dist/{kv-cache-bdPqTR1k.mjs → kv-cache-C7RWL7hz.mjs} +1 -1
- package/dist/{kv-cache-BweyIZTb.js → kv-cache-CZiBDLPi.js} +1 -1
- package/dist/{kv-cache-D2fnB8qu.cjs → kv-cache-Ct-KWRta.cjs} +1 -1
- package/dist/{ld-lU_ZqzsQ.mjs → ld-CyliEU48.mjs} +3 -3
- package/dist/{metrics-B41pRPfd.mjs → metrics-CFBEehTc.mjs} +1 -1
- package/dist/{middleware-xdukzV3x.mjs → middleware-B99JjvFn.mjs} +70 -20
- package/dist/{middleware-TrCVULgs.mjs → middleware-BmqEWsWP.mjs} +1 -1
- package/dist/{middleware-BYWyYjmX.js → middleware-CdpTFsa2.js} +103 -29
- package/dist/{mod-Bz9tFXI2.d.ts → mod--jv6vyrP.d.ts} +1 -1
- package/dist/{mod-DaiBrWDW.d.ts → mod-B5z_q9TH.d.ts} +1 -1
- package/dist/{mod-Mt4aO3DX.d.cts → mod-BA_F2x53.d.cts} +1 -1
- package/dist/{mod-Decmur1U.d.cts → mod-BLOXGs6m.d.cts} +1 -1
- package/dist/{mod-S--JxYjo.d.cts → mod-CPPqkyV_.d.cts} +1 -1
- package/dist/{mod-CnGtADvO.d.ts → mod-DK0y9fd-.d.ts} +1 -1
- package/dist/mod.cjs +4 -4
- package/dist/mod.d.cts +7 -7
- package/dist/mod.d.ts +7 -7
- package/dist/mod.js +4 -4
- package/dist/nodeinfo/handler.test.mjs +1 -1
- package/dist/{owner-D63yPo_i.mjs → owner-DKdtsrEq.mjs} +18 -3
- package/dist/{portable-collection-_lZg-osX.mjs → portable-collection-Cb5cdPIU.mjs} +2 -2
- package/dist/{portable-collection-B8ojoQ50.cjs → portable-collection-UajqDrSL.cjs} +15 -1
- package/dist/{portable-collection-C3xEzYri.js → portable-collection-uv4CuQXm.js} +15 -1
- package/dist/{portable-key-D8uWZF7b.mjs → portable-key-Bv1RdQ0n.mjs} +2 -1
- package/dist/{proof-C2YwD8a_.mjs → proof-CRTPWbVh.mjs} +4 -4
- package/dist/{send-D9SGhxTB.mjs → send-2dULlZc8.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 +2 -2
- package/dist/sig/key-owner-evidence.test.d.mts +2 -0
- package/dist/sig/key-owner-evidence.test.mjs +100 -0
- package/dist/sig/key.test.mjs +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.test.mjs +6 -6
- package/dist/sig/proof.test.mjs +3 -3
- package/dist/{temporal-q6FSekcN.mjs → temporal-Dhsk6aUT.mjs} +1 -1
- package/dist/testing/kv.test.mjs +1 -1
- package/dist/testing/mod.d.mts +118 -1
- package/dist/utils/docloader.test.mjs +2 -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,348 @@
|
|
|
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 { r as ed25519PublicKey } from "../keys-DIJNHW-Y.mjs";
|
|
10
|
+
import { o as createFederation } from "../middleware-B99JjvFn.mjs";
|
|
11
|
+
import { Note, Object as Object$1, Person } from "@fedify/vocab";
|
|
12
|
+
import { test } from "@fedify/fixture";
|
|
13
|
+
import { exportDidKey, parseIri } from "@fedify/vocab-runtime";
|
|
14
|
+
//#region src/federation/portable-parse-uri.test.ts
|
|
15
|
+
const did = await exportDidKey(ed25519PublicKey.publicKey);
|
|
16
|
+
const otherDid = await exportDidKey((await crypto.subtle.generateKey("Ed25519", true, ["sign", "verify"])).publicKey);
|
|
17
|
+
const encodedDid = encodeURIComponent(did);
|
|
18
|
+
const ORIGIN = "https://example.com";
|
|
19
|
+
const NOTE_TYPE_ID = new URL("https://www.w3.org/ns/activitystreams#Note");
|
|
20
|
+
const OBJECT_TYPE_ID = new URL("https://www.w3.org/ns/activitystreams#Object");
|
|
21
|
+
function createTestFederation() {
|
|
22
|
+
const calls = [];
|
|
23
|
+
const federation = createFederation({ kv: new MemoryKvStore() });
|
|
24
|
+
federation.setActorDispatcher("/users/{identifier}", (_ctx, identifier) => {
|
|
25
|
+
calls.push(`actor:${identifier}`);
|
|
26
|
+
return new Person({});
|
|
27
|
+
}).mapActorAlias("/bot", "bot").mapPortableActorId((_ctx, identifier) => {
|
|
28
|
+
calls.push(`portableActorId:${identifier}`);
|
|
29
|
+
return null;
|
|
30
|
+
});
|
|
31
|
+
federation.setObjectDispatcher(Note, "/users/{identifier}/notes/{id}", () => {
|
|
32
|
+
calls.push("object");
|
|
33
|
+
return null;
|
|
34
|
+
});
|
|
35
|
+
federation.setInboxListeners("/users/{identifier}/inbox", "/inbox");
|
|
36
|
+
federation.setOutboxDispatcher("/users/{identifier}/outbox", () => null);
|
|
37
|
+
federation.setFollowingDispatcher("/users/{identifier}/following", () => null);
|
|
38
|
+
federation.setFollowersDispatcher("/users/{identifier}/followers", () => null);
|
|
39
|
+
federation.setLikedDispatcher("/users/{identifier}/liked", () => null);
|
|
40
|
+
federation.setFeaturedDispatcher("/users/{identifier}/featured", () => null);
|
|
41
|
+
federation.setFeaturedTagsDispatcher("/users/{identifier}/tags", () => null);
|
|
42
|
+
federation.setCollectionDispatcher("bookmarks", Object$1, "/users/{identifier}/bookmarks", () => null).mapPortableOwner((_ctx, values) => {
|
|
43
|
+
calls.push(`portableOwner:${values.identifier}`);
|
|
44
|
+
return values.identifier;
|
|
45
|
+
});
|
|
46
|
+
federation.setOrderedCollectionDispatcher("pins", Note, "/users/{identifier}/pins", () => null);
|
|
47
|
+
return {
|
|
48
|
+
federation,
|
|
49
|
+
ctx: federation.createContext(new URL(ORIGIN)),
|
|
50
|
+
calls
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function portableId(path, authority = did) {
|
|
54
|
+
return parseIri(`ap+ef61://${authority}${path}`);
|
|
55
|
+
}
|
|
56
|
+
function compatibleId(path, origin = ORIGIN, authority = did) {
|
|
57
|
+
return new URL(`${origin}/.well-known/apgateway/${authority}${path}`);
|
|
58
|
+
}
|
|
59
|
+
function parsePortable(ctx, uri) {
|
|
60
|
+
return ctx.parseUri(uri, { portable: true });
|
|
61
|
+
}
|
|
62
|
+
test("Context.parseUri() does not recognize portable IDs by default", () => {
|
|
63
|
+
const { ctx } = createTestFederation();
|
|
64
|
+
for (const uri of [
|
|
65
|
+
portableId("/users/alice"),
|
|
66
|
+
parseIri(`ap://${did}/users/alice`),
|
|
67
|
+
compatibleId("/users/alice"),
|
|
68
|
+
compatibleId("/users/alice", "https://gateway.example")
|
|
69
|
+
]) {
|
|
70
|
+
assertEquals(ctx.parseUri(uri), null, uri.href);
|
|
71
|
+
assertEquals(ctx.parseUri(uri, {}), null, uri.href);
|
|
72
|
+
assertEquals(ctx.parseUri(uri, { portable: false }), null, uri.href);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
test("Context.parseUri() keeps the results of ordinary URIs", () => {
|
|
76
|
+
const { ctx } = createTestFederation();
|
|
77
|
+
for (const path of [
|
|
78
|
+
"/users/alice",
|
|
79
|
+
"/bot",
|
|
80
|
+
"/users/alice/notes/123",
|
|
81
|
+
"/inbox",
|
|
82
|
+
"/users/alice/inbox",
|
|
83
|
+
"/users/alice/outbox",
|
|
84
|
+
"/users/alice/following",
|
|
85
|
+
"/users/alice/followers",
|
|
86
|
+
"/users/alice/liked",
|
|
87
|
+
"/users/alice/featured",
|
|
88
|
+
"/users/alice/tags",
|
|
89
|
+
"/users/alice/bookmarks",
|
|
90
|
+
"/users/alice/pins",
|
|
91
|
+
"/nowhere"
|
|
92
|
+
]) {
|
|
93
|
+
const uri = new URL(path, ORIGIN);
|
|
94
|
+
const result = ctx.parseUri(uri);
|
|
95
|
+
assertEquals(parsePortable(ctx, uri), result, path);
|
|
96
|
+
if (result != null) assertFalse("authority" in result, path);
|
|
97
|
+
}
|
|
98
|
+
assertEquals(parsePortable(ctx, new URL("https://other.example/users/alice")), null);
|
|
99
|
+
assertEquals(ctx.parseUri(null, { portable: true }), null);
|
|
100
|
+
});
|
|
101
|
+
test("Context.parseUri() recognizes portable IDs", () => {
|
|
102
|
+
const { ctx, calls } = createTestFederation();
|
|
103
|
+
const identifier = "alice";
|
|
104
|
+
const cases = [
|
|
105
|
+
["/users/alice", {
|
|
106
|
+
type: "actor",
|
|
107
|
+
identifier,
|
|
108
|
+
authority: did
|
|
109
|
+
}],
|
|
110
|
+
["/bot", {
|
|
111
|
+
type: "actor",
|
|
112
|
+
identifier: "bot",
|
|
113
|
+
authority: did
|
|
114
|
+
}],
|
|
115
|
+
["/users/alice/notes/123", {
|
|
116
|
+
type: "object",
|
|
117
|
+
class: Note,
|
|
118
|
+
typeId: NOTE_TYPE_ID,
|
|
119
|
+
values: {
|
|
120
|
+
identifier,
|
|
121
|
+
id: "123"
|
|
122
|
+
},
|
|
123
|
+
authority: did
|
|
124
|
+
}],
|
|
125
|
+
["/users/alice/inbox", {
|
|
126
|
+
type: "inbox",
|
|
127
|
+
identifier,
|
|
128
|
+
authority: did
|
|
129
|
+
}],
|
|
130
|
+
["/users/alice/outbox", {
|
|
131
|
+
type: "outbox",
|
|
132
|
+
identifier,
|
|
133
|
+
authority: did
|
|
134
|
+
}],
|
|
135
|
+
["/users/alice/following", {
|
|
136
|
+
type: "following",
|
|
137
|
+
identifier,
|
|
138
|
+
authority: did
|
|
139
|
+
}],
|
|
140
|
+
["/users/alice/followers", {
|
|
141
|
+
type: "followers",
|
|
142
|
+
identifier,
|
|
143
|
+
authority: did
|
|
144
|
+
}],
|
|
145
|
+
["/users/alice/liked", {
|
|
146
|
+
type: "liked",
|
|
147
|
+
identifier,
|
|
148
|
+
authority: did
|
|
149
|
+
}],
|
|
150
|
+
["/users/alice/featured", {
|
|
151
|
+
type: "featured",
|
|
152
|
+
identifier,
|
|
153
|
+
authority: did
|
|
154
|
+
}],
|
|
155
|
+
["/users/alice/tags", {
|
|
156
|
+
type: "featuredTags",
|
|
157
|
+
identifier,
|
|
158
|
+
authority: did
|
|
159
|
+
}],
|
|
160
|
+
["/users/alice/bookmarks", {
|
|
161
|
+
type: "collection",
|
|
162
|
+
name: "bookmarks",
|
|
163
|
+
class: Object$1,
|
|
164
|
+
typeId: OBJECT_TYPE_ID,
|
|
165
|
+
values: { identifier },
|
|
166
|
+
authority: did
|
|
167
|
+
}],
|
|
168
|
+
["/users/alice/pins", {
|
|
169
|
+
type: "orderedCollection",
|
|
170
|
+
name: "pins",
|
|
171
|
+
class: Note,
|
|
172
|
+
typeId: NOTE_TYPE_ID,
|
|
173
|
+
values: { identifier },
|
|
174
|
+
authority: did
|
|
175
|
+
}]
|
|
176
|
+
];
|
|
177
|
+
for (const [path, expected] of cases) for (const uri of [
|
|
178
|
+
portableId(path),
|
|
179
|
+
parseIri(`ap://${did}${path}`),
|
|
180
|
+
new URL(`ap+ef61://${encodedDid}${path}`),
|
|
181
|
+
new URL(`ap://${encodedDid}${path}`),
|
|
182
|
+
compatibleId(path),
|
|
183
|
+
compatibleId(path, "https://gateway.example"),
|
|
184
|
+
compatibleId(path, "http://localhost:8000")
|
|
185
|
+
]) assertEquals(parsePortable(ctx, uri), expected, uri.href);
|
|
186
|
+
assertEquals(calls, []);
|
|
187
|
+
});
|
|
188
|
+
test("Context.parseUri() round-trips the portable ID helpers", () => {
|
|
189
|
+
const { ctx } = createTestFederation();
|
|
190
|
+
const uris = [
|
|
191
|
+
[ctx.getPortableActorUri("alice", did), "actor"],
|
|
192
|
+
[ctx.getPortableObjectUri(Note, {
|
|
193
|
+
identifier: "alice",
|
|
194
|
+
id: "1"
|
|
195
|
+
}, did), "object"],
|
|
196
|
+
[ctx.getPortableInboxUri("alice", did), "inbox"],
|
|
197
|
+
[ctx.getPortableOutboxUri("alice", did), "outbox"],
|
|
198
|
+
[ctx.getPortableFollowingUri("alice", did), "following"],
|
|
199
|
+
[ctx.getPortableFollowersUri("alice", did), "followers"],
|
|
200
|
+
[ctx.getPortableLikedUri("alice", did), "liked"],
|
|
201
|
+
[ctx.getPortableFeaturedUri("alice", did), "featured"],
|
|
202
|
+
[ctx.getPortableFeaturedTagsUri("alice", did), "featuredTags"],
|
|
203
|
+
[ctx.getPortableCollectionUri("bookmarks", { identifier: "alice" }, did), "collection"],
|
|
204
|
+
[ctx.getPortableCollectionUri("pins", { identifier: "alice" }, did), "orderedCollection"]
|
|
205
|
+
];
|
|
206
|
+
for (const [uri, type] of uris) {
|
|
207
|
+
const result = parsePortable(ctx, uri);
|
|
208
|
+
assertEquals(result?.type, type, uri.href);
|
|
209
|
+
assertEquals(result?.authority, did, uri.href);
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
test("Context.parseUri() returns any DID for the caller to check", () => {
|
|
213
|
+
const { ctx } = createTestFederation();
|
|
214
|
+
assertEquals(parsePortable(ctx, portableId("/users/alice", otherDid)), {
|
|
215
|
+
type: "actor",
|
|
216
|
+
identifier: "alice",
|
|
217
|
+
authority: otherDid
|
|
218
|
+
});
|
|
219
|
+
assertEquals(parsePortable(ctx, compatibleId("/users/alice", "https://gateway.example", otherDid)), {
|
|
220
|
+
type: "actor",
|
|
221
|
+
identifier: "alice",
|
|
222
|
+
authority: otherDid
|
|
223
|
+
});
|
|
224
|
+
assertEquals(parsePortable(ctx, portableId("/users/alice", "did:web:example.com")), {
|
|
225
|
+
type: "actor",
|
|
226
|
+
identifier: "alice",
|
|
227
|
+
authority: "did:web:example.com"
|
|
228
|
+
});
|
|
229
|
+
assertEquals(parsePortable(ctx, parseIri(`ap://DID:KEY:${did.slice(8)}/users/alice`)), {
|
|
230
|
+
type: "actor",
|
|
231
|
+
identifier: "alice",
|
|
232
|
+
authority: did
|
|
233
|
+
});
|
|
234
|
+
assertEquals(parsePortable(ctx, new URL(`ap://DID%3Akey%3A${did.slice(8)}/users/alice`)), {
|
|
235
|
+
type: "actor",
|
|
236
|
+
identifier: "alice",
|
|
237
|
+
authority: did
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
test("Context.parseUri() ignores the query and fragment of portable IDs", () => {
|
|
241
|
+
const { ctx } = createTestFederation();
|
|
242
|
+
const expected = {
|
|
243
|
+
type: "actor",
|
|
244
|
+
identifier: "alice",
|
|
245
|
+
authority: did
|
|
246
|
+
};
|
|
247
|
+
const hint = "@gateway=https%3A%2F%2Fgateway.example";
|
|
248
|
+
for (const uri of [
|
|
249
|
+
parseIri(`ap://${did}/users/alice?${hint}`),
|
|
250
|
+
portableId("/users/alice#main-key"),
|
|
251
|
+
new URL(`${compatibleId("/users/alice").href}?${hint}`),
|
|
252
|
+
new URL(`${compatibleId("/users/alice").href}#main-key`)
|
|
253
|
+
]) assertEquals(parsePortable(ctx, uri), expected, uri.href);
|
|
254
|
+
});
|
|
255
|
+
test("Context.parseUri() routes portable paths like ordinary ones", () => {
|
|
256
|
+
const { ctx } = createTestFederation();
|
|
257
|
+
for (const path of [
|
|
258
|
+
"/users/alice/",
|
|
259
|
+
"/users/alice/notes/",
|
|
260
|
+
"/users"
|
|
261
|
+
]) {
|
|
262
|
+
const ordinary = ctx.parseUri(new URL(path, ORIGIN));
|
|
263
|
+
const expected = ordinary == null ? null : {
|
|
264
|
+
...ordinary,
|
|
265
|
+
authority: did
|
|
266
|
+
};
|
|
267
|
+
assertEquals(parsePortable(ctx, portableId(path)), expected, path);
|
|
268
|
+
assertEquals(parsePortable(ctx, compatibleId(path)), expected, path);
|
|
269
|
+
}
|
|
270
|
+
});
|
|
271
|
+
test("Context.parseUri() does not recognize unroutable portable IDs", () => {
|
|
272
|
+
const { ctx } = createTestFederation();
|
|
273
|
+
for (const uri of [
|
|
274
|
+
portableId("/nowhere"),
|
|
275
|
+
compatibleId("/nowhere"),
|
|
276
|
+
portableId("/inbox"),
|
|
277
|
+
compatibleId("/inbox"),
|
|
278
|
+
compatibleId("/inbox", "https://gateway.example"),
|
|
279
|
+
new URL(`${ORIGIN}/.well-known/apgateway`),
|
|
280
|
+
new URL(`${ORIGIN}/.well-known/apgateway/users/alice`),
|
|
281
|
+
new URL(`${ORIGIN}/.well-known/apgateway/hl:zQmdfTbBqBPQ7VNxZEYEj14VmRuZBkqFbiwReogJgS1zR1n`)
|
|
282
|
+
]) assertEquals(parsePortable(ctx, uri), null, uri.href);
|
|
283
|
+
});
|
|
284
|
+
test("Context.parseUri() does not recognize malformed portable IDs", () => {
|
|
285
|
+
const { ctx } = createTestFederation();
|
|
286
|
+
const base64UrlDid = `did:key:u${did.slice(8).slice(1)}`;
|
|
287
|
+
for (const uri of [
|
|
288
|
+
new URL(`ap+ef61://${encodeURIComponent(base64UrlDid)}/users/alice`),
|
|
289
|
+
compatibleId("/users/alice", ORIGIN, base64UrlDid),
|
|
290
|
+
new URL("ap+ef61://did%3Akey/users/alice"),
|
|
291
|
+
new URL("ap+ef61://example.com/users/alice"),
|
|
292
|
+
compatibleId("/users/alice", ORIGIN, "did:"),
|
|
293
|
+
compatibleId("/users/alice", "https://gateway.example", "did:key"),
|
|
294
|
+
new URL(`ap+ef61://${encodedDid}/users/%ZZ`),
|
|
295
|
+
new URL(`${compatibleId("/users/").href}%ZZ`),
|
|
296
|
+
new URL(`ap+ef61://user:pass@${encodedDid}/users/alice`),
|
|
297
|
+
new URL(`https://user:pass@gateway.example/.well-known/apgateway/${did}/users/alice`)
|
|
298
|
+
]) assertEquals(parsePortable(ctx, uri), null, uri.href);
|
|
299
|
+
});
|
|
300
|
+
test("Context.parseUri() prefers the application's routes", () => {
|
|
301
|
+
const federation = createFederation({ kv: new MemoryKvStore() });
|
|
302
|
+
federation.setActorDispatcher("/users/{identifier}", () => null);
|
|
303
|
+
federation.setObjectDispatcher(Note, "/.well-known/apgateway/{+did}/users/{identifier}/notes/{id}", () => null);
|
|
304
|
+
federation.setNodeInfoDispatcher(`/.well-known/apgateway/${did}/users/alice`, () => ({
|
|
305
|
+
software: {
|
|
306
|
+
name: "test",
|
|
307
|
+
version: "1.0.0"
|
|
308
|
+
},
|
|
309
|
+
protocols: ["activitypub"],
|
|
310
|
+
usage: {
|
|
311
|
+
users: {},
|
|
312
|
+
localPosts: 0,
|
|
313
|
+
localComments: 0
|
|
314
|
+
}
|
|
315
|
+
}));
|
|
316
|
+
const ctx = federation.createContext(new URL(ORIGIN));
|
|
317
|
+
assertEquals(parsePortable(ctx, compatibleId("/users/alice")), null);
|
|
318
|
+
assertEquals(parsePortable(ctx, compatibleId("/users/alice", "https://gateway.example")), {
|
|
319
|
+
type: "actor",
|
|
320
|
+
identifier: "alice",
|
|
321
|
+
authority: did
|
|
322
|
+
});
|
|
323
|
+
const note = parsePortable(ctx, compatibleId("/users/alice/notes/1"));
|
|
324
|
+
assert(note?.type === "object");
|
|
325
|
+
assertEquals(note.values, {
|
|
326
|
+
did,
|
|
327
|
+
identifier: "alice",
|
|
328
|
+
id: "1"
|
|
329
|
+
});
|
|
330
|
+
assertFalse("authority" in note);
|
|
331
|
+
assertEquals(parsePortable(ctx, portableId("/users/alice")), {
|
|
332
|
+
type: "actor",
|
|
333
|
+
identifier: "alice",
|
|
334
|
+
authority: did
|
|
335
|
+
});
|
|
336
|
+
});
|
|
337
|
+
test("RequestContext.parseUri() recognizes portable IDs", () => {
|
|
338
|
+
const { federation } = createTestFederation();
|
|
339
|
+
const ctx = federation.createContext(new Request(compatibleId("/users/alice")), void 0);
|
|
340
|
+
assertEquals(ctx.parseUri(portableId("/users/alice")), null);
|
|
341
|
+
assertEquals(ctx.parseUri(portableId("/users/alice", otherDid), { portable: true }), {
|
|
342
|
+
type: "actor",
|
|
343
|
+
identifier: "alice",
|
|
344
|
+
authority: otherDid
|
|
345
|
+
});
|
|
346
|
+
});
|
|
347
|
+
//#endregion
|
|
348
|
+
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-CRTPWbVh.mjs";
|
|
12
|
+
import { l as signRequest } from "../http-DRH0n8TD.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-B99JjvFn.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-
|
|
13
|
+
import { d as verifyRequest } from "../http-DRH0n8TD.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-DKdtsrEq.mjs";
|
|
16
|
+
import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "../send-2dULlZc8.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-B99JjvFn.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-B99JjvFn.mjs";
|
|
8
|
+
import { r as createFederationBuilder } from "../../builder-BqRmnx7p.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-Dhsk6aUT.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-B99JjvFn.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-R4g1McwE.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-UajqDrSL.cjs");
|
|
7
|
+
const require_kv_cache = require("./kv-cache-Ct-KWRta.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");
|
|
@@ -2497,10 +2497,13 @@ async function routeActivity({ context: ctx, json, originalJson, normalizedActiv
|
|
|
2497
2497
|
const Temporal$9 = require_http.Temporal;
|
|
2498
2498
|
const KEY_CACHE_GENERATION = "2";
|
|
2499
2499
|
const COMPATIBLE_KEY_SEGMENT = "__compatible";
|
|
2500
|
-
const
|
|
2500
|
+
const MAX_OWNER_BYTES = 32 * 1024;
|
|
2501
2501
|
function isCompatibleKeyEntry(value) {
|
|
2502
2502
|
return value != null && typeof value === "object" && "key" in value && "expires" in value && typeof value.expires === "number";
|
|
2503
2503
|
}
|
|
2504
|
+
function isJsonObject(value) {
|
|
2505
|
+
return value != null && typeof value === "object" && !Array.isArray(value);
|
|
2506
|
+
}
|
|
2504
2507
|
var KvKeyCache = class {
|
|
2505
2508
|
kv;
|
|
2506
2509
|
prefix;
|
|
@@ -2582,30 +2585,48 @@ var KvKeyCache = class {
|
|
|
2582
2585
|
keyId.href
|
|
2583
2586
|
];
|
|
2584
2587
|
const isExpired = (entry) => entry.expires <= now().epochMilliseconds;
|
|
2585
|
-
const keyTtl = Math.min(this.keyTtl.total("millisecond"),
|
|
2588
|
+
const keyTtl = Math.min(this.keyTtl.total("millisecond"), require_http.MAX_PORTABLE_KEY_TTL.total("millisecond"));
|
|
2586
2589
|
const unavailableKeyTtl = this.unavailableKeyTtl.total("millisecond");
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2590
|
+
const getEntry = async (keyId) => {
|
|
2591
|
+
const entry = await this.kv.get(entryKey(keyId));
|
|
2592
|
+
if (entry === void 0) return void 0;
|
|
2593
|
+
if (!isCompatibleKeyEntry(entry) || isExpired(entry)) {
|
|
2594
|
+
await this.kv.delete(entryKey(keyId));
|
|
2595
|
+
return;
|
|
2596
|
+
}
|
|
2597
|
+
if (entry.key === null) return null;
|
|
2598
|
+
let expires;
|
|
2599
|
+
try {
|
|
2600
|
+
expires = Temporal$9.Instant.fromEpochMilliseconds(entry.expires);
|
|
2601
|
+
} catch (error) {
|
|
2602
|
+
if (!(error instanceof RangeError)) throw error;
|
|
2603
|
+
await this.kv.delete(entryKey(keyId));
|
|
2604
|
+
return;
|
|
2605
|
+
}
|
|
2606
|
+
let key;
|
|
2607
|
+
try {
|
|
2608
|
+
key = await _fedify_vocab.CryptographicKey.fromJsonLd(entry.key, this.options);
|
|
2609
|
+
} catch {
|
|
2597
2610
|
try {
|
|
2598
|
-
key = await _fedify_vocab.
|
|
2611
|
+
key = await _fedify_vocab.Multikey.fromJsonLd(entry.key, this.options);
|
|
2599
2612
|
} catch {
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
} catch {
|
|
2603
|
-
await this.kv.delete(entryKey(keyId));
|
|
2604
|
-
return;
|
|
2605
|
-
}
|
|
2613
|
+
await this.kv.delete(entryKey(keyId));
|
|
2614
|
+
return;
|
|
2606
2615
|
}
|
|
2607
|
-
|
|
2616
|
+
}
|
|
2617
|
+
if (isExpired(entry)) return void 0;
|
|
2618
|
+
return {
|
|
2619
|
+
key,
|
|
2620
|
+
...isJsonObject(entry.owner) ? { owner: entry.owner } : {},
|
|
2621
|
+
expires
|
|
2622
|
+
};
|
|
2623
|
+
};
|
|
2624
|
+
return {
|
|
2625
|
+
get: async (keyId) => {
|
|
2626
|
+
const entry = await getEntry(keyId);
|
|
2627
|
+
return entry == null ? entry : entry.key;
|
|
2608
2628
|
},
|
|
2629
|
+
getEntry,
|
|
2609
2630
|
set: async (keyId, key, options) => {
|
|
2610
2631
|
const serialized = key == null ? null : await key.toJsonLd(this.options);
|
|
2611
2632
|
const current = now();
|
|
@@ -2616,10 +2637,13 @@ var KvKeyCache = class {
|
|
|
2616
2637
|
await this.kv.delete(entryKey(keyId));
|
|
2617
2638
|
return;
|
|
2618
2639
|
}
|
|
2640
|
+
const owner = key == null || options?.owner == null || new TextEncoder().encode(JSON.stringify(options.owner)).length > MAX_OWNER_BYTES ? void 0 : options.owner;
|
|
2619
2641
|
await this.kv.set(entryKey(keyId), {
|
|
2620
2642
|
key: serialized,
|
|
2621
|
-
expires
|
|
2643
|
+
expires,
|
|
2644
|
+
...owner == null ? {} : { owner }
|
|
2622
2645
|
}, { ttl: Temporal$9.Duration.from({ milliseconds: ttl }) });
|
|
2646
|
+
return key == null ? void 0 : Temporal$9.Instant.fromEpochMilliseconds(expires);
|
|
2623
2647
|
},
|
|
2624
2648
|
delete: async (keyId) => {
|
|
2625
2649
|
await this.kv.delete(entryKey(keyId));
|
|
@@ -2823,6 +2847,43 @@ function parsePortableGatewayRequest(url) {
|
|
|
2823
2847
|
};
|
|
2824
2848
|
}
|
|
2825
2849
|
/**
|
|
2850
|
+
* Recognizes an FEP-ef61 portable ID, i.e., an `ap:` or `ap+ef61:` URI, or
|
|
2851
|
+
* a compatible identifier on any gateway, and splits it into its DID and
|
|
2852
|
+
* the object path to route.
|
|
2853
|
+
*
|
|
2854
|
+
* It accepts the same IDs as the gateway endpoint does: the query, e.g.,
|
|
2855
|
+
* location hints, and the fragment are ignored, and a malformed DID, or
|
|
2856
|
+
* a `did:key` DID that is not encoded in base58-btc, is refused.
|
|
2857
|
+
*
|
|
2858
|
+
* @param uri The ID to recognize.
|
|
2859
|
+
* @returns The DID normalized as an FEP-fe34 origin, and the object path,
|
|
2860
|
+
* or `null` if the ID is not a portable ID or is malformed.
|
|
2861
|
+
*/
|
|
2862
|
+
function parsePortableId(uri) {
|
|
2863
|
+
if (uri.username !== "" || uri.password !== "") return null;
|
|
2864
|
+
if (uri.protocol !== "ap:" && uri.protocol !== "ap+ef61:") {
|
|
2865
|
+
const request = parsePortableGatewayRequest(uri);
|
|
2866
|
+
if (request?.type !== "object") return null;
|
|
2867
|
+
return {
|
|
2868
|
+
authority: request.portableRequest.authority,
|
|
2869
|
+
path: request.path
|
|
2870
|
+
};
|
|
2871
|
+
}
|
|
2872
|
+
try {
|
|
2873
|
+
const id = (0, _fedify_vocab_runtime.parseIri)(uri);
|
|
2874
|
+
(0, _fedify_vocab_runtime.canonicalizePortableUri)((0, _fedify_vocab_runtime.formatIri)(id));
|
|
2875
|
+
const authority = (0, _fedify_vocab_runtime.getFe34Origin)(id);
|
|
2876
|
+
assertBase58BtcDidKey(authority);
|
|
2877
|
+
return {
|
|
2878
|
+
authority,
|
|
2879
|
+
path: id.pathname
|
|
2880
|
+
};
|
|
2881
|
+
} catch (error) {
|
|
2882
|
+
if (error instanceof TypeError) return null;
|
|
2883
|
+
throw error;
|
|
2884
|
+
}
|
|
2885
|
+
}
|
|
2886
|
+
/**
|
|
2826
2887
|
* Builds a portable ID from a DID authority and an object path.
|
|
2827
2888
|
* @param authority The bare DID, e.g., `did:key:z6Mk...`.
|
|
2828
2889
|
* @param path The object path, e.g., `/notes/123`.
|
|
@@ -9484,12 +9545,25 @@ var ContextImpl = class ContextImpl {
|
|
|
9484
9545
|
if (path === null) throw new _fedify_uri_template.RouterError(`No collection dispatcher registered for "${String(name)}".`);
|
|
9485
9546
|
return buildPortableUri(requirePortableAuthority(authority), path);
|
|
9486
9547
|
}
|
|
9487
|
-
parseUri(uri) {
|
|
9548
|
+
parseUri(uri, options = {}) {
|
|
9488
9549
|
if (uri == null) return null;
|
|
9489
|
-
if (uri.origin
|
|
9490
|
-
|
|
9491
|
-
|
|
9492
|
-
|
|
9550
|
+
if (uri.origin === this.origin || uri.origin === this.canonicalOrigin) {
|
|
9551
|
+
const route = this.federation.router.route(uri.pathname);
|
|
9552
|
+
if (route != null) return this.#parseRoute(route);
|
|
9553
|
+
}
|
|
9554
|
+
if (!options.portable) return null;
|
|
9555
|
+
const portable = parsePortableId(uri);
|
|
9556
|
+
if (portable == null) return null;
|
|
9557
|
+
const route = this.federation.router.route(portable.path);
|
|
9558
|
+
if (route == null || route.name === "sharedInbox") return null;
|
|
9559
|
+
const result = this.#parseRoute(route);
|
|
9560
|
+
return result == null ? null : {
|
|
9561
|
+
...result,
|
|
9562
|
+
authority: portable.authority
|
|
9563
|
+
};
|
|
9564
|
+
}
|
|
9565
|
+
#parseRoute(route) {
|
|
9566
|
+
if (route.name === "sharedInbox") return {
|
|
9493
9567
|
type: "inbox",
|
|
9494
9568
|
identifier: void 0
|
|
9495
9569
|
};
|