@fedify/fedify 2.4.0-dev.2065 → 2.4.0-dev.2073

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 (98) hide show
  1. package/dist/{builder-CMkrnYbY.mjs → builder-CVHpPAjK.mjs} +6 -2
  2. package/dist/compat/mod.d.cts +1 -1
  3. package/dist/compat/mod.d.ts +1 -1
  4. package/dist/compat/transformers.test.mjs +1 -1
  5. package/dist/{compound-proof-Dj3DCsoj.mjs → compound-proof-B5Skp-vE.mjs} +1 -1
  6. package/dist/{context-D_gg4M0n.d.ts → context-BSywYspi.d.ts} +153 -2
  7. package/dist/{context-b36TpPoG.d.cts → context-CZjZYXFC.d.cts} +153 -2
  8. package/dist/{context-DG5QeQ8W.mjs → context-DlZhxdFT.mjs} +2 -1
  9. package/dist/{deno-BuRFI7wk.mjs → deno-LYTbBhYV.mjs} +1 -1
  10. package/dist/{docloader-_aSNLA90.mjs → docloader-C0SWQylk.mjs} +2 -2
  11. package/dist/federation/builder.test.mjs +1 -1
  12. package/dist/federation/compound-fanout.test.mjs +4 -4
  13. package/dist/federation/handler-compound-proof.test.mjs +4 -4
  14. package/dist/federation/handler.test.mjs +6 -6
  15. package/dist/federation/hashlink-media.test.mjs +2 -2
  16. package/dist/federation/idempotency.test.mjs +2 -2
  17. package/dist/federation/metrics.test.mjs +1 -1
  18. package/dist/federation/middleware.test.mjs +9 -9
  19. package/dist/federation/mod.cjs +1 -1
  20. package/dist/federation/mod.d.cts +3 -3
  21. package/dist/federation/mod.d.ts +3 -3
  22. package/dist/federation/mod.js +1 -1
  23. package/dist/federation/outgoing-proof.test.mjs +6 -6
  24. package/dist/federation/portable-gateway-keys.test.d.mts +2 -0
  25. package/dist/federation/portable-gateway-keys.test.mjs +363 -0
  26. package/dist/federation/portable-inbox.test.d.mts +2 -0
  27. package/dist/federation/portable-inbox.test.mjs +546 -0
  28. package/dist/federation/portable.test.mjs +3 -3
  29. package/dist/federation/send.test.mjs +3 -3
  30. package/dist/federation/tasks/enqueue.test.mjs +2 -2
  31. package/dist/federation/tasks/tasks.test.mjs +3 -3
  32. package/dist/federation/temporal.test.mjs +1 -1
  33. package/dist/federation/webfinger.test.mjs +2 -2
  34. package/dist/{federation-Coi_bg12.cjs → federation-B1aeTKKN.cjs} +631 -145
  35. package/dist/{http-f1qaBtOh.mjs → http-C7qHlv-b.mjs} +20 -5
  36. package/dist/{http-BGShIsd6.cjs → http-DGk8XI38.cjs} +2684 -48
  37. package/dist/{http-65PYi9jY.d.ts → http-n2v6DPxu.d.ts} +52 -2
  38. package/dist/http-vfTpzGEq.js +5087 -0
  39. package/dist/{http-CP-7OdSq.d.cts → http-xlG-jRdB.d.cts} +52 -2
  40. package/dist/{key-BxHsA1s9.mjs → key-BYXnTGro.mjs} +147 -7
  41. package/dist/{kv-cache-Cgj-kpQS.js → kv-cache-DCJuwdnB.js} +1 -1
  42. package/dist/{kv-cache-C9IY-FMN.mjs → kv-cache-DEH5jyND.mjs} +1 -1
  43. package/dist/{kv-cache-CY6pDNoS.cjs → kv-cache-DvqkKNIj.cjs} +1 -1
  44. package/dist/{ld-CqJq5erX.mjs → ld-DT3e2hOv.mjs} +3 -3
  45. package/dist/{metrics-DOqknlZd.mjs → metrics-DvlvYpq_.mjs} +1 -1
  46. package/dist/{middleware-VOCZN3SO.mjs → middleware-BJcez_E3.mjs} +1 -1
  47. package/dist/{middleware-1h_LgwXC.js → middleware-CCbyUF4c.js} +547 -62
  48. package/dist/{middleware-s-AZzx4r.mjs → middleware-CpyCNOW_.mjs} +549 -72
  49. package/dist/{mod-BhMvsgdF.d.ts → mod-B2h2KY0q.d.ts} +1 -1
  50. package/dist/{mod-Dhm7SqyE.d.ts → mod-C9WSpZWm.d.ts} +1 -1
  51. package/dist/{mod-C0BYc0OU.d.ts → mod-CVZrXSdm.d.ts} +1 -1
  52. package/dist/{mod-pLbeNwcC.d.cts → mod-HDSvS-ww.d.cts} +1 -1
  53. package/dist/{mod-Bfenkg_c.d.cts → mod-JzkaEXsi.d.cts} +1 -1
  54. package/dist/{mod-CyiSjRkk.d.cts → mod-xGFCHqq_.d.cts} +1 -1
  55. package/dist/mod.cjs +18 -18
  56. package/dist/mod.d.cts +7 -7
  57. package/dist/mod.d.ts +7 -7
  58. package/dist/mod.js +5 -5
  59. package/dist/nodeinfo/handler.test.mjs +2 -2
  60. package/dist/{owner-D_F22lfM.mjs → owner-CDsVeZI7.mjs} +45 -2
  61. package/dist/owner-DCIH7Iq0.cjs +225 -0
  62. package/dist/owner-DfGz0cXm.js +215 -0
  63. package/dist/portable-key-DCyKGCQd.mjs +194 -0
  64. package/dist/{proof-B03JXhrk.mjs → proof-CxsVG6vM.mjs} +5 -5
  65. package/dist/{send-NNkfKtVN.mjs → send-BZJ7NsVV.mjs} +8 -4
  66. package/dist/sig/compound-proof-discovery.test.mjs +1 -1
  67. package/dist/sig/compound-proof-producer.test.mjs +1 -1
  68. package/dist/sig/compound-proof-verification.test.mjs +2 -2
  69. package/dist/sig/compound-proof.test.mjs +3 -3
  70. package/dist/sig/http.test.mjs +2 -2
  71. package/dist/sig/key.test.mjs +1 -1
  72. package/dist/sig/ld.test.mjs +2 -2
  73. package/dist/sig/mod.cjs +16 -16
  74. package/dist/sig/mod.d.cts +2 -2
  75. package/dist/sig/mod.d.ts +2 -2
  76. package/dist/sig/mod.js +3 -3
  77. package/dist/sig/owner.test.mjs +1 -1
  78. package/dist/sig/portable-collection.test.mjs +3 -3
  79. package/dist/sig/portable-key.test.d.mts +2 -0
  80. package/dist/sig/portable-key.test.mjs +275 -0
  81. package/dist/sig/proof.test.mjs +4 -4
  82. package/dist/{sig-B1E2uyhQ.cjs → sig-C6TnADze.cjs} +4 -4
  83. package/dist/{sig-Bb20Pyrd.js → sig-Cs9_ecdh.js} +2 -2
  84. package/dist/{temporal-VVVSuruq.mjs → temporal-CLzSfIOV.mjs} +1 -1
  85. package/dist/testing/kv.test.mjs +1 -1
  86. package/dist/testing/mod.d.mts +151 -0
  87. package/dist/testing/mod.mjs +2 -2
  88. package/dist/{testing-By1YYsIK.mjs → testing-CO_cEvQc.mjs} +1 -1
  89. package/dist/utils/docloader.test.mjs +2 -2
  90. package/dist/utils/kv-cache.test.mjs +1 -1
  91. package/dist/utils/mod.cjs +1 -1
  92. package/dist/utils/mod.d.cts +1 -1
  93. package/dist/utils/mod.d.ts +1 -1
  94. package/dist/utils/mod.js +1 -1
  95. package/package.json +6 -6
  96. package/dist/http-z5n7chWw.js +0 -2647
  97. package/dist/owner-BLBWXCNt.js +0 -2276
  98. package/dist/owner-D8ZpRusc.cjs +0 -2446
