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

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 (79) hide show
  1. package/dist/{builder-Ld4Y9ymR.mjs → builder-Bno54o_-.mjs} +2 -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-CTx_lewQ.mjs → compound-proof-X3SxXVbc.mjs} +2 -2
  6. package/dist/{context-CZjZYXFC.d.cts → context-C43spTGI.d.cts} +5 -2
  7. package/dist/{context-BSywYspi.d.ts → context-MORIQJIh.d.ts} +5 -2
  8. package/dist/{deno-8t8k_6Yv.mjs → deno-dxdvl2XZ.mjs} +1 -1
  9. package/dist/{docloader-DJKcsAiF.mjs → docloader-eBxQDsk9.mjs} +2 -2
  10. package/dist/federation/builder.test.mjs +1 -1
  11. package/dist/federation/compound-fanout.test.mjs +3 -3
  12. package/dist/federation/handler-compound-proof.test.mjs +3 -3
  13. package/dist/federation/handler.test.mjs +5 -5
  14. package/dist/federation/hashlink-media.test.mjs +2 -2
  15. package/dist/federation/idempotency.test.mjs +2 -2
  16. package/dist/federation/metrics.test.mjs +1 -1
  17. package/dist/federation/middleware.test.mjs +7 -7
  18. package/dist/federation/mod.cjs +1 -1
  19. package/dist/federation/mod.d.cts +2 -2
  20. package/dist/federation/mod.d.ts +2 -2
  21. package/dist/federation/mod.js +1 -1
  22. package/dist/federation/outgoing-proof.test.mjs +5 -5
  23. package/dist/federation/portable-gateway-keys.test.mjs +3 -3
  24. package/dist/federation/portable-inbox.test.mjs +375 -10
  25. package/dist/federation/portable.test.mjs +3 -3
  26. package/dist/federation/send.test.mjs +3 -3
  27. package/dist/federation/tasks/enqueue.test.mjs +1 -1
  28. package/dist/federation/tasks/tasks.test.mjs +2 -2
  29. package/dist/federation/temporal.test.mjs +1 -1
  30. package/dist/federation/webfinger.test.mjs +1 -1
  31. package/dist/{federation-aSHO04ob.cjs → federation-C4Ef3JKa.cjs} +1211 -1089
  32. package/dist/{http-DSaSAN7X.cjs → http-C8cZTE3Y.cjs} +89 -2
  33. package/dist/{http-DyT7DP1Y.js → http-D-rbr_Sy.js} +78 -3
  34. package/dist/{http-B2ftY0Ha.mjs → http-DsgawTd8.mjs} +82 -7
  35. package/dist/{key-Dwm4VswT.mjs → key-DYfmjnmp.mjs} +2 -2
  36. package/dist/{kv-cache-DmroAz3M.js → kv-cache-DIxwMeNi.js} +1 -1
  37. package/dist/{kv-cache-BSJAlP4C.cjs → kv-cache-Dvd-Q-E2.cjs} +1 -1
  38. package/dist/{kv-cache-D0yYukqk.mjs → kv-cache-EvOgrFsS.mjs} +1 -1
  39. package/dist/{ld-BYZOoDzZ.mjs → ld-B00wWHzc.mjs} +3 -3
  40. package/dist/{metrics-B7YwKTFr.mjs → metrics-BP3vdBpb.mjs} +1 -1
  41. package/dist/{middleware-E_0IH72m.mjs → middleware-Br1_d-Ga.mjs} +627 -505
  42. package/dist/{middleware-Blq0882d.js → middleware-CcXMe400.js} +1307 -1185
  43. package/dist/{middleware-BNeqKSsL.mjs → middleware-CeBVBa61.mjs} +1 -1
  44. package/dist/{mod-B2h2KY0q.d.ts → mod-BL5BQHCq.d.ts} +1 -1
  45. package/dist/{mod-JzkaEXsi.d.cts → mod-DMI4DbOo.d.cts} +1 -1
  46. package/dist/mod.cjs +5 -5
  47. package/dist/mod.d.cts +2 -2
  48. package/dist/mod.d.ts +2 -2
  49. package/dist/mod.js +5 -5
  50. package/dist/nodeinfo/handler.test.mjs +1 -1
  51. package/dist/{owner-Cgx2Dc7I.mjs → owner-BJPDRDRq.mjs} +3 -3
  52. package/dist/{owner-szIZl4qI.cjs → owner-DSzH7wro.cjs} +1 -1
  53. package/dist/{owner-DAGSC5gX.js → owner-U12fDpQW.js} +1 -1
  54. package/dist/{portable-key-Cn1WJSN1.mjs → portable-key-D0mWbV9G.mjs} +1 -1
  55. package/dist/{proof-DVXC6nYz.mjs → proof-ChjBwnep.mjs} +4 -4
  56. package/dist/{send-D-hOvYKy.mjs → send-yFX0E-ye.mjs} +3 -3
  57. package/dist/sig/compound-proof-discovery.test.mjs +1 -1
  58. package/dist/sig/compound-proof-producer.test.mjs +1 -1
  59. package/dist/sig/compound-proof-verification.test.mjs +2 -2
  60. package/dist/sig/compound-proof.test.mjs +2 -2
  61. package/dist/sig/http.test.mjs +72 -2
  62. package/dist/sig/key.test.mjs +1 -1
  63. package/dist/sig/ld.test.mjs +2 -2
  64. package/dist/sig/mod.cjs +3 -3
  65. package/dist/sig/mod.js +3 -3
  66. package/dist/sig/owner.test.mjs +1 -1
  67. package/dist/sig/portable-collection.test.mjs +2 -2
  68. package/dist/sig/portable-key.test.mjs +3 -3
  69. package/dist/sig/proof.test.mjs +3 -3
  70. package/dist/{sig-CXOKN4Br.cjs → sig-B689Nj7y.cjs} +2 -2
  71. package/dist/{sig-B5Ell7pO.js → sig-C85KqAcx.js} +2 -2
  72. package/dist/{temporal-CqpKGEPk.mjs → temporal-Dl9x-Qw4.mjs} +1 -1
  73. package/dist/testing/kv.test.mjs +1 -1
  74. package/dist/testing/mod.d.mts +5 -2
  75. package/dist/utils/docloader.test.mjs +2 -2
  76. package/dist/utils/kv-cache.test.mjs +1 -1
  77. package/dist/utils/mod.cjs +1 -1
  78. package/dist/utils/mod.js +1 -1
  79. package/package.json +6 -6
