@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.
- package/dist/{builder-Ld4Y9ymR.mjs → builder-Bno54o_-.mjs} +2 -2
- package/dist/compat/mod.d.cts +1 -1
- package/dist/compat/mod.d.ts +1 -1
- package/dist/compat/transformers.test.mjs +1 -1
- package/dist/{compound-proof-CTx_lewQ.mjs → compound-proof-X3SxXVbc.mjs} +2 -2
- package/dist/{context-CZjZYXFC.d.cts → context-C43spTGI.d.cts} +5 -2
- package/dist/{context-BSywYspi.d.ts → context-MORIQJIh.d.ts} +5 -2
- package/dist/{deno-8t8k_6Yv.mjs → deno-dxdvl2XZ.mjs} +1 -1
- package/dist/{docloader-DJKcsAiF.mjs → docloader-eBxQDsk9.mjs} +2 -2
- package/dist/federation/builder.test.mjs +1 -1
- package/dist/federation/compound-fanout.test.mjs +3 -3
- package/dist/federation/handler-compound-proof.test.mjs +3 -3
- package/dist/federation/handler.test.mjs +5 -5
- package/dist/federation/hashlink-media.test.mjs +2 -2
- package/dist/federation/idempotency.test.mjs +2 -2
- package/dist/federation/metrics.test.mjs +1 -1
- package/dist/federation/middleware.test.mjs +7 -7
- package/dist/federation/mod.cjs +1 -1
- package/dist/federation/mod.d.cts +2 -2
- package/dist/federation/mod.d.ts +2 -2
- package/dist/federation/mod.js +1 -1
- package/dist/federation/outgoing-proof.test.mjs +5 -5
- package/dist/federation/portable-gateway-keys.test.mjs +3 -3
- package/dist/federation/portable-inbox.test.mjs +375 -10
- package/dist/federation/portable.test.mjs +3 -3
- package/dist/federation/send.test.mjs +3 -3
- package/dist/federation/tasks/enqueue.test.mjs +1 -1
- package/dist/federation/tasks/tasks.test.mjs +2 -2
- package/dist/federation/temporal.test.mjs +1 -1
- package/dist/federation/webfinger.test.mjs +1 -1
- package/dist/{federation-aSHO04ob.cjs → federation-C4Ef3JKa.cjs} +1211 -1089
- package/dist/{http-DSaSAN7X.cjs → http-C8cZTE3Y.cjs} +89 -2
- package/dist/{http-DyT7DP1Y.js → http-D-rbr_Sy.js} +78 -3
- package/dist/{http-B2ftY0Ha.mjs → http-DsgawTd8.mjs} +82 -7
- package/dist/{key-Dwm4VswT.mjs → key-DYfmjnmp.mjs} +2 -2
- package/dist/{kv-cache-DmroAz3M.js → kv-cache-DIxwMeNi.js} +1 -1
- package/dist/{kv-cache-BSJAlP4C.cjs → kv-cache-Dvd-Q-E2.cjs} +1 -1
- package/dist/{kv-cache-D0yYukqk.mjs → kv-cache-EvOgrFsS.mjs} +1 -1
- package/dist/{ld-BYZOoDzZ.mjs → ld-B00wWHzc.mjs} +3 -3
- package/dist/{metrics-B7YwKTFr.mjs → metrics-BP3vdBpb.mjs} +1 -1
- package/dist/{middleware-E_0IH72m.mjs → middleware-Br1_d-Ga.mjs} +627 -505
- package/dist/{middleware-Blq0882d.js → middleware-CcXMe400.js} +1307 -1185
- package/dist/{middleware-BNeqKSsL.mjs → middleware-CeBVBa61.mjs} +1 -1
- package/dist/{mod-B2h2KY0q.d.ts → mod-BL5BQHCq.d.ts} +1 -1
- package/dist/{mod-JzkaEXsi.d.cts → mod-DMI4DbOo.d.cts} +1 -1
- package/dist/mod.cjs +5 -5
- package/dist/mod.d.cts +2 -2
- package/dist/mod.d.ts +2 -2
- package/dist/mod.js +5 -5
- package/dist/nodeinfo/handler.test.mjs +1 -1
- package/dist/{owner-Cgx2Dc7I.mjs → owner-BJPDRDRq.mjs} +3 -3
- package/dist/{owner-szIZl4qI.cjs → owner-DSzH7wro.cjs} +1 -1
- package/dist/{owner-DAGSC5gX.js → owner-U12fDpQW.js} +1 -1
- package/dist/{portable-key-Cn1WJSN1.mjs → portable-key-D0mWbV9G.mjs} +1 -1
- package/dist/{proof-DVXC6nYz.mjs → proof-ChjBwnep.mjs} +4 -4
- package/dist/{send-D-hOvYKy.mjs → send-yFX0E-ye.mjs} +3 -3
- package/dist/sig/compound-proof-discovery.test.mjs +1 -1
- package/dist/sig/compound-proof-producer.test.mjs +1 -1
- package/dist/sig/compound-proof-verification.test.mjs +2 -2
- package/dist/sig/compound-proof.test.mjs +2 -2
- package/dist/sig/http.test.mjs +72 -2
- package/dist/sig/key.test.mjs +1 -1
- package/dist/sig/ld.test.mjs +2 -2
- package/dist/sig/mod.cjs +3 -3
- package/dist/sig/mod.js +3 -3
- package/dist/sig/owner.test.mjs +1 -1
- package/dist/sig/portable-collection.test.mjs +2 -2
- package/dist/sig/portable-key.test.mjs +3 -3
- package/dist/sig/proof.test.mjs +3 -3
- package/dist/{sig-CXOKN4Br.cjs → sig-B689Nj7y.cjs} +2 -2
- package/dist/{sig-B5Ell7pO.js → sig-C85KqAcx.js} +2 -2
- package/dist/{temporal-CqpKGEPk.mjs → temporal-Dl9x-Qw4.mjs} +1 -1
- package/dist/testing/kv.test.mjs +1 -1
- package/dist/testing/mod.d.mts +5 -2
- package/dist/utils/docloader.test.mjs +2 -2
- package/dist/utils/kv-cache.test.mjs +1 -1
- package/dist/utils/mod.cjs +1 -1
- package/dist/utils/mod.js +1 -1
- 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-
|
|
12
|
-
import { i as signObject } from "../proof-
|
|
13
|
-
import {
|
|
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 {
|
|
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(
|
|
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: {
|
|
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)
|
|
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-
|
|
12
|
-
import {
|
|
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 {
|
|
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 {
|
|
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-
|
|
16
|
-
import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "../send-
|
|
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-
|
|
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-
|
|
8
|
-
import { r as createFederationBuilder } from "../../builder-
|
|
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-
|
|
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-
|
|
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";
|