@fedify/fedify 2.4.0-dev.2077 → 2.4.0-dev.2093

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 (75) hide show
  1. package/dist/{builder-i3jxVe9m.mjs → builder-Ld4Y9ymR.mjs} +34 -3
  2. package/dist/compat/transformers.test.mjs +1 -1
  3. package/dist/{compound-proof-Cayy-ECj.mjs → compound-proof-CTx_lewQ.mjs} +191 -13
  4. package/dist/{deno-Djkzd-G_.mjs → deno-8t8k_6Yv.mjs} +1 -1
  5. package/dist/{docloader-dA0RswUV.mjs → docloader-DJKcsAiF.mjs} +2 -2
  6. package/dist/federation/builder.test.mjs +1 -1
  7. package/dist/federation/compound-fanout.test.mjs +3 -3
  8. package/dist/federation/handler-compound-proof.test.mjs +3 -3
  9. package/dist/federation/handler.test.mjs +5 -5
  10. package/dist/federation/hashlink-media.test.mjs +2 -2
  11. package/dist/federation/idempotency.test.mjs +2 -2
  12. package/dist/federation/metrics.test.mjs +1 -1
  13. package/dist/federation/middleware.test.mjs +7 -7
  14. package/dist/federation/mod.cjs +1 -1
  15. package/dist/federation/mod.js +1 -1
  16. package/dist/federation/outgoing-proof.test.mjs +254 -5
  17. package/dist/federation/portable-gateway-keys.test.mjs +72 -11
  18. package/dist/federation/portable-inbox.test.mjs +513 -6
  19. package/dist/federation/portable.test.mjs +182 -4
  20. package/dist/federation/send.test.mjs +3 -3
  21. package/dist/federation/tasks/enqueue.test.mjs +1 -1
  22. package/dist/federation/tasks/tasks.test.mjs +2 -2
  23. package/dist/federation/temporal.test.mjs +1 -1
  24. package/dist/federation/webfinger.test.mjs +89 -1
  25. package/dist/{federation-CFd0Jhhv.cjs → federation-aSHO04ob.cjs} +472 -74
  26. package/dist/{http-CCJ97Jql.mjs → http-B2ftY0Ha.mjs} +4 -4
  27. package/dist/{http-Dvy_IwRa.cjs → http-DSaSAN7X.cjs} +1609 -1457
  28. package/dist/{http-aqh3yFdM.js → http-DyT7DP1Y.js} +1518 -1390
  29. package/dist/{key-IxTTC8Yf.mjs → key-Dwm4VswT.mjs} +39 -99
  30. package/dist/{kv-cache-BP69NV0P.cjs → kv-cache-BSJAlP4C.cjs} +1 -1
  31. package/dist/{kv-cache-Bwwbh-xR.mjs → kv-cache-D0yYukqk.mjs} +1 -1
  32. package/dist/{kv-cache-Y6SDRSX6.js → kv-cache-DmroAz3M.js} +1 -1
  33. package/dist/{ld-l0B9nREb.mjs → ld-BYZOoDzZ.mjs} +3 -3
  34. package/dist/{metrics-D1p6hv4z.mjs → metrics-B7YwKTFr.mjs} +1 -1
  35. package/dist/{middleware-BDjCUHMS.mjs → middleware-BNeqKSsL.mjs} +1 -1
  36. package/dist/{middleware-SSfryVzY.js → middleware-Blq0882d.js} +480 -82
  37. package/dist/{middleware-BMgzqBtr.mjs → middleware-E_0IH72m.mjs} +278 -80
  38. package/dist/{mod-HDSvS-ww.d.cts → mod-C8LieBzF.d.cts} +16 -3
  39. package/dist/{mod-C9WSpZWm.d.ts → mod-Coacostr.d.ts} +16 -3
  40. package/dist/mod.cjs +5 -5
  41. package/dist/mod.d.cts +1 -1
  42. package/dist/mod.d.ts +1 -1
  43. package/dist/mod.js +5 -5
  44. package/dist/nodeinfo/handler.test.mjs +1 -1
  45. package/dist/{owner-DGbYmXqU.mjs → owner-Cgx2Dc7I.mjs} +5 -4
  46. package/dist/{owner-jH1z_YfD.js → owner-DAGSC5gX.js} +2 -2
  47. package/dist/{owner-BpOxL5PG.cjs → owner-szIZl4qI.cjs} +2 -2
  48. package/dist/{portable-key-CCwJGSsU.mjs → portable-key-Cn1WJSN1.mjs} +60 -41
  49. package/dist/portable-key-id-Bva5iT3W.mjs +136 -0
  50. package/dist/{proof-CVL7JnZd.mjs → proof-DVXC6nYz.mjs} +59 -18
  51. package/dist/{send-Ejy1jOz9.mjs → send-D-hOvYKy.mjs} +3 -3
  52. package/dist/sig/compound-proof-discovery.test.mjs +1 -1
  53. package/dist/sig/compound-proof-producer.test.mjs +1 -1
  54. package/dist/sig/compound-proof-verification.test.mjs +160 -3
  55. package/dist/sig/compound-proof.test.mjs +2 -2
  56. package/dist/sig/http.test.mjs +2 -2
  57. package/dist/sig/key.test.mjs +1 -1
  58. package/dist/sig/ld.test.mjs +2 -2
  59. package/dist/sig/mod.cjs +3 -3
  60. package/dist/sig/mod.d.cts +1 -1
  61. package/dist/sig/mod.d.ts +1 -1
  62. package/dist/sig/mod.js +3 -3
  63. package/dist/sig/owner.test.mjs +1 -1
  64. package/dist/sig/portable-collection.test.mjs +69 -5
  65. package/dist/sig/portable-key.test.mjs +176 -10
  66. package/dist/sig/proof.test.mjs +107 -4
  67. package/dist/{sig-Dpy1B1Ad.js → sig-B5Ell7pO.js} +19 -3
  68. package/dist/{sig-xoqy3ATq.cjs → sig-CXOKN4Br.cjs} +19 -3
  69. package/dist/{temporal-DNJ1cNpr.mjs → temporal-CqpKGEPk.mjs} +1 -1
  70. package/dist/testing/kv.test.mjs +1 -1
  71. package/dist/utils/docloader.test.mjs +2 -2
  72. package/dist/utils/kv-cache.test.mjs +1 -1
  73. package/dist/utils/mod.cjs +1 -1
  74. package/dist/utils/mod.js +1 -1
  75. package/package.json +7 -7