@@ -8,14 +8,15 @@ import { t as assertFalse } from "../assert_false-CPdRnc3E.mjs";
8
8
  import { t as assertThrows } from "../assert_throws-BYITOvPO.mjs";
9
9
  import "../std__assert-DvMDRPes.mjs";
10
10
  import { t as assert } from "../assert-D1Eafmd_.mjs";
11
- import { p as signJsonLd } from "../ld-BYZOoDzZ.mjs";
12
- import { i as signObject } from "../proof-DVXC6nYz.mjs";
13
- import { s as signRequest, u as verifyRequestDetailed } from "../http-B2ftY0Ha.mjs";
11
+ import { p as signJsonLd } from "../ld-B00wWHzc.mjs";
12
+ import { i as signObject } from "../proof-ChjBwnep.mjs";
13
+ import { f as verifyRequestDetailed, l as signRequest } from "../http-DsgawTd8.mjs";
14
14
  import { a as rsaPrivateKey3, c as rsaPublicKey3, i as rsaPrivateKey2, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2 } from "../keys-DIJNHW-Y.mjs";
15
- import { l as forwardPortableInboxActivity, o as createFederation, u as resolvePortableInboxForwardingOptions } from "../middleware-E_0IH72m.mjs";
16
- import { Create, Follow, Note, Person, Tombstone } from "@fedify/vocab";
15
+ import { _ as coversDelivery, o as createFederation, v as forwardPortableInboxActivity, y as resolvePortableInboxForwardingOptions } from "../middleware-Br1_d-Ga.mjs";
16
+ import { Create, CryptographicKey, Follow, Note, Person, Tombstone } from "@fedify/vocab";
17
17
  import { mockDocumentLoader, test } from "@fedify/fixture";