@@ -6,7 +6,7 @@ import { t as assertFalse } from "../assert_false-CPdRnc3E.mjs";
6
6
  import "../std__assert-DvMDRPes.mjs";
7
7
  import { t as assert } from "../assert-D1Eafmd_.mjs";
8
8
  import { o as rsaPublicKey1, s as rsaPublicKey2 } from "../keys-DIJNHW-Y.mjs";
9
- import { n as getKeyOwner, t as doesActorOwnKey } from "../owner-D_F22lfM.mjs";
9
+ import { n as getKeyOwner, t as doesActorOwnKey } from "../owner-CDsVeZI7.mjs";
10
10
  import { Create, CryptographicKey, lookupObject } from "@fedify/vocab";
11
11
  import { createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
12
12
  //#region src/sig/owner.test.ts
@@ -1,16 +1,16 @@
1
1
  import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "../deno-BuRFI7wk.mjs";
4
+ import { n as version, t as name } from "../deno-LYTbBhYV.mjs";
5
+ import { c as verifyPortableObjectProofWithRoot, o as verifyPortableObjectProof } from "../proof-CxsVG6vM.mjs";
5
6
  import { n as ed25519PrivateKey, r as ed25519PublicKey } from "../keys-DIJNHW-Y.mjs";
6
- import { c as verifyPortableObjectProofWithRoot, o as verifyPortableObjectProof } from "../proof-B03JXhrk.mjs";
7
7
  import { getLogger } from "@logtape/logtape";
8
8
  import { Collection, CollectionPage, Note, Object as Object$1, OrderedCollection, Person, isActor, traverseCollection } from "@fedify/vocab";
9
9
  import { trace } from "@opentelemetry/api";
10
10
  import { test } from "@fedify/fixture";
11
11
  import { FetchError, canonicalizePortableUri, encodeMultibase, exportDidKey, formatIri, fromCompatibleEf61Id, getDocumentLoader, getFe34Origin, parseIri, preloadedContexts } from "@fedify/vocab-runtime";
12
- import { deepStrictEqual, ok, strictEqual } from "node:assert/strict";
13
12
  import serialize from "json-canon";
13
+ import { deepStrictEqual, ok, strictEqual } from "node:assert/strict";
14
14
  import { dereferencePortableIri, getPortableGatewayCandidates } from "@fedify/vocab-runtime/internal/portable-dereference";
15
15
  //#region src/sig/portable-collection.ts
16
16
  const logger = getLogger([
@@ -0,0 +1,2 @@
1
+ import { Temporal } from "temporal-polyfill";
2
+ import { URLPattern } from "urlpattern-polyfill";
@@ -0,0 +1,275 @@
1
+ import "temporal-polyfill";
2
+ import "urlpattern-polyfill";
3
+ globalThis.addEventListener = () => {};
4
+ import { r as fetchKey } from "../key-BYXnTGro.mjs";
5
+ import { l as verifyRequest, s as signRequest } from "../http-C7qHlv-b.mjs";
6
+ import { c as rsaPublicKey3, i as rsaPrivateKey2, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2 } from "../keys-DIJNHW-Y.mjs";
7
+ import { n as getKeyOwner, t as doesActorOwnKey } from "../owner-CDsVeZI7.mjs";
8
+ import { Create, CryptographicKey, Multikey, Person } from "@fedify/vocab";
9
+ import { test } from "@fedify/fixture";
10
+ import { FetchError, encodeMultibase, exportDidKey, parseIri, preloadedContexts } from "@fedify/vocab-runtime";
11
+ import serialize from "json-canon";
12
+ import { deepStrictEqual, ok, strictEqual } from "node:assert/strict";
13
+ //#region src/sig/portable-key.test.ts
14
+ const did = await exportDidKey(ed25519PublicKey.publicKey);
15
+ const didKeyId = `${did}#${did.slice(8)}`;
16
+ const otherKeyPair = await crypto.subtle.generateKey("Ed25519", true, ["sign", "verify"]);
17
+ const otherDid = await exportDidKey(otherKeyPair.publicKey);
18
+ const actorId = `ap+ef61://${did}/actor`;
19
+ const gw1 = "https://gw1.example";
20
+ const gw2 = "https://gw2.example";
21
+ function compatibleId(gateway, id = actorId) {
22
+ return `${gateway}/.well-known/apgateway/${id.replace(/^ap\+ef61:\/\//, "")}`;
23
+ }
24
+ const keyId = new URL(`${compatibleId(gw1)}#main-key`);
25
+ const gatewayPublicKey = rsaPublicKey2.publicKey;
26
+ const contextLoader = (url) => {
27
+ const document = preloadedContexts[url];
28
+ if (document == null) return Promise.reject(/* @__PURE__ */ new Error(`No context: ${url}`));
29
+ return Promise.resolve({
30
+ contextUrl: null,
31
+ documentUrl: url,
32
+ document
33
+ });
34
+ };
35
+ function createLoader(responses) {
36
+ const fetched = [];
37
+ const loader = (url) => {
38
+ fetched.push(url);
39
+ const document = responses[url.replace(/#.*$/, "")];
40
+ if (document == null) return Promise.reject(new FetchError(url, "HTTP 404", new Response(null, { status: 404 })));
41
+ return Promise.resolve({
42
+ contextUrl: null,
43
+ documentUrl: url,
44
+ document: structuredClone(document)
45
+ });
46
+ };
47
+ return Object.assign(loader, { fetched });
48
+ }
49
+ async function sign(document, privateKey = ed25519PrivateKey, verificationMethod = didKeyId) {
50
+ const proofConfig = {
51
+ "@context": document["@context"],
52
+ type: "DataIntegrityProof",
53
+ cryptosuite: "eddsa-jcs-2022",
54
+ verificationMethod,
55
+ proofPurpose: "assertionMethod",
56
+ created: "2023-02-24T23:36:38Z"
57
+ };
58
+ const encoder = new TextEncoder();
59
+ const proofDigest = await crypto.subtle.digest("SHA-256", encoder.encode(serialize(proofConfig)));
60
+ const messageDigest = await crypto.subtle.digest("SHA-256", encoder.encode(serialize(document)));
61
+ const digest = /* @__PURE__ */ new Uint8Array(64);
62
+ digest.set(new Uint8Array(proofDigest), 0);
63
+ digest.set(new Uint8Array(messageDigest), 32);
64
+ const signature = await crypto.subtle.sign("Ed25519", privateKey, digest);
65
+ return {
66
+ ...document,
67
+ proof: {
68
+ ...proofConfig,
69
+ proofValue: new TextDecoder().decode(encodeMultibase("base58btc", new Uint8Array(signature)))
70
+ }
71
+ };
72
+ }
73
+ async function actorJson(options = {}) {
74
+ const id = parseIri(options.id ?? actorId);
75
+ const key = options.key ?? keyId;
76
+ const json = await new Person({
77
+ id,
78
+ preferredUsername: "alice",
79
+ inbox: parseIri(`${options.id ?? actorId}/inbox`),
80
+ gateways: (options.gateways ?? [gw1, gw2]).map((g) => new URL(g)),
81
+ assertionMethods: options.assertionMethod === false ? [] : [new Multikey({
82
+ id: key,
83
+ controller: id,
84
+ publicKey: gatewayPublicKey
85
+ })],
86
+ publicKeys: options.publicKey === null ? [] : [new CryptographicKey({
87
+ id: key,
88
+ owner: id,
89
+ publicKey: options.publicKey ?? gatewayPublicKey
90
+ })]
91
+ }).toJsonLd({
92
+ format: "compact",
93
+ contextLoader
94
+ });
95
+ const context = json["@context"];
96
+ if (!context.includes("https://w3id.org/security/data-integrity/v1")) json["@context"] = [...context, "https://w3id.org/security/data-integrity/v1"];
97
+ return json;
98
+ }
99
+ async function signedRequest(spec, key = keyId) {
100
+ return await signRequest(new Request("https://recipient.example/inbox", {
101
+ method: "POST",
102
+ body: "{}",
103
+ headers: { "Content-Type": "application/activity+json" }
104
+ }), rsaPrivateKey2, key, { spec });
105
+ }
106
+ test("verifyRequest() accepts gateway keys vouched for by portable actors", async () => {
107
+ const documentLoader = createLoader({ [compatibleId(gw1)]: await sign(await actorJson()) });
108
+ for (const spec of ["draft-cavage-http-signatures-12", "rfc9421"]) {
109
+ const key = await verifyRequest(await signedRequest(spec), {
110
+ documentLoader,
111
+ contextLoader,
112
+ spec
113
+ });
114
+ ok(key != null, spec);
115
+ strictEqual(key.id?.href, keyId.href);
116
+ strictEqual(key.ownerId?.href, parseIri(actorId).href);
117
+ }
118
+ });
119
+ test("verifyRequest() accepts gateway keys only in assertionMethod", async () => {
120
+ const documentLoader = createLoader({ [compatibleId(gw1)]: await sign(await actorJson({ publicKey: null })) });
121
+ strictEqual((await verifyRequest(await signedRequest(), {
122
+ documentLoader,
123
+ contextLoader
124
+ }))?.id?.href, keyId.href);
125
+ });
126
+ test("verifyRequest() rejects gateway keys that portable actors do not vouch for", async () => {
127
+ const unsigned = await actorJson();
128
+ const cases = {
129
+ "an unsigned actor document": unsigned,
130
+ "an actor document signed by another DID": await sign(unsigned, otherKeyPair.privateKey, `${otherDid}#${otherDid.slice(8)}`),
131
+ "an unlisted gateway": await sign(await actorJson({ gateways: [gw2] })),
132
+ "a key missing from assertionMethod": await sign(await actorJson({ assertionMethod: false })),
133
+ "a key referred to by URL": await sign({
134
+ ...await actorJson({
135
+ publicKey: null,
136
+ assertionMethod: false
137
+ }),
138
+ assertionMethod: [keyId.href]
139
+ }),
140
+ "a publicKey entry with other key material": await sign(await actorJson({ publicKey: rsaPublicKey3.publicKey })),
141
+ "a key ID for another actor": await sign(await actorJson({ key: new URL(`${compatibleId(gw1, `${actorId}/other`)}#main-key`) }))
142
+ };
143
+ for (const [name, document] of Object.entries(cases)) {
144
+ const documentLoader = createLoader({ [compatibleId(gw1)]: document });
145
+ strictEqual(await verifyRequest(await signedRequest(), {
146
+ documentLoader,
147
+ contextLoader
148
+ }), null, name);
149
+ }
150
+ const otherActorKey = new URL(`${compatibleId(gw1, `${actorId}/other`)}#main-key`);
151
+ const documentLoader = createLoader({ [compatibleId(gw1, `${actorId}/other`)]: await sign(await actorJson()) });
152
+ strictEqual(await verifyRequest(await signedRequest(void 0, otherActorKey), {
153
+ documentLoader,
154
+ contextLoader
155
+ }), null);
156
+ });
157
+ test("verifyRequest() keeps web-origin trust for compatible-ID actors", async () => {
158
+ const id = compatibleId(gw1);
159
+ const documentLoader = createLoader({ [id]: await actorJson({
160
+ id,
161
+ gateways: [gw1]
162
+ }) });
163
+ const key = await verifyRequest(await signedRequest(), {
164
+ documentLoader,
165
+ contextLoader
166
+ });
167
+ strictEqual(key?.id?.href, keyId.href);
168
+ strictEqual(key?.ownerId?.href, id);
169
+ });
170
+ test("gateway keys are neither cached nor accepted outside HTTP Signatures", async () => {
171
+ const calls = [];
172
+ const keyCache = {
173
+ get(id) {
174
+ calls.push(`get ${id.href}`);
175
+ return Promise.resolve(void 0);
176
+ },
177
+ set(id) {
178
+ calls.push(`set ${id.href}`);
179
+ return Promise.resolve();
180
+ }
181
+ };
182
+ const documentLoader = createLoader({ [compatibleId(gw1)]: await sign(await actorJson()) });
183
+ ok(await verifyRequest(await signedRequest(), {
184
+ documentLoader,
185
+ contextLoader,
186
+ keyCache
187
+ }) != null);
188
+ const fetchOptions = {
189
+ documentLoader,
190
+ contextLoader,
191
+ keyCache
192
+ };
193
+ strictEqual((await fetchKey(keyId, CryptographicKey, fetchOptions)).key, null);
194
+ strictEqual((await fetchKey(keyId, Multikey, fetchOptions)).key, null);
195
+ deepStrictEqual(calls.filter((c) => c.includes(keyId.origin)), []);
196
+ });
197
+ test("doesActorOwnKey() and getKeyOwner() resolve gateway keys to portable actors", async () => {
198
+ const options = {
199
+ documentLoader: createLoader({ [compatibleId(gw1)]: await sign(await actorJson()) }),
200
+ contextLoader
201
+ };
202
+ const key = await verifyRequest(await signedRequest(), options);
203
+ ok(key != null);
204
+ const activity = (actor) => new Create({
205
+ id: parseIri(`ap+ef61://${did}/activities/1`),
206
+ actor: parseIri(actor)
207
+ });
208
+ ok(await doesActorOwnKey(activity(actorId), key, options));
209
+ ok(await doesActorOwnKey(activity(compatibleId(gw2)), key, options));
210
+ ok(!await doesActorOwnKey(activity(`${actorId}/other`), key, options));
211
+ strictEqual((await getKeyOwner(keyId, options))?.id?.href, parseIri(actorId).href);
212
+ strictEqual((await getKeyOwner(key, options))?.id?.href, key.ownerId?.href);
213
+ const forged = key.clone({ publicKey: rsaPublicKey3.publicKey });
214
+ strictEqual(await getKeyOwner(forged, options), null);
215
+ ok(!await doesActorOwnKey(activity(actorId), forged, options));
216
+ });
217
+ test("doesActorOwnKey() and getKeyOwner() do not fall back for rejected gateway keys", async () => {
218
+ const options = {
219
+ documentLoader: createLoader({ [compatibleId(gw1)]: await actorJson() }),
220
+ contextLoader
221
+ };
222
+ const key = new CryptographicKey({
223
+ id: keyId,
224
+ owner: parseIri(actorId),
225
+ publicKey: gatewayPublicKey
226
+ });
227
+ ok(!await doesActorOwnKey(new Create({
228
+ id: parseIri(`ap+ef61://${did}/activities/1`),
229
+ actor: parseIri(actorId)
230
+ }), key, options));
231
+ strictEqual(await getKeyOwner(keyId, options), null);
232
+ strictEqual(await getKeyOwner(key, options), null);
233
+ });
234
+ test("verifyRequest() binds gateway keys to the actor whose document is signed", async () => {
235
+ const attackerDid = otherDid;
236
+ const attackerActorId = `ap+ef61://${attackerDid}/actor`;
237
+ const shiftingContext = "https://attacker.example/context";
238
+ let loads = 0;
239
+ const shiftingContextLoader = (url) => {
240
+ if (url !== shiftingContext) return contextLoader(url);
241
+ loads++;
242
+ const document = { "@context": loads <= 2 ? {
243
+ victimId: "@id",
244
+ attackerId: "https://attacker.example/ns#a"
245
+ } : {
246
+ victimId: "https://attacker.example/ns#v",
247
+ attackerId: "@id"
248
+ } };
249
+ return Promise.resolve({
250
+ contextUrl: null,
251
+ documentUrl: url,
252
+ document
253
+ });
254
+ };
255
+ const { id: _, ...victim } = await actorJson({ gateways: [gw1] });
256
+ const document = await sign({
257
+ ...victim,
258
+ "@context": [...victim["@context"], shiftingContext],
259
+ victimId: actorId,
260
+ attackerId: attackerActorId
261
+ }, otherKeyPair.privateKey, `${attackerDid}#${attackerDid.slice(8)}`);
262
+ const documentLoader = createLoader({ [compatibleId(gw1)]: document });
263
+ strictEqual(await verifyRequest(await signedRequest(), {
264
+ documentLoader,
265
+ contextLoader: shiftingContextLoader
266
+ }), null);
267
+ ok(loads > 2, "the shifting context was loaded for the proof");
268
+ loads = 0;
269
+ strictEqual(await getKeyOwner(keyId, {
270
+ documentLoader,
271
+ contextLoader: shiftingContextLoader
272
+ }), null);
273
+ });
274
+ //#endregion
275
+ export {};
@@ -9,11 +9,11 @@ import { t as assertInstanceOf } from "../assert_instance_of-mrXWWNY-.mjs";
9
9
  import "../std__assert-DvMDRPes.mjs";
10
10
  import { t as assertRejects } from "../assert_rejects-CTptdYck.mjs";
11
11
  import { t as assert } from "../assert-D1Eafmd_.mjs";
12
- import { l as verifyRequest } from "../http-f1qaBtOh.mjs";
13
- import { i as rsaPrivateKey2, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2, t as ed25519Multikey } from "../keys-DIJNHW-Y.mjs";
14
- import { t as getAuthenticatedDocumentLoader } from "../docloader-_aSNLA90.mjs";
15
12
  import { r as normalizeOutgoingActivityJsonLd } from "../outgoing-jsonld-4IJqPYos.mjs";
16
- import { a as verifyObject, l as verifyProof, n as hasProofLike, o as verifyPortableObjectProof, r as signObject, t as createProof } from "../proof-B03JXhrk.mjs";
13
+ import { a as verifyObject, l as verifyProof, n as hasProofLike, o as verifyPortableObjectProof, r as signObject, t as createProof } from "../proof-CxsVG6vM.mjs";
14
+ import { l as verifyRequest } from "../http-C7qHlv-b.mjs";
15
+ import { i as rsaPrivateKey2, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2, t as ed25519Multikey } from "../keys-DIJNHW-Y.mjs";
16
+ import { t as getAuthenticatedDocumentLoader } from "../docloader-C0SWQylk.mjs";
17
17
  import { Create, DataIntegrityProof, Document, Multikey, Note, PUBLIC_COLLECTION, Place, Translation } from "@fedify/vocab";
18
18
  import { createTestMeterProvider, mockDocumentLoader, test } from "@fedify/fixture";
19
19
  import { decodeMultibase, encodeMultibase, exportDidKey, exportMultibaseKey, formatIri, importMultibaseKey, parseIri } from "@fedify/vocab-runtime";
@@ -1,6 +1,6 @@
1
1
  const { URLPattern } = require("urlpattern-polyfill");
2
- const require_http = require("./http-BGShIsd6.cjs");
3
- const require_owner = require("./owner-D8ZpRusc.cjs");
2
+ const require_http = require("./http-DGk8XI38.cjs");
3
+ require("./owner-DCIH7Iq0.cjs");
4
4
  let _logtape_logtape = require("@logtape/logtape");
5
5
  let _fedify_vocab = require("@fedify/vocab");
6
6
  let _opentelemetry_api = require("@opentelemetry/api");
@@ -236,7 +236,7 @@ async function fetchOwner(candidate, options) {
236
236
  contextLoader,
237
237
  tracerProvider,
238
238
  gateways,
239
- verifyPortableObject: (document, verifierOptions) => require_owner.verifyPortableObjectProof(document, {
239
+ verifyPortableObject: (document, verifierOptions) => require_http.verifyPortableObjectProof(document, {
240
240
  ...options,
241
241
  ...verifierOptions
242
242
  }),
@@ -340,7 +340,7 @@ function getSourceGateway(node, documentUrl) {
340
340
  * @since 2.4.0
341
341
  */
342
342
  async function verifyPortableObject(jsonLd, options = {}) {
343
- const { result, root, objectType } = await require_owner.verifyPortableObjectProofWithRoot(jsonLd, options);
343
+ const { result, root, objectType } = await require_http.verifyPortableObjectProofWithRoot(jsonLd, options);
344
344
  if (result.verified) {
345
345
  const node = objectType === "collection" && root != null ? describeRoot(root) : null;
346
346
  const chain = node == null ? null : resolveChain(node, options.referrer);
@@ -1,7 +1,7 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
- import { I as name, L as version } from "./http-z5n7chWw.js";
4
- import { c as verifyPortableObjectProof, u as verifyPortableObjectProofWithRoot } from "./owner-BLBWXCNt.js";
3
+ import { f as verifyPortableObjectProof, ht as version, m as verifyPortableObjectProofWithRoot, mt as name } from "./http-vfTpzGEq.js";
4
+ import "./owner-DfGz0cXm.js";
5
5
  import { getLogger } from "@logtape/logtape";
6
6
  import { CollectionPage, Object as Object$1, isActor } from "@fedify/vocab";
7
7
  import { trace } from "@opentelemetry/api";
@@ -1,7 +1,7 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { c as getNormalizationContextLoader } from "./ld-CqJq5erX.mjs";
4
+ import { c as getNormalizationContextLoader } from "./ld-DT3e2hOv.mjs";
5
5
  import jsonld from "@fedify/vocab-runtime/jsonld";
6
6
  //#region src/federation/temporal.ts
7
7
  function isPlainObject(value) {
@@ -6,7 +6,7 @@ import { t as MemoryKvStore } from "../kv-DCHkaJ9-.mjs";
6
6
  import { t as ManualClockKvStore } from "../kv-DBRWVx3Q.mjs";
7
7
  import { n as baseOptions, o as stringSchema, t as MockQueue } from "../tasks-dbCShlCb.mjs";
8
8
  import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
9
- import { o as createFederation } from "../middleware-s-AZzx4r.mjs";
9
+ import { o as createFederation } from "../middleware-CpyCNOW_.mjs";
10
10
  //#region src/testing/kv.test.ts
11
11
  test("ManualClockKvStore keeps an entry at its exact expiration instant", async () => {
12
12
  const kv = new ManualClockKvStore();
@@ -348,6 +348,25 @@ type ActorKeyPairsDispatcher<TContextData> = (context: Context<TContextData>, id
348
348
  * @since 0.15.0
349
349
  */
350
350
  type ActorHandleMapper<TContextData> = (context: Context<TContextData>, username: string) => string | null | Promise<string | null>;
351
+ /**
352
+ * A callback that maps an actor's internal identifier to the ID of the
353
+ * [FEP-ef61] portable actor it dispatches, or `null` if the actor is not
354
+ * portable.
355
+ *
356
+ * The returned ID is an `ap:` or `ap+ef61:` URI, e.g.,
357
+ * `ap+ef61://did:key:z6Mk.../actors/alice`, or its compatible identifier.
358
+ * It must be the same ID that the actor dispatcher puts in the actor's `id`,
359
+ * and must not have a fragment.
360
+ *
361
+ * [FEP-ef61]: https://w3id.org/fep/ef61
362
+ *
363
+ * @template TContextData The context data to pass to the {@link Context}.
364
+ * @param context The context.
365
+ * @param identifier The actor's internal identifier.
366
+ * @returns The portable actor's ID, or `null` if the actor is not portable.
367
+ * @since 2.4.0
368
+ */
369
+ type PortableActorIdMapper<TContextData> = (context: Context<TContextData>, identifier: string) => URL | null | Promise<URL | null>;
351
370
  /**
352
371
  * A callback that maps a WebFinger query to the corresponding actor's
353
372
  * internal identifier or username, or `null` if the query is not found.
@@ -1399,6 +1418,16 @@ interface FederationKvPrefixes {
1399
1418
  * @since 2.4.0
1400
1419
  */
1401
1420
  readonly taskDeduplication: KvKey;
1421
+ /**
1422
+ * The key prefix used for remembering which activities received in
1423
+ * [FEP-ef61] portable inboxes have been forwarded to which gateways, so that
1424
+ * each activity is forwarded at most once.
1425
+ *
1426
+ * [FEP-ef61]: https://w3id.org/fep/ef61
1427
+ * @default `["_fedify", "portableInboxForwarding"]`
1428
+ * @since 2.4.0
1429
+ */
1430
+ readonly portableInboxForwarding: KvKey;
1402
1431
  }
1403
1432
  /**
1404
1433
  * Options for {@link FederationOptions.origin} when it is not a string.
@@ -1977,6 +2006,44 @@ interface Federation<TContextData> extends Federatable<TContextData> {
1977
2006
  */
1978
2007
  fetch(request: Request, options: FederationFetchOptions<TContextData>): Promise<Response>;
1979
2008
  }
2009
+ /**
2010
+ * Options for forwarding activities received in [FEP-ef61] portable inboxes to
2011
+ * the other gateways of their actors. See
2012
+ * {@link FederationOptions.portableInboxForwarding}.
2013
+ *
2014
+ * [FEP-ef61]: https://w3id.org/fep/ef61
2015
+ * @since 2.4.0
2016
+ */
2017
+ interface PortableInboxForwardingOptions {
2018
+ /**
2019
+ * The maximum number of other gateways that a single delivery is forwarded
2020
+ * to. Gateways beyond it are skipped with a warning. `0` turns off
2021
+ * forwarding.
2022
+ * @default `10`
2023
+ */
2024
+ maxTargets?: number;
2025
+ /**
2026
+ * How long Fedify remembers that it has forwarded an activity from
2027
+ * a portable inbox to a gateway, under
2028
+ * {@link FederationKvPrefixes.portableInboxForwarding}. Within it, the same
2029
+ * activity is never forwarded to the same gateway again; once it expires,
2030
+ * a redelivery of the activity may be forwarded again. It cannot have
2031
+ * calendar units, i.e., weeks, months, or years.
2032
+ * @default `{ days: 30 }`
2033
+ */
2034
+ ttl?: Temporal.DurationLike;
2035
+ /**
2036
+ * How long a delivery waits for forwarding requests made immediately, i.e.,
2037
+ * when no outbox queue is configured, before Fedify responds to it.
2038
+ * Requests still running afterwards continue in the background. It does
2039
+ * not apply when forwarding is queued. It cannot have calendar units,
2040
+ * i.e., weeks, months, or years, and cannot be longer than
2041
+ * 2,147,483,647 milliseconds (about 24.8 days), the longest delay timers
2042
+ * support.
2043
+ * @default `{ seconds: 10 }`
2044
+ */
2045
+ deadline?: Temporal.DurationLike;
2046
+ }
1980
2047
  /**
1981
2048
  * Policy for emitting `Accept-Signature` challenges on inbox `401`
1982
2049
  * responses, as defined in
@@ -2071,6 +2138,14 @@ interface FederationOptions<TContextData> {
2071
2138
  * @since 2.4.0
2072
2139
  */
2073
2140
  httpMessageSignaturesSpecTtl?: Temporal.DurationLike;
2141
+ /**
2142
+ * Options for forwarding activities received in [FEP-ef61] portable inboxes
2143
+ * to the other gateways of their actors.
2144
+ *
2145
+ * [FEP-ef61]: https://w3id.org/fep/ef61
2146
+ * @since 2.4.0
2147
+ */
2148
+ portableInboxForwarding?: PortableInboxForwardingOptions;
2074
2149
  /**
2075
2150
  * The message queue for sending and receiving activities. If not provided,
2076
2151
  * activities will not be queued and will be processed immediately.
@@ -2365,6 +2440,30 @@ interface ActorCallbackSetters<TContextData> {
2365
2440
  * @since 1.4.0
2366
2441
  */
2367
2442
  mapAlias(mapper: ActorAliasMapper<TContextData>): ActorCallbackSetters<TContextData>;
2443
+ /**
2444
+ * Sets the callback function that maps an actor's identifier to the ID of
2445
+ * the [FEP-ef61] portable actor it dispatches. If the callback returns
2446
+ * a portable ID for an identifier, the key pairs of that actor are treated
2447
+ * as this server's *gateway keys* for the portable actor:
2448
+ * {@link Context.getActorKeyPairs} derives their key IDs from the actor's
2449
+ * compatible identifier on this server, e.g.,
2450
+ * `https://example.com/.well-known/apgateway/did:key:z6Mk.../actors/alice#main-key`,
2451
+ * and makes the portable actor their owner, so that requests this server
2452
+ * makes on behalf of the actor, such as activity deliveries and signed
2453
+ * fetches, are signed with them. Gateway keys are only used for HTTP
2454
+ * Signatures; they never make Object Integrity Proofs or Linked Data
2455
+ * Signatures.
2456
+ *
2457
+ * If it's omitted, or it returns `null`, the actor's keys are derived from
2458
+ * {@link Context.getActorUri} as usual.
2459
+ *
2460
+ * [FEP-ef61]: https://w3id.org/fep/ef61
2461
+ * @param mapper A callback that maps an actor's identifier to its portable
2462
+ * ID, or `null` if the actor is not portable.
2463
+ * @returns The setters object so that settings can be chained.
2464
+ * @since 2.4.0
2465
+ */
2466
+ mapPortableActorId(mapper: PortableActorIdMapper<TContextData>): ActorCallbackSetters<TContextData>;
2368
2467
  /**
2369
2468
  * Maps a fixed path to a sentinel identifier. It is useful for exposing
2370
2469
  * a single, instance-level actor at a fixed path, such as `/actor` for
@@ -2830,6 +2929,35 @@ interface Context<TContextData> {
2830
2929
  * @since 2.4.0
2831
2930
  */
2832
2931
  getPortableObjectUri<TObject extends Object$1>(cls: ConstructorWithTypeId<TObject>, values: Record<string, string>, authority: string): URL;
2932
+ /**
2933
+ * Builds the [FEP-ef61] portable ID of an actor's inbox with the given
2934
+ * identifier under the given DID authority.
2935
+ *
2936
+ * The path is the same as the one {@link Context.getInboxUri} builds from
2937
+ * the inbox path, but the result is an `ap+ef61:` URI whose authority is
2938
+ * the DID, e.g., `ap+ef61://did:key:z6Mk.../users/alice/inbox`. If
2939
+ * a portable actor has it as its `inbox` and lists this server in its
2940
+ * `gateways`, this server accepts deliveries to the inbox through
2941
+ * the FEP-ef61 gateway endpoint, e.g.,
2942
+ * `POST /.well-known/apgateway/did:key:z6Mk.../users/alice/inbox`, and
2943
+ * dispatches them to the inbox listeners.
2944
+ *
2945
+ * The returned `URL` keeps the DID authority percent-encoded, because
2946
+ * the `URL` class cannot represent the canonical form. Use `formatIri()`
2947
+ * from `@fedify/vocab-runtime` to get the canonical string.
2948
+ *
2949
+ * [FEP-ef61]: https://w3id.org/fep/ef61
2950
+ *
2951
+ * @param identifier The actor's identifier.
2952
+ * @param authority The DID that controls the actor, e.g.,
2953
+ * `did:key:z6Mk...`. It must be a bare DID, without
2954
+ * a path, query, or fragment.
2955
+ * @returns The inbox's portable ID.
2956
+ * @throws {RouterError} If no inbox path is registered.
2957
+ * @throws {TypeError} If the authority is not a DID.
2958
+ * @since 2.4.0
2959
+ */
2960
+ getPortableInboxUri(identifier: string, authority: string): URL;
2833
2961
  /**
2834
2962
  * Builds the URI of an actor's outbox with the given identifier.
2835
2963
  * @param identifier The actor's identifier.
@@ -3244,6 +3372,29 @@ interface RequestContext<TContextData> extends Context<TContextData> {
3244
3372
  * @since 2.4.0
3245
3373
  */
3246
3374
  getPortableObjectUri<TObject extends Object$1>(cls: ConstructorWithTypeId<TObject>, values: Record<string, string>, authority?: string): URL;
3375
+ /**
3376
+ * Builds the [FEP-ef61] portable ID of an actor's inbox with the given
3377
+ * identifier. It works the same as {@link Context.getPortableInboxUri},
3378
+ * except that the authority can be omitted while handling a portable object
3379
+ * request, i.e., when {@link RequestContext.portableRequest} is set. In
3380
+ * that case, its DID is used. Deliveries to portable inboxes do not set
3381
+ * {@link RequestContext.portableRequest}, so pass the authority explicitly
3382
+ * while handling them.
3383
+ *
3384
+ * [FEP-ef61]: https://w3id.org/fep/ef61
3385
+ *
3386
+ * @param identifier The actor's identifier.
3387
+ * @param authority The DID that controls the actor, e.g.,
3388
+ * `did:key:z6Mk...`. It must be a bare DID, without
3389
+ * a path, query, or fragment. Defaults to the DID of
3390
+ * the requested portable object, if any.
3391
+ * @returns The inbox's portable ID.
3392
+ * @throws {RouterError} If no inbox path is registered.
3393
+ * @throws {TypeError} If the authority is not a DID, or the authority is
3394
+ * omitted outside a portable gateway request.
3395
+ * @since 2.4.0
3396
+ */
3397
+ getPortableInboxUri(identifier: string, authority?: string): URL;
3247
3398
  /**
3248
3399
  * Creates a new context with the same properties as this one,
3249
3400
  * but with the given data.
@@ -2,8 +2,8 @@ import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { r as testDefinitions } from "../dist-BQvArojP.mjs";
5
- import { n as createOutboxContext, r as createRequestContext, t as createInboxContext } from "../context-DG5QeQ8W.mjs";
5
+ import { n as createOutboxContext, r as createRequestContext, t as createInboxContext } from "../context-DlZhxdFT.mjs";
6
6
  import { t as ManualClockKvStore } from "../kv-DBRWVx3Q.mjs";
7
7
  import { a as numberSchema, i as makeSchema, n as baseOptions, o as stringSchema, r as envelopeSchema, t as MockQueue } from "../tasks-dbCShlCb.mjs";
8
- import "../testing-By1YYsIK.mjs";
8
+ import "../testing-CO_cEvQc.mjs";
9
9
  export { ManualClockKvStore, MockQueue, baseOptions, createInboxContext, createOutboxContext, createRequestContext, envelopeSchema, makeSchema, numberSchema, stringSchema, testDefinitions };
@@ -2,7 +2,7 @@ import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import "./dist-BQvArojP.mjs";
5
- import "./context-DG5QeQ8W.mjs";
5
+ import "./context-DlZhxdFT.mjs";
6
6
  import "./kv-DBRWVx3Q.mjs";
7
7
  import "./tasks-dbCShlCb.mjs";
8
8
  export {};
@@ -5,9 +5,9 @@ import { t as esm_default } from "../esm-BHXXx4Wx.mjs";
5
5
  import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
6
6
  import "../std__assert-DvMDRPes.mjs";
7
7
  import { t as assertRejects } from "../assert_rejects-CTptdYck.mjs";
8
- import { l as verifyRequest } from "../http-f1qaBtOh.mjs";
8
+ import { l as verifyRequest } from "../http-C7qHlv-b.mjs";
9
9
  import { i as rsaPrivateKey2 } from "../keys-DIJNHW-Y.mjs";
10
- import { t as getAuthenticatedDocumentLoader } from "../docloader-_aSNLA90.mjs";
10
+ import { t as getAuthenticatedDocumentLoader } from "../docloader-C0SWQylk.mjs";
11
11
  import { mockDocumentLoader, test } from "@fedify/fixture";
12
12
  import { FetchError, UrlError } from "@fedify/vocab-runtime";
13
13
  //#region src/utils/docloader.test.ts
@@ -1,7 +1,7 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as kvCache, t as MockKvStore } from "../kv-cache-C9IY-FMN.mjs";
4
+ import { n as kvCache, t as MockKvStore } from "../kv-cache-DEH5jyND.mjs";
5
5
  import { deepStrictEqual, throws } from "node:assert";
6
6
  import { createTestMeterProvider, mockDocumentLoader, test } from "@fedify/fixture";
7
7
  import { preloadedContexts } from "@fedify/vocab-runtime";
@@ -1,5 +1,5 @@
1
1
  const { URLPattern } = require("urlpattern-polyfill");
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
- const require_kv_cache = require("../kv-cache-CY6pDNoS.cjs");
3
+ const require_kv_cache = require("../kv-cache-DvqkKNIj.cjs");
4
4
  exports.getAuthenticatedDocumentLoader = require_kv_cache.getAuthenticatedDocumentLoader;
5
5
  exports.kvCache = require_kv_cache.kvCache;
@@ -1,3 +1,3 @@
1
1
  /// <reference lib="esnext.temporal" />
2
- import { n as getAuthenticatedDocumentLoader, t as kvCache } from "../mod-CyiSjRkk.cjs";
2
+ import { n as getAuthenticatedDocumentLoader, t as kvCache } from "../mod-xGFCHqq_.cjs";
3
3
  export { getAuthenticatedDocumentLoader, kvCache };
@@ -1,3 +1,3 @@
1
1
  /// <reference lib="esnext.temporal" />
2
- import { n as getAuthenticatedDocumentLoader, t as kvCache } from "../mod-C0BYc0OU.js";
2
+ import { n as getAuthenticatedDocumentLoader, t as kvCache } from "../mod-CVZrXSdm.js";
3
3
  export { getAuthenticatedDocumentLoader, kvCache };
package/dist/utils/mod.js CHANGED
@@ -1,4 +1,4 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
- import { n as getAuthenticatedDocumentLoader, t as kvCache } from "../kv-cache-Cgj-kpQS.js";
3
+ import { n as getAuthenticatedDocumentLoader, t as kvCache } from "../kv-cache-DCJuwdnB.js";
4
4
  export { getAuthenticatedDocumentLoader, kvCache };