@@ -1,13 +1,13 @@
1
1
  import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { r as fetchKey } from "../key-IxTTC8Yf.mjs";
5
- import { l as verifyRequest, s as signRequest } from "../http-CCJ97Jql.mjs";
4
+ import { r as fetchKey } from "../key-Dwm4VswT.mjs";
5
+ import { l as verifyRequest, s as signRequest } from "../http-B2ftY0Ha.mjs";
6
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-DGbYmXqU.mjs";
7
+ import { n as getKeyOwner, t as doesActorOwnKey } from "../owner-Cgx2Dc7I.mjs";
8
8
  import { Create, CryptographicKey, Multikey, Person } from "@fedify/vocab";
9
9
  import { test } from "@fedify/fixture";
10
- import { FetchError, encodeMultibase, exportDidKey, parseIri, preloadedContexts } from "@fedify/vocab-runtime";
10
+ import { FetchError, encodeMultibase, exportDidKey, exportSpki, parseIri, preloadedContexts } from "@fedify/vocab-runtime";
11
11
  import serialize from "json-canon";
12
12
  import { deepStrictEqual, ok, strictEqual } from "node:assert/strict";
13
13
  //#region src/sig/portable-key.test.ts
@@ -129,7 +129,16 @@ test("verifyRequest() rejects gateway keys that portable actors do not vouch for
129
129
  "an unsigned actor document": unsigned,
130
130
  "an actor document signed by another DID": await sign(unsigned, otherKeyPair.privateKey, `${otherDid}#${otherDid.slice(8)}`),
131
131
  "an unlisted gateway": await sign(await actorJson({ gateways: [gw2] })),
132
- "a key missing from assertionMethod": await sign(await actorJson({ assertionMethod: false })),
132
+ "a key missing from both assertionMethod and publicKey": await sign(await actorJson({
133
+ assertionMethod: false,
134
+ publicKey: null
135
+ })),
136
+ "a key referred to by URL in assertionMethod, embedded in publicKey": await sign({
137
+ ...await actorJson({ assertionMethod: false }),
138
+ assertionMethod: [keyId.href]
139
+ }),
140
+ "a key only in publicKey without an owner": await sign(withoutPublicKeyOwner(await actorJson({ assertionMethod: false }))),
141
+ "two keys with the same ID in publicKey": await sign(await duplicatePublicKey(await actorJson({ assertionMethod: false }))),
133
142
  "a key referred to by URL": await sign({
134
143
  ...await actorJson({
135
144
  publicKey: null,
@@ -154,18 +163,175 @@ test("verifyRequest() rejects gateway keys that portable actors do not vouch for
154
163
  contextLoader
155
164
  }), null);
156
165
  });
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]
166
+ function withoutPublicKeyOwner(json) {
167
+ const { owner: _, ...publicKey } = json.publicKey;
168
+ return {
169
+ ...json,
170
+ publicKey
171
+ };
172
+ }
173
+ async function duplicatePublicKey(json) {
174
+ const publicKey = json.publicKey;
175
+ return {
176
+ ...json,
177
+ publicKey: [publicKey, {
178
+ ...publicKey,
179
+ publicKeyPem: await exportSpki(rsaPublicKey3.publicKey)
180
+ }]
181
+ };
182
+ }
183
+ test("verifyRequest() accepts gateway keys referred to by URL in publicKey", async () => {
184
+ const documentLoader = createLoader({ [compatibleId(gw1)]: await sign({
185
+ ...await actorJson({ publicKey: null }),
186
+ publicKey: keyId.href
162
187
  }) });
188
+ strictEqual((await verifyRequest(await signedRequest(), {
189
+ documentLoader,
190
+ contextLoader
191
+ }))?.id?.href, keyId.href);
192
+ });
193
+ test("verifyRequest() accepts gateway keys only in publicKey", async () => {
194
+ const documentLoader = createLoader({ [compatibleId(gw1)]: await sign(await actorJson({ assertionMethod: false })) });
163
195
  const key = await verifyRequest(await signedRequest(), {
164
196
  documentLoader,
165
197
  contextLoader
166
198
  });
167
199
  strictEqual(key?.id?.href, keyId.href);
200
+ strictEqual(key?.ownerId?.href, parseIri(actorId).href);
201
+ });
202
+ test("verifyRequest() resolves gateway keys of compatible-ID actors", async () => {
203
+ const id = compatibleId(gw1);
204
+ const signed = await sign(await actorJson({
205
+ id,
206
+ gateways: [gw1]
207
+ }));
208
+ let key = await verifyRequest(await signedRequest(), {
209
+ documentLoader: createLoader({ [id]: signed }),
210
+ contextLoader
211
+ });
212
+ strictEqual(key?.id?.href, keyId.href);
168
213
  strictEqual(key?.ownerId?.href, id);
214
+ key = await verifyRequest(await signedRequest(), {
215
+ documentLoader: createLoader({ [id]: await sign(await actorJson({
216
+ id,
217
+ gateways: [gw1],
218
+ assertionMethod: false
219
+ })) }),
220
+ contextLoader
221
+ });
222
+ strictEqual(key?.id?.href, keyId.href);
223
+ const evil = "https://evil.example";
224
+ const evilId = compatibleId(evil);
225
+ const evilKeyId = new URL(`${evilId}#main-key`);
226
+ const unsigned = await actorJson({
227
+ id: evilId,
228
+ gateways: [evil],
229
+ key: evilKeyId
230
+ });
231
+ const documentLoader = createLoader({ [evilId]: unsigned });
232
+ strictEqual(await verifyRequest(await signedRequest(void 0, evilKeyId), {
233
+ documentLoader,
234
+ contextLoader
235
+ }), null);
236
+ strictEqual(await getKeyOwner(evilKeyId, {
237
+ documentLoader,
238
+ contextLoader
239
+ }), null);
240
+ strictEqual(await verifyRequest(await signedRequest(), {
241
+ documentLoader: createLoader({ [id]: await sign(await actorJson({
242
+ id,
243
+ gateways: [gw1]
244
+ }), otherKeyPair.privateKey, `${otherDid}#${otherDid.slice(8)}`) }),
245
+ contextLoader
246
+ }), null);
247
+ });
248
+ test("keys at ordinary URLs cannot belong to portable actors", async () => {
249
+ const evil = "https://evil.example";
250
+ const evilActorId = compatibleId(evil);
251
+ const evilKeyId = new URL(`${evil}/keys/1`);
252
+ const options = {
253
+ documentLoader: createLoader({
254
+ [evilKeyId.href]: {
255
+ "@context": "https://w3id.org/security/v1",
256
+ id: evilKeyId.href,
257
+ type: "Key",
258
+ owner: evilActorId,
259
+ publicKeyPem: await exportSpki(gatewayPublicKey)
260
+ },
261
+ [evilActorId]: await actorJson({
262
+ id: evilActorId,
263
+ gateways: [evil],
264
+ key: evilKeyId,
265
+ assertionMethod: false
266
+ })
267
+ }),
268
+ contextLoader
269
+ };
270
+ strictEqual(await verifyRequest(await signedRequest(void 0, evilKeyId), options), null);
271
+ strictEqual((await fetchKey(evilKeyId, CryptographicKey, options)).key, null);
272
+ strictEqual(await getKeyOwner(evilKeyId, options), null);
273
+ const key = new CryptographicKey({
274
+ id: evilKeyId,
275
+ owner: new URL(evilActorId),
276
+ publicKey: gatewayPublicKey
277
+ });
278
+ strictEqual(await getKeyOwner(key, options), null);
279
+ ok(!await doesActorOwnKey(new Create({
280
+ id: new URL(`${evil}/activities/1`),
281
+ actor: new URL(evilActorId)
282
+ }), key, options));
283
+ const actorKeyId = new URL(`${evil}/users/alice#main-key`);
284
+ for (const document of [await actorJson({
285
+ id: evilActorId,
286
+ gateways: [evil],
287
+ key: actorKeyId
288
+ }), withoutPublicKeyOwner(await actorJson({
289
+ id: evilActorId,
290
+ gateways: [evil],
291
+ key: actorKeyId,
292
+ assertionMethod: false
293
+ }))]) {
294
+ const actorOptions = {
295
+ documentLoader: createLoader({ [`${evil}/users/alice`]: document }),
296
+ contextLoader
297
+ };
298
+ strictEqual(await verifyRequest(await signedRequest(void 0, actorKeyId), actorOptions), null);
299
+ strictEqual((await fetchKey(actorKeyId, CryptographicKey, actorOptions)).key, null);
300
+ strictEqual(await getKeyOwner(actorKeyId, actorOptions), null);
301
+ }
302
+ const ownerKeyId = new URL(`${evil}/keys/2`);
303
+ const ownerOptions = {
304
+ documentLoader: createLoader({
305
+ [ownerKeyId.href]: {
306
+ "@context": "https://w3id.org/security/v1",
307
+ id: ownerKeyId.href,
308
+ type: "Key",
309
+ owner: `${evil}/users/bob`,
310
+ publicKeyPem: await exportSpki(gatewayPublicKey)
311
+ },
312
+ [`${evil}/users/bob`]: await actorJson({
313
+ id: evilActorId,
314
+ gateways: [evil],
315
+ key: ownerKeyId,
316
+ assertionMethod: false
317
+ })
318
+ }),
319
+ contextLoader
320
+ };
321
+ strictEqual(await getKeyOwner(ownerKeyId, ownerOptions), null);
322
+ strictEqual(await getKeyOwner(new CryptographicKey({
323
+ id: ownerKeyId,
324
+ owner: new URL(`${evil}/users/bob`),
325
+ publicKey: gatewayPublicKey
326
+ }), ownerOptions), null);
327
+ const keyCache = {
328
+ get: () => Promise.resolve(key),
329
+ set: () => Promise.resolve()
330
+ };
331
+ strictEqual((await fetchKey(evilKeyId, CryptographicKey, {
332
+ ...options,
333
+ keyCache
334
+ })).key, null);
169
335
  });