18
18
  import { exportDidKey, formatIri, parseIri } from "@fedify/vocab-runtime";
19
+ import { encodeBase64 } from "byte-encodings/base64";
19
20
  //#region src/federation/portable-inbox.test.ts
20
21
  const did = await exportDidKey(ed25519PublicKey.publicKey);
21
22
  const keyId = new URL(`${did}#${did.substring(8)}`);
@@ -659,12 +660,12 @@ function signatureKeyId(headers) {
659
660
  * at {@link LOCAL} does: signed by her DID, and embedding the gateway keys in
660
661
  * its `assertionMethod`.
661
662
  */
662
- async function createGatewayLoader(keyPairs) {
663
+ async function createGatewayLoader(keyPairs, origin = LOCAL) {
663
664
  const { federation } = setup({
664
665
  aliceKeyPairs: keyPairs,
665
666
  mapPortableActorId: mapAlice
666
667
  });
667
- const keys = await federation.createContext(new URL(LOCAL)).getActorKeyPairs("alice");
668
+ const keys = await federation.createContext(new URL(origin)).getActorKeyPairs("alice");
668
669
  const document = await (await signObject(new Person({
669
670
  id: aliceId,
670
671
  inbox: parseIri(`${aliceId.href}/inbox`),
@@ -676,7 +677,7 @@ async function createGatewayLoader(keyPairs) {
676
677
  assertionMethods: keys.map((k) => k.multikey)
677
678
  }), ed25519PrivateKey, keyId, { contextLoader: mockDocumentLoader })).toJsonLd({ contextLoader: mockDocumentLoader });
678
679
  return (url) => {
679
- if (url.replace(/#.*$/, "") !== compatibleAliceId()) return mockDocumentLoader(url);
680
+ if (url.replace(/#.*$/, "") !== compatibleAliceId(origin)) return mockDocumentLoader(url);
680
681
  return Promise.resolve({
681
682
  contextUrl: null,
682
683
  documentUrl: url,
@@ -919,14 +920,240 @@ test("A Fedify gateway accepts signed forwarded portable inbox deliveries", asyn
919
920
  }
920
921
  await assertSignedBy(forwarded, `${compatibleAliceId()}#main-key`, await createGatewayLoader([rsaGatewayKeyPair]));
921
922
  const queue = new RecordingQueue();
923
+ const loader = await createGatewayLoader([rsaGatewayKeyPair]);
922
924
  const { federation, received } = setup({
923
925
  queue,
924
- options: { origin: GATEWAY2 }
926
+ options: {
927
+ origin: GATEWAY2,
928
+ documentLoaderFactory: () => loader
929
+ }
925
930
  });
926
931
  assertEquals((await federation.fetch(toRequest(forwarded), { contextData: void 0 })).status, 202);
927
932
  assertEquals(received.length, 1);
928
933
  assertEquals(received[0].recipient, "alice");
929
- assertEquals(queue.outbox.map((m) => m.inbox).sort(), [inboxUrl(did, "/users/alice/inbox", LOCAL), inboxUrl(did, "/users/alice/inbox", GATEWAY3)]);
934
+ assertEquals(queue.outbox.map((m) => m.inbox), [inboxUrl(did, "/users/alice/inbox", GATEWAY3)]);
935
+ });
936
+ /** Records the URLs that a document loader is asked for. */
937
+ function recordLoader(loader) {
938
+ const urls = [];
939
+ return {
940
+ urls,
941
+ loader: (url, options) => {
942
+ urls.push(url);
943
+ return loader(url, options);
944
+ }
945
+ };
946
+ }
947
+ const gateway2KeyId = new URL(`${compatibleAliceId(GATEWAY2)}#main-key`);
948
+ /** Signs a delivery to Alice's inbox on {@link LOCAL}. */
949
+ async function signDelivery(body, { keyId = gateway2KeyId, privateKey = rsaGatewayKeyPair.privateKey, spec = "draft-cavage-http-signatures-12", components, label, headers } = {}) {
950
+ const signed = await signRequest(post(inboxUrl(), body, headers), privateKey, keyId, {
951
+ spec,
952
+ ...components == null ? {} : { body: null },
953
+ rfc9421: {
954
+ label,
955
+ components: components?.map((value) => ({
956
+ value,
957
+ params: {}
958
+ }))
959
+ }
960
+ });
961
+ return new Request(signed.url, {
962
+ method: "POST",
963
+ headers: signed.headers,
964
+ body: JSON.stringify(body)
965
+ });
966
+ }
967
+ async function deliverToGateway(request, { loader, kv, maxTargets }) {
968
+ const queue = new RecordingQueue();
969
+ const { federation, received, kv: store } = setup({
970
+ queue,
971
+ kv,
972
+ options: {
973
+ documentLoaderFactory: () => loader,
974
+ ...maxTargets == null ? {} : { portableInboxForwarding: { maxTargets } }
975
+ }
976
+ });
977
+ const response = await federation.fetch(request, { contextData: void 0 });
978
+ assert(received.length <= 1);
979
+ return {
980
+ response,
981
+ queue,
982
+ kv: store
983
+ };
984
+ }
985
+ function forwardedGateways(queue) {
986
+ return queue.outbox.map((m) => new URL(m.inbox).origin).sort();
987
+ }
988
+ test("Federation.fetch() does not forward portable inbox deliveries back to the gateway that forwarded them", async (t) => {
989
+ const gatewayLoader = await createGatewayLoader([rsaGatewayKeyPair], GATEWAY2);
990
+ for (const spec of ["draft-cavage-http-signatures-12", "rfc9421"]) await t.step(spec, async () => {
991
+ const { loader, urls } = recordLoader(gatewayLoader);
992
+ const json = await signedFollow();
993
+ const { response, queue, kv } = await deliverToGateway(await signDelivery(json, { spec }), { loader });
994
+ assertEquals(response.status, 202);
995
+ assertEquals(forwardedGateways(queue), [GATEWAY3]);
996
+ assertEquals(urls.filter((u) => u.startsWith(GATEWAY2)), [gateway2KeyId.href]);
997
+ const { queue: queue2 } = await deliverToGateway(post(inboxUrl(), json), {
998
+ loader,
999
+ kv
1000
+ });
1001
+ assertEquals(queue2.outbox, []);
1002
+ });
1003
+ await t.step("identified before the targets are capped", async () => {
1004
+ const { queue } = await deliverToGateway(await signDelivery(await signedFollow()), {
1005
+ loader: gatewayLoader,
1006
+ maxTargets: 1
1007
+ });
1008
+ assertEquals(forwardedGateways(queue), [GATEWAY3]);
1009
+ });
1010
+ await t.step("with an unrelated signature first", async () => {
1011
+ const { loader, urls } = recordLoader(gatewayLoader);
1012
+ const json = await signedFollow();
1013
+ const unrelated = await signDelivery(json, {
1014
+ spec: "rfc9421",
1015
+ keyId: new URL("https://unrelated.example/key"),
1016
+ privateKey: rsaPrivateKey3,
1017
+ label: "sig1"
1018
+ });
1019
+ const gateway = await signDelivery(json, {
1020
+ spec: "rfc9421",
1021
+ label: "sig2"
1022
+ });
1023
+ const headers = new Headers(gateway.headers);
1024
+ for (const name of ["Signature-Input", "Signature"]) headers.set(name, `${unrelated.headers.get(name)}, ${gateway.headers.get(name)}`);
1025
+ const { queue } = await deliverToGateway(new Request(gateway.url, {
1026
+ method: "POST",
1027
+ headers,
1028
+ body: JSON.stringify(json)
1029
+ }), { loader });
1030
+ assertEquals(forwardedGateways(queue), [GATEWAY3]);
1031
+ assertFalse(urls.some((u) => u.startsWith("https://unrelated.example/")));
1032
+ });
1033
+ await t.step("activity authenticated by a Linked Data Signature", async () => {
1034
+ const { queue } = await deliverToGateway(await signDelivery(await signJsonLd({
1035
+ "@context": "https://www.w3.org/ns/activitystreams",
1036
+ id: `https://example.com/follows/lds-${++activityCounter}`,
1037
+ type: "Follow",
1038
+ actor: "https://example.com/person2",
1039
+ object: formatIri(aliceId)
1040
+ }, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: mockDocumentLoader })), { loader: gatewayLoader });
1041
+ assertEquals(forwardedGateways(queue), [GATEWAY3]);
1042
+ });
1043
+ });
1044
+ test("Federation.fetch() forwards portable inbox deliveries whose sending gateway is not identified", async (t) => {
1045
+ const gatewayLoader = await createGatewayLoader([rsaGatewayKeyPair], GATEWAY2);
1046
+ const bobKeyId = new URL(`${GATEWAY2}/.well-known/apgateway/${did}/users/bob#main-key`);
1047
+ const compatibleId = new URL(compatibleAliceId(GATEWAY2));
1048
+ const unsignedActor = await new Person({
1049
+ id: compatibleId,
1050
+ inbox: new URL(`${compatibleId.href}/inbox`),
1051
+ publicKey: rsaPublicKey2.clone({
1052
+ id: gateway2KeyId,
1053
+ owner: compatibleId
1054
+ })
1055
+ }).toJsonLd({ contextLoader: mockDocumentLoader });
1056
+ const unsignedLoader = (url) => {
1057
+ if (url.replace(/#.*$/, "") !== compatibleId.href) return mockDocumentLoader(url);
1058
+ return Promise.resolve({
1059
+ contextUrl: null,
1060
+ documentUrl: url,
1061
+ document: structuredClone(unsignedActor)
1062
+ });
1063
+ };
1064
+ const json = await signedFollow();
1065
+ const contentDigest = `sha-256=:${encodeBase64(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(JSON.stringify(json))))}:`;
1066
+ const cases = [
1067
+ {
1068
+ name: "unsigned",
1069
+ unsigned: true,
1070
+ fetches: false
1071
+ },
1072
+ {
1073
+ name: "invalid signature",
1074
+ sign: { privateKey: rsaPrivateKey2 },
1075
+ fetches: true
1076
+ },
1077
+ {
1078
+ name: "unknown key",
1079
+ loader: mockDocumentLoader,
1080
+ fetches: true
1081
+ },
1082
+ {
1083
+ name: "gateway not in gateways",
1084
+ sign: { keyId: new URL(`https://unlisted.example/.well-known/apgateway/${did}/users/alice#main-key`) },
1085
+ fetches: false
1086
+ },
1087
+ {
1088
+ name: "key of another actor",
1089
+ sign: { keyId: bobKeyId },
1090
+ fetches: false
1091
+ },
1092
+ {
1093
+ name: "content-digest not covered",
1094
+ sign: {
1095
+ spec: "rfc9421",
1096
+ components: ["@method", "@target-uri"]
1097
+ },
1098
+ fetches: false
1099
+ },
1100
+ {
1101
+ name: "target URI not covered",
1102
+ sign: {
1103
+ spec: "rfc9421",
1104
+ components: [
1105
+ "@method",
1106
+ "@authority",
1107
+ "@path",
1108
+ "content-digest"
1109
+ ],
1110
+ headers: { "Content-Digest": contentDigest }
1111
+ },
1112
+ fetches: false
1113
+ },
1114
+ {
1115
+ name: "non-canonical spelling of content-digest",
1116
+ sign: {
1117
+ spec: "rfc9421",
1118
+ components: [
1119
+ "@method",
1120
+ "@target-uri",
1121
+ "Content-Digest"
1122
+ ],
1123
+ headers: { "Content-Digest": "sha-256=:AAAA:" }
1124
+ },
1125
+ fetches: false
1126
+ },
1127
+ {
1128
+ name: "key not vouched for by the DID",
1129
+ sign: { privateKey: rsaPrivateKey2 },
1130
+ loader: unsignedLoader,
1131
+ fetches: true
1132
+ }
1133
+ ];
1134
+ for (const { name, sign, unsigned, loader, fetches } of cases) await t.step(name, async () => {
1135
+ const recorded = recordLoader(loader ?? gatewayLoader);
1136
+ const { response, queue } = await deliverToGateway(unsigned ? post(inboxUrl(), json) : await signDelivery(json, sign), { loader: recorded.loader });
1137
+ assertEquals(response.status, 202);
1138
+ assertEquals(forwardedGateways(queue), [GATEWAY3, GATEWAY2].sort());
1139
+ assertEquals(recorded.urls.some((u) => u.includes("/.well-known/apgateway/")), fetches);
1140
+ });
1141
+ await t.step("already forwarded to the gateway", async () => {
1142
+ const recorded = recordLoader(gatewayLoader);
1143
+ const kv = new MemoryKvStore();
1144
+ const json = await signedFollow();
1145
+ const { queue } = await deliverToGateway(post(inboxUrl(), json), {
1146
+ loader: recorded.loader,
1147
+ kv
1148
+ });
1149
+ assertEquals(forwardedGateways(queue), [GATEWAY3, GATEWAY2].sort());
1150
+ const { queue: queue2 } = await deliverToGateway(await signDelivery(json), {
1151
+ loader: recorded.loader,
1152
+ kv
1153
+ });
1154
+ assertEquals(queue2.outbox, []);
1155
+ assertEquals(recorded.urls.filter((u) => u.startsWith(GATEWAY2)), []);
1156
+ });
930
1157
  });
931
1158
  function forwardingParameters(overrides = {}) {
932
1159
  const activityId = parseIri(`ap+ef61://${did}/follows/${++activityCounter}`);
@@ -1027,6 +1254,144 @@ test("forwardPortableInboxActivity() does not wait for slow keys", async () => {
1027
1254
  esm_default.hardReset();
1028
1255
  }
1029
1256
  });
1257
+ function gatewaySignature(keyId, verify) {
1258
+ return {
1259
+ keyId,
1260
+ coversDelivery: true,
1261
+ verify
1262
+ };
1263
+ }
1264
+ function gatewayKeyOf(keyId, owner = aliceId) {
1265
+ return new CryptographicKey({
1266
+ id: keyId,
1267
+ owner,
1268
+ publicKey: rsaGatewayKeyPair.publicKey
1269
+ });
1270
+ }
1271
+ test("forwardPortableInboxActivity() skips the gateway that forwarded the activity", async (t) => {
1272
+ const recipient = {
1273
+ actorId: parseIri(`ap://${did}/users/alice`),
1274
+ inboxId: parseIri(`ap+ef61://${did}/users/alice/inbox`),
1275
+ canonicalInboxId: `ap://${did}/users/alice/inbox`,
1276
+ gateways: [
1277
+ new URL(LOCAL),
1278
+ new URL(GATEWAY3),
1279
+ new URL(GATEWAY2)
1280
+ ]
1281
+ };
1282
+ const owners = [["identified", aliceId], ["identified by a key of a compatible identifier", new URL(compatibleAliceId())]];
1283
+ for (const [name, owner] of owners) await t.step(name, async () => {
1284
+ const queue = new RecordingQueue();
1285
+ assertEquals((await forwardPortableInboxActivity(forwardingParameters({
1286
+ recipient,
1287
+ outboxQueue: queue,
1288
+ signatures: [gatewaySignature(gateway2KeyId, () => Promise.resolve(gatewayKeyOf(gateway2KeyId, owner)))]
1289
+ }))).map((u) => u.origin), [GATEWAY3]);
1290
+ });
1291
+ const notIdentified = [
1292
+ ["verify() rejects", [gatewaySignature(gateway2KeyId, () => Promise.reject(/* @__PURE__ */ new Error("Failed.")))]],
1293
+ ["verify() throws", [gatewaySignature(gateway2KeyId, () => {
1294
+ throw new Error("Failed.");
1295
+ })]],
1296
+ ["not verified", [gatewaySignature(gateway2KeyId, () => Promise.resolve(null))]],
1297
+ ["the key is not owned by a portable actor", [gatewaySignature(gateway2KeyId, () => Promise.resolve(gatewayKeyOf(gateway2KeyId, new URL(`${GATEWAY2}/users/alice`))))]],
1298
+ ["the key is owned by another actor", [gatewaySignature(gateway2KeyId, () => Promise.resolve(gatewayKeyOf(gateway2KeyId, parseIri(`ap://${did}/users/bob`))))]],
1299
+ ["another key is verified", [gatewaySignature(gateway2KeyId, () => Promise.resolve(gatewayKeyOf(new URL(`${compatibleAliceId(GATEWAY3)}#main-key`))))]],
1300
+ ["the signature does not cover the delivery", [{
1301
+ ...gatewaySignature(gateway2KeyId, () => Promise.resolve(gatewayKeyOf(gateway2KeyId))),
1302
+ coversDelivery: false
1303
+ }]]
1304
+ ];
1305
+ for (const [name, signatures] of notIdentified) await t.step(name, async () => {
1306
+ const queue = new RecordingQueue();
1307
+ assertEquals((await forwardPortableInboxActivity(forwardingParameters({
1308
+ recipient,
1309
+ outboxQueue: queue,
1310
+ signatures
1311
+ }))).map((u) => u.origin), [GATEWAY3, GATEWAY2]);
1312
+ });
1313
+ await t.step("verified only if it may save a request", async (t) => {
1314
+ const cases = [
1315
+ ["forwarding is turned off", { options: resolvePortableInboxForwardingOptions({ maxTargets: 0 }) }],
1316
+ ["no compare-and-swap", { kv: {
1317
+ ...new MemoryKvStore(),
1318
+ cas: void 0
1319
+ } }],
1320
+ ["this server", { excludedOrigins: [LOCAL, GATEWAY2] }]
1321
+ ];
1322
+ for (const [name, overrides] of cases) await t.step(name, async () => {
1323
+ let calls = 0;
1324
+ await forwardPortableInboxActivity(forwardingParameters({
1325
+ recipient,
1326
+ outboxQueue: new RecordingQueue(),
1327
+ signatures: [gatewaySignature(gateway2KeyId, () => {
1328
+ calls++;
1329
+ return Promise.resolve(gatewayKeyOf(gateway2KeyId));
1330
+ })],
1331
+ ...overrides
1332
+ }));
1333
+ assertEquals(calls, 0);
1334
+ });
1335
+ });
1336
+ await t.step("verification does not finish in time", async () => {
1337
+ let release = () => {};
1338
+ const pending = new Promise((resolve) => release = resolve);
1339
+ const kv = new MemoryKvStore();
1340
+ const parameters = forwardingParameters({
1341
+ recipient,
1342
+ kv,
1343
+ outboxQueue: new RecordingQueue(),
1344
+ options: resolvePortableInboxForwardingOptions({
1345
+ maxTargets: 1,
1346
+ deadline: { milliseconds: 10 }
1347
+ }),
1348
+ signatures: [gatewaySignature(gateway2KeyId, () => pending)]
1349
+ });
1350
+ const started = performance.now();
1351
+ const forwarded = await forwardPortableInboxActivity(parameters);
1352
+ assert(performance.now() - started < 5e3);
1353
+ assertEquals(forwarded.map((u) => u.origin), [GATEWAY3]);
1354
+ release(gatewayKeyOf(gateway2KeyId));
1355
+ await new Promise((resolve) => setTimeout(resolve, 20));
1356
+ const claimed = [];
1357
+ for await (const { key } of kv.list(parameters.kvPrefix)) claimed.push(String(key.at(-1)));
1358
+ assertEquals(claimed, [GATEWAY3]);
1359
+ });
1360
+ });
1361
+ test("coversDelivery()", () => {
1362
+ const signature = (label, ...names) => ({
1363
+ keyId: gateway2KeyId,
1364
+ label,
1365
+ components: names.map((value) => ({
1366
+ value,
1367
+ params: {}
1368
+ }))
1369
+ });
1370
+ assert(coversDelivery(signature("sig1", "@method", "@target-uri", "@authority", "content-digest")));
1371
+ assert(coversDelivery(signature(null, "(request-target)", "host", "date", "digest")));
1372
+ assertFalse(coversDelivery(signature("sig1", "@method", "@target-uri")));
1373
+ assertFalse(coversDelivery(signature("sig1", "@method", "@target-uri", "Content-Digest")));
1374
+ assertFalse(coversDelivery(signature("sig1", "@method", "@authority", "@path", "content-digest")));
1375
+ assertFalse(coversDelivery(signature(null, "(request-target)", "digest")));
1376
+ assertFalse(coversDelivery({
1377
+ keyId: gateway2KeyId,
1378
+ label: "sig1",
1379
+ components: [
1380
+ {
1381
+ value: "@method",
1382
+ params: {}
1383
+ },
1384
+ {
1385
+ value: "@target-uri",
1386
+ params: {}
1387
+ },
1388
+ {
1389
+ value: "content-digest",
1390
+ params: { req: true }
1391
+ }
1392
+ ]
1393
+ }));
1394
+ });
1030
1395
  test("Context.getPortableInboxUri()", async (t) => {
1031
1396
  const { federation } = setup();
1032
1397
  await t.step("Context", () => {
@@ -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 { i as signObject, s as verifyPortableObjectProof } from "../proof-DVXC6nYz.mjs";
12
- import { s as signRequest } from "../http-B2ftY0Ha.mjs";
11
+ import { i as signObject, s as verifyPortableObjectProof } from "../proof-ChjBwnep.mjs";
12
+ import { l as signRequest } from "../http-DsgawTd8.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 { o as createFederation, y as PORTABLE_OBJECT_CONTENT_TYPE } from "../middleware-E_0IH72m.mjs";
14
+ import { b as PORTABLE_OBJECT_CONTENT_TYPE, o as createFederation } from "../middleware-Br1_d-Ga.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 { l as verifyRequest } from "../http-B2ftY0Ha.mjs";
13
+ import { d as verifyRequest } from "../http-DsgawTd8.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-Cgx2Dc7I.mjs";
16
- import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "../send-D-hOvYKy.mjs";
15
+ import { t as doesActorOwnKey } from "../owner-BJPDRDRq.mjs";
16
+ import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "../send-yFX0E-ye.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-dbCShlCb.mjs";
6
6
  import "../../testing-CO_cEvQc.mjs";
7
- import { o as createFederation } from "../../middleware-E_0IH72m.mjs";
7
+ import { o as createFederation } from "../../middleware-Br1_d-Ga.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-dbCShlCb.mjs";
6
6
  import "../../testing-CO_cEvQc.mjs";
7
- import { o as createFederation } from "../../middleware-E_0IH72m.mjs";
8
- import { r as createFederationBuilder } from "../../builder-Ld4Y9ymR.mjs";
7
+ import { o as createFederation } from "../../middleware-Br1_d-Ga.mjs";
8
+ import { r as createFederationBuilder } from "../../builder-Bno54o_-.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-CqpKGEPk.mjs";
7
+ import { t as hasMalformedKnownTemporalLiteral } from "../temporal-Dl9x-Qw4.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-E_0IH72m.mjs";
9
+ import { o as createFederation, s as handleWebFinger } from "../middleware-Br1_d-Ga.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";