170
336
  test("gateway keys are neither cached nor accepted outside HTTP Signatures", async () => {
171
337
  const calls = [];
@@ -10,10 +10,10 @@ 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
12
  import { r as normalizeOutgoingActivityJsonLd } from "../outgoing-jsonld-4IJqPYos.mjs";
13
- import { a as verifyObject, l as verifyProof, n as hasProofLike, o as verifyPortableObjectProof, r as signObject, t as createProof } from "../proof-CVL7JnZd.mjs";
14
- import { l as verifyRequest } from "../http-CCJ97Jql.mjs";
13
+ import { i as signObject, n as createProof, o as verifyObject, r as hasProofLike, s as verifyPortableObjectProof, u as verifyProof } from "../proof-DVXC6nYz.mjs";
14
+ import { l as verifyRequest } from "../http-B2ftY0Ha.mjs";
15
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-dA0RswUV.mjs";
16
+ import { t as getAuthenticatedDocumentLoader } from "../docloader-DJKcsAiF.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";
@@ -1064,6 +1064,45 @@ test("verifyPortableObjectProof()", async (t) => {
1064
1064
  reason: { type: "missingProof" }
1065
1065
  });
1066
1066
  });
1067
+ await t.step("treats compatible identifiers as portable IDs", async () => {
1068
+ const compatibleActorId = `https://gateway.example/.well-known/apgateway/did:key:${portableDidMethod}/actor`;
1069
+ const actor = {
1070
+ "@context": portableContext,
1071
+ id: compatibleActorId,
1072
+ type: "Person",
1073
+ inbox: `${compatibleActorId}/inbox`,
1074
+ outbox: `${compatibleActorId}/outbox`
1075
+ };
1076
+ const signed = await signPortableJsonLd(actor);
1077
+ const result = await verifyPortableObjectProof(signed, options);
1078
+ assert(result.verified);
1079
+ assertEquals(result.keys[0].id, portableKeyId);
1080
+ assertEquals(signed.id, compatibleActorId);
1081
+ assertEquals(await verifyPortableObjectProof(actor, options), {
1082
+ verified: false,
1083
+ reason: { type: "missingProof" }
1084
+ });
1085
+ const otherId = `https://gateway.example/.well-known/apgateway/did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK/activities/1`;
1086
+ assertEquals(await verifyPortableObjectProof(await signPortableJsonLd({
1087
+ "@context": portableContext,
1088
+ id: otherId,
1089
+ type: "Create",
1090
+ actor: compatibleActorId,
1091
+ object: "https://social.example/objects/1"
1092
+ }), options), {
1093
+ verified: false,
1094
+ reason: {
1095
+ type: "verificationMethodMismatch",
1096
+ proofIndex: 0,
1097
+ objectId: new URL(otherId),
1098
+ verificationMethod: portableKeyId
1099
+ }
1100
+ });
1101
+ await assertRejects(() => verifyPortableObjectProof({
1102
+ ...actor,
1103
+ id: `https://user@gateway.example/.well-known/apgateway/did:key:${portableDidMethod}/actor`
1104
+ }, options), TypeError);
1105
+ });
1067
1106
  await t.step("keeps non-portable documents outside the policy", async () => {
1068
1107
  for (const document of [
1069
1108
  {
@@ -1072,7 +1111,7 @@ test("verifyPortableObjectProof()", async (t) => {
1072
1111
  },
1073
1112
  {
1074
1113
  ...unsignedObject,
1075
- id: "https://gateway.example/.well-known/apgateway/did:key:z6MkAlice/objects/1"
1114
+ id: "https://gateway.example/.well-known/apgateway/hl:zQmdfTbBqBPQ"
1076
1115
  },
1077
1116
  {
1078
1117
  "@context": portableContext,
@@ -1804,6 +1843,70 @@ test("verifyObject() rejects did:key proofs from another portable attribution or
1804
1843
  contextLoader: mockDocumentLoader
1805
1844
  }), null);
1806
1845
  });
1846
+ async function signCompatibleNote(attribution, keyId) {
1847
+ const context = ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/data-integrity/v1"];
1848
+ return await (await signObject(new Note({
1849
+ id: new URL(`${attribution}/notes/1`),
1850
+ attribution: new URL(attribution),
1851
+ content: "Note of a compatible-ID actor"
1852
+ }), ed25519PrivateKey, keyId, {
1853
+ contextLoader: mockDocumentLoader,
1854
+ context
1855
+ })).toJsonLd({
1856
+ format: "compact",
1857
+ contextLoader: mockDocumentLoader,
1858
+ context
1859
+ });
1860
+ }
1861
+ test("verifyObject() authenticates compatible-ID attributions by DID proofs", async () => {
1862
+ const did = await exportDidKey(ed25519PublicKey.publicKey);
1863
+ const keyId = new URL(`${did}#${did.substring(8)}`);
1864
+ const options = {
1865
+ documentLoader() {
1866
+ throw new TypeError("did:key must not use the document loader");
1867
+ },
1868
+ contextLoader: mockDocumentLoader
1869
+ };
1870
+ for (const gateway of ["https://gw1.example", "https://gw2.example"]) assertInstanceOf(await verifyObject(Note, await signCompatibleNote(`${gateway}/.well-known/apgateway/${did}/actor`, keyId), options), Note);
1871
+ assertEquals(await verifyObject(Note, await signCompatibleNote("https://gw1.example/.well-known/apgateway/did:key:z6MkOther/actor", keyId), options), null);
1872
+ });
1873
+ test("verifyObject() does not trust compatible-ID actors by web origin", async () => {
1874
+ const did = await exportDidKey(ed25519PublicKey.publicKey);
1875
+ const attribution = `https://evil.example/.well-known/apgateway/${did}/actor`;
1876
+ const keyId = new URL("https://evil.example/keys/1");
1877
+ const documents = {
1878
+ [keyId.href]: {
1879
+ "@context": "https://w3id.org/security/multikey/v1",
1880
+ id: keyId.href,
1881
+ type: "Multikey",
1882
+ controller: attribution,
1883
+ publicKeyMultibase: await exportMultibaseKey(ed25519PublicKey.publicKey)
1884
+ },
1885
+ [attribution]: {
1886
+ "@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/multikey/v1"],
1887
+ id: attribution,
1888
+ type: "Person",
1889
+ inbox: `${attribution}/inbox`,
1890
+ assertionMethod: keyId.href
1891
+ }
1892
+ };
1893
+ const fetched = [];
1894
+ assertEquals(await verifyObject(Note, await signCompatibleNote(attribution, keyId), {
1895
+ documentLoader: (url) => {
1896
+ fetched.push(url);
1897
+ const document = documents[url];
1898
+ if (document == null) throw new TypeError(`Unexpected: ${url}`);
1899
+ return Promise.resolve({
1900
+ contextUrl: null,
1901
+ documentUrl: url,
1902
+ document
1903
+ });
1904
+ },
1905
+ contextLoader: mockDocumentLoader
1906
+ }), null);
1907
+ assertEquals(fetched, [keyId.href]);
1908
+ assertEquals(await verifyObject(Note, await signCompatibleNote(`https://user@gw.example/.well-known/apgateway/${did}/actor`, new URL(`${did}#${did.substring(8)}`)), { contextLoader: mockDocumentLoader }), null);
1909
+ });
1807
1910
  test("signObject() preserves FEP-22cd contexts through proof verification", async () => {
1808
1911
  const did = await exportDidKey(ed25519PublicKey.publicKey);
1809
1912
  const keyId = new URL(`${did}#${did.substring(8)}`);
@@ -1,7 +1,7 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
- import { f as verifyPortableObjectProof, ht as version, m as verifyPortableObjectProofWithRoot, mt as name } from "./http-aqh3yFdM.js";
4
- import "./owner-jH1z_YfD.js";
3
+ import { h as verifyPortableObjectProofWithRoot, p as verifyPortableObjectProof, vt as name, yt as version } from "./http-DyT7DP1Y.js";
4
+ import "./owner-DAGSC5gX.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";
@@ -49,6 +49,8 @@ function getContextState(context) {
49
49
  }
50
50
  function canonicalize(id) {
51
51
  try {
52
+ const portable = fromCompatibleEf61Id(id);
53
+ if (portable != null) return canonicalizePortableUri(formatIri(portable));
52
54
  return canonicalizePortableUri(typeof id === "string" ? id : formatIri(id));
53
55
  } catch (error) {
54
56
  if (error instanceof TypeError) return null;
@@ -229,7 +231,20 @@ async function fetchOwner(candidate, options) {
229
231
  const contextLoader = options.contextLoader ?? getDocumentLoader();
230
232
  const tracerProvider = options.tracerProvider ?? trace.getTracerProvider();
231
233
  const tracer = tracerProvider.getTracer(name, version);
232
- const gateways = options.gateways ?? (getPortableGatewayCandidates(candidate).length > 0 ? void 0 : options.gatewayHints);
234
+ let inferredGateways;
235
+ try {
236
+ const portable = fromCompatibleEf61Id(candidate);
237
+ if (portable != null) {
238
+ inferredGateways = [new URL(candidate.origin)];
239
+ candidate = portable;
240
+ }
241
+ } catch (error) {
242
+ if (!(error instanceof TypeError)) throw error;
243
+ logger.debug("The owner {owner} of a portable collection is a malformed compatible identifier.", { owner: candidate.href });
244
+ return null;
245
+ }
246
+ const gateways = options.gateways ?? (inferredGateways != null || getPortableGatewayCandidates(candidate).length > 0 ? void 0 : options.gatewayHints);
247
+ if (options.gateways != null) inferredGateways = void 0;
233
248
  const span = tracer.startSpan("activitypub.lookup_object");
234
249
  try {
235
250
  const object = await dereferencePortableIri(candidate, {
@@ -237,6 +252,7 @@ async function fetchOwner(candidate, options) {
237
252
  contextLoader,
238
253
  tracerProvider,
239
254
  gateways,
255
+ ...inferredGateways == null ? {} : { inferredGateways },
240
256
  verifyPortableObject: (document, verifierOptions) => verifyPortableObjectProof(document, {
241
257
  ...options,
242
258
  ...verifierOptions
@@ -1,6 +1,6 @@
1
1
  const { URLPattern } = require("urlpattern-polyfill");
2
- const require_http = require("./http-Dvy_IwRa.cjs");
3
- require("./owner-BpOxL5PG.cjs");
2
+ const require_http = require("./http-DSaSAN7X.cjs");
3
+ require("./owner-szIZl4qI.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");
@@ -48,6 +48,8 @@ function getContextState(context) {
48
48
  }
49
49
  function canonicalize(id) {
50
50
  try {
51
+ const portable = (0, _fedify_vocab_runtime.fromCompatibleEf61Id)(id);
52
+ if (portable != null) return (0, _fedify_vocab_runtime.canonicalizePortableUri)((0, _fedify_vocab_runtime.formatIri)(portable));
51
53
  return (0, _fedify_vocab_runtime.canonicalizePortableUri)(typeof id === "string" ? id : (0, _fedify_vocab_runtime.formatIri)(id));
52
54
  } catch (error) {
53
55
  if (error instanceof TypeError) return null;
@@ -228,7 +230,20 @@ async function fetchOwner(candidate, options) {
228
230
  const contextLoader = options.contextLoader ?? (0, _fedify_vocab_runtime.getDocumentLoader)();
229
231
  const tracerProvider = options.tracerProvider ?? _opentelemetry_api.trace.getTracerProvider();
230
232
  const tracer = tracerProvider.getTracer(require_http.name, require_http.version);
231
- const gateways = options.gateways ?? ((0, _fedify_vocab_runtime_internal_portable_dereference.getPortableGatewayCandidates)(candidate).length > 0 ? void 0 : options.gatewayHints);
233
+ let inferredGateways;
234
+ try {
235
+ const portable = (0, _fedify_vocab_runtime.fromCompatibleEf61Id)(candidate);
236
+ if (portable != null) {
237
+ inferredGateways = [new URL(candidate.origin)];
238
+ candidate = portable;
239
+ }
240
+ } catch (error) {
241
+ if (!(error instanceof TypeError)) throw error;
242
+ logger.debug("The owner {owner} of a portable collection is a malformed compatible identifier.", { owner: candidate.href });
243
+ return null;
244
+ }
245
+ const gateways = options.gateways ?? (inferredGateways != null || (0, _fedify_vocab_runtime_internal_portable_dereference.getPortableGatewayCandidates)(candidate).length > 0 ? void 0 : options.gatewayHints);
246
+ if (options.gateways != null) inferredGateways = void 0;
232
247
  const span = tracer.startSpan("activitypub.lookup_object");
233
248
  try {
234
249
  const object = await (0, _fedify_vocab_runtime_internal_portable_dereference.dereferencePortableIri)(candidate, {
@@ -236,6 +251,7 @@ async function fetchOwner(candidate, options) {
236
251
  contextLoader,
237
252
  tracerProvider,
238
253
  gateways,
254
+ ...inferredGateways == null ? {} : { inferredGateways },
239
255
  verifyPortableObject: (document, verifierOptions) => require_http.verifyPortableObjectProof(document, {
240
256
  ...options,
241
257
  ...verifierOptions
@@ -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-l0B9nREb.mjs";
4
+ import { c as getNormalizationContextLoader } from "./ld-BYZOoDzZ.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-BMgzqBtr.mjs";
9
+ import { o as createFederation } from "../middleware-E_0IH72m.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();
@@ -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-CCJ97Jql.mjs";
8
+ import { l as verifyRequest } from "../http-B2ftY0Ha.mjs";
9
9
  import { i as rsaPrivateKey2 } from "../keys-DIJNHW-Y.mjs";
10
- import { t as getAuthenticatedDocumentLoader } from "../docloader-dA0RswUV.mjs";
10
+ import { t as getAuthenticatedDocumentLoader } from "../docloader-DJKcsAiF.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-Bwwbh-xR.mjs";
4
+ import { n as kvCache, t as MockKvStore } from "../kv-cache-D0yYukqk.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-BP69NV0P.cjs");
3
+ const require_kv_cache = require("../kv-cache-BSJAlP4C.cjs");
4
4
  exports.getAuthenticatedDocumentLoader = require_kv_cache.getAuthenticatedDocumentLoader;
5
5
  exports.kvCache = require_kv_cache.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-Y6SDRSX6.js";
3
+ import { n as getAuthenticatedDocumentLoader, t as kvCache } from "../kv-cache-DmroAz3M.js";
4
4
  export { getAuthenticatedDocumentLoader, kvCache };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fedify/fedify",
3
- "version": "2.4.0-dev.2077+c29a03d4",
3
+ "version": "2.4.0-dev.2093+e9706a54",
4
4
  "description": "An ActivityPub server framework",
5
5
  "keywords": [
6
6
  "ActivityPub",
@@ -151,10 +151,10 @@
151
151
  "structured-field-values": "^2.0.4",
152
152
  "temporal-polyfill": "^1.0.1",
153
153
  "urlpattern-polyfill": "^10.1.0",
154
- "@fedify/uri-template": "2.4.0-dev.2077+c29a03d4",
155
- "@fedify/vocab": "2.4.0-dev.2077+c29a03d4",
156
- "@fedify/vocab-runtime": "2.4.0-dev.2077+c29a03d4",
157
- "@fedify/webfinger": "2.4.0-dev.2077+c29a03d4"
154
+ "@fedify/uri-template": "2.4.0-dev.2093+e9706a54",
155
+ "@fedify/vocab": "2.4.0-dev.2093+e9706a54",
156
+ "@fedify/webfinger": "2.4.0-dev.2093+e9706a54",
157
+ "@fedify/vocab-runtime": "2.4.0-dev.2093+e9706a54"
158
158
  },
159
159
  "devDependencies": {
160
160
  "@std/assert": "npm:@jsr/std__assert@^0.226.0",
@@ -166,8 +166,8 @@
166
166
  "tsx": "^4.21.0",
167
167
  "typescript": "^6.0.0",
168
168
  "wrangler": "^4.17.0",
169
- "@fedify/vocab-tools": "^2.4.0-dev.2077+c29a03d4",
170
- "@fedify/fixture": "2.0.0"
169
+ "@fedify/fixture": "2.0.0",
170
+ "@fedify/vocab-tools": "^2.4.0-dev.2093+e9706a54"
171
171
  },
172
172
  "scripts": {
173
173
  "build:self": "tsdown",