@fedify/fedify 2.4.0-pr.936.41 → 2.5.0-dev.2271
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/README.md +50 -39
- package/dist/{assert-CJ1KXcvv.mjs → assert-D1Eafmd_.mjs} +1 -1
- package/dist/{assert_equals-BV7oLMxm.mjs → assert_equals-Z6IcEbWU.mjs} +2 -28
- package/dist/assert_exists-cffSpJ7X.mjs +28 -0
- package/dist/assert_false-CPdRnc3E.mjs +23 -0
- package/dist/assert_greater_or_equal-BlafOF4-.mjs +31 -0
- package/dist/{assert_instance_of-Q9KTBnrf.mjs → assert_instance_of-mrXWWNY-.mjs} +1 -1
- package/dist/{assert_not_equals-EPJPoxuf.mjs → assert_not_equals-CpPK2t89.mjs} +2 -1
- package/dist/assert_rejects-CTptdYck.mjs +39 -0
- package/dist/{assert_strict_equals-Ec20AUwa.mjs → assert_strict_equals-CYvBmkpC.mjs} +2 -1
- package/dist/{assert_throws-NiFfqbnH.mjs → assert_throws-BYITOvPO.mjs} +2 -1
- package/dist/assertion_error-B-cx0fFA.mjs +31 -0
- package/dist/audience-CHQ-FlLD.mjs +95 -0
- package/dist/{builder-DonuxRl_.mjs → builder-Bda6LK7A.mjs} +169 -14
- package/dist/{circuit-breaker-CBGyz5F7.mjs → circuit-breaker-XJ53md4S.mjs} +224 -17
- package/dist/circuit-breaker-test-utils-DW6YC_Pe.mjs +14 -0
- package/dist/{client-CjF5IYQS.mjs → client-CEnNz8O7.mjs} +144 -8
- package/dist/{codec-BBga-FWI.mjs → codec-C6rTrpIp.mjs} +26 -3
- package/dist/{collection-BIhlldqK.mjs → collection-CnnP1UAv.mjs} +1 -1
- package/dist/compat/mod.cjs +1 -1
- package/dist/compat/mod.d.cts +1 -1
- package/dist/compat/mod.d.ts +1 -1
- package/dist/compat/mod.js +1 -1
- package/dist/compat/outgoing-jsonld.test.mjs +71 -3
- package/dist/compat/public-audience.test.mjs +14 -5
- package/dist/compat/secured-document.test.d.mts +2 -0
- package/dist/compat/secured-document.test.mjs +58 -0
- package/dist/compat/transformers.test.mjs +5 -5
- package/dist/compound-proof-B-9mXxFW.mjs +989 -0
- package/dist/{context-Zii1f5xS.d.ts → context-7NyiqIcg.d.ts} +1600 -25
- package/dist/{context-BJXxPElm.mjs → context-8Tu9kea0.mjs} +21 -63
- package/dist/{context-lxX6I7as.d.cts → context-tsYPJ2Ew.d.cts} +1600 -25
- package/dist/{deno-BHzKn9Ig.mjs → deno-oR5aJKpU.mjs} +1 -1
- package/dist/dist-DJQM4u7i.mjs +189 -0
- package/dist/docloader-DBoYbvYW.mjs +87 -0
- package/dist/{esm-yYQfPhIY.mjs → esm-BHXXx4Wx.mjs} +13 -6
- package/dist/federation/audience.test.d.mts +2 -0
- package/dist/federation/audience.test.mjs +130 -0
- package/dist/federation/builder.test.mjs +54 -5
- package/dist/federation/circuit-breaker.test.mjs +838 -7
- package/dist/federation/collection.test.mjs +3 -3
- package/dist/federation/compound-fanout.test.d.mts +2 -0
- package/dist/federation/compound-fanout.test.mjs +228 -0
- package/dist/federation/handler-compound-proof.test.d.mts +2 -0
- package/dist/federation/handler-compound-proof.test.mjs +384 -0
- package/dist/federation/handler.test.mjs +385 -15
- package/dist/federation/hashlink-media.test.d.mts +2 -0
- package/dist/federation/hashlink-media.test.mjs +422 -0
- package/dist/federation/idempotency.test.mjs +6 -6
- package/dist/federation/inbox-report.test.d.mts +2 -0
- package/dist/federation/inbox-report.test.mjs +475 -0
- package/dist/federation/inbox.test.mjs +3 -3
- package/dist/federation/keycache.test.mjs +215 -14
- package/dist/federation/kv.test.mjs +3 -3
- package/dist/federation/metrics.test.mjs +5 -5
- package/dist/federation/middleware.test.mjs +967 -35
- package/dist/federation/mod.cjs +18 -261
- package/dist/federation/mod.d.cts +5 -5
- package/dist/federation/mod.d.ts +5 -5
- package/dist/federation/mod.js +279 -1
- package/dist/federation/mq.test.mjs +123 -47
- package/dist/federation/negotiation.test.mjs +4 -4
- package/dist/federation/outgoing-proof.test.d.mts +2 -0
- package/dist/federation/outgoing-proof.test.mjs +931 -0
- package/dist/federation/portable-actor.test.d.mts +2 -0
- package/dist/federation/portable-actor.test.mjs +496 -0
- package/dist/federation/portable-chain.test.d.mts +2 -0
- package/dist/federation/portable-chain.test.mjs +370 -0
- package/dist/federation/portable-collection.test.d.mts +2 -0
- package/dist/federation/portable-collection.test.mjs +832 -0
- package/dist/federation/portable-delivery.test.d.mts +2 -0
- package/dist/federation/portable-delivery.test.mjs +746 -0
- package/dist/federation/portable-gateway-keys.test.d.mts +2 -0
- package/dist/federation/portable-gateway-keys.test.mjs +686 -0
- package/dist/federation/portable-inbox.test.d.mts +2 -0
- package/dist/federation/portable-inbox.test.mjs +1535 -0
- package/dist/federation/portable-parse-uri.test.d.mts +2 -0
- package/dist/federation/portable-parse-uri.test.mjs +348 -0
- package/dist/federation/portable-route-activity.test.d.mts +2 -0
- package/dist/federation/portable-route-activity.test.mjs +259 -0
- package/dist/federation/portable.test.d.mts +2 -0
- package/dist/federation/portable.test.mjs +1072 -0
- package/dist/federation/retry.test.mjs +4 -3
- package/dist/federation/router.test.mjs +9 -159
- package/dist/federation/send.test.mjs +245 -27
- package/dist/federation/tasks/codec.test.mjs +4 -4
- package/dist/federation/tasks/enqueue.test.mjs +42 -4
- package/dist/federation/tasks/tasks.test.mjs +7 -6
- package/dist/federation/temporal.test.mjs +4 -4
- package/dist/federation/webfinger.test.mjs +275 -12
- package/dist/{middleware-FrSMAYEx.cjs → federation-CkZiAcWY.cjs} +8640 -3722
- package/dist/http-B5JT115n.cjs +10630 -0
- package/dist/http-DydWCB7R.js +6527 -0
- package/dist/{http-CPVMUgsW.mjs → http-siciWkmx.mjs} +397 -168
- package/dist/key-BspPiXqn.mjs +951 -0
- package/dist/key-C1nv7_jD.d.ts +1125 -0
- package/dist/key-DYxuyd-s.d.cts +1125 -0
- package/dist/key-owner-evidence-APB6NpSk.mjs +108 -0
- package/dist/keycache-Da8A8mZa.mjs +200 -0
- package/dist/{kv-D6hNiMTK.d.ts → kv-B2mzK5tl.d.cts} +3 -2
- package/dist/{kv-gJ8LYbxX.d.cts → kv-B2mzK5tl.d.ts} +3 -2
- package/dist/kv-DBRWVx3Q.mjs +104 -0
- package/dist/{kv-cache-KyGqJqSp.mjs → kv-cache-BF1hnW65.mjs} +1 -1
- package/dist/{kv-cache-5bnX3-Jq.js → kv-cache-CTQr1tbn.js} +54 -16
- package/dist/{kv-cache-DQ29C4XL.cjs → kv-cache-DCac1nEU.cjs} +55 -19
- package/dist/{ld-Cj9F_5AP.mjs → ld-CzaP-hJl.mjs} +44 -12
- package/dist/map-local-context-conflict-DOYEEJ3u.mjs +204 -0
- package/dist/map-local-create-note-B0SeXJms.mjs +214 -0
- package/dist/{metrics-DMk-nTxI.mjs → metrics-CYg9yxBL.mjs} +3 -2
- package/dist/metrics-DVKYY6K1.d.cts +36 -0
- package/dist/metrics-DoRtI0qU.d.ts +36 -0
- package/dist/{middleware-Y-kpsp6S.js → middleware-CsDDXKuL.js} +5493 -1103
- package/dist/{middleware-CvwTUgnP.mjs → middleware-D7_LO-Vs.mjs} +3157 -272
- package/dist/{middleware-CEctxBN6.mjs → middleware-DWjrfpcz.mjs} +1 -1
- package/dist/{mod-gOlPDuT8.d.cts → mod-BojP2Bw3.d.cts} +1 -1
- package/dist/mod-CavhAgLw.d.cts +320 -0
- package/dist/mod-CtpF2BU5.d.ts +320 -0
- package/dist/{mod-BtEaIEcD.d.cts → mod-DEZXwV6J.d.ts} +10 -4
- package/dist/{mod-UMmveiO0.d.ts → mod-Dw-nPxQh.d.cts} +10 -4
- package/dist/{mod-DLc2Ot6f.d.ts → mod-Xj5P4s4d.d.ts} +1 -1
- package/dist/mod.cjs +40 -39
- package/dist/mod.d.cts +10 -11
- package/dist/mod.d.ts +10 -11
- package/dist/mod.js +9 -9
- package/dist/{mq-llHKivST.mjs → mq-Dm1Wmtm-.mjs} +6 -0
- package/dist/nodeinfo/client.test.mjs +139 -4
- package/dist/nodeinfo/handler.test.mjs +5 -5
- package/dist/nodeinfo/mod.cjs +1 -1
- package/dist/nodeinfo/mod.d.cts +1 -1
- package/dist/nodeinfo/mod.d.ts +1 -1
- package/dist/nodeinfo/mod.js +1 -1
- package/dist/nodeinfo/types.test.mjs +4 -4
- package/dist/otel/exporter.test.mjs +3 -3
- package/dist/otel/mod.cjs +0 -1
- package/dist/otel/mod.d.cts +1 -1
- package/dist/otel/mod.d.ts +1 -1
- package/dist/{outgoing-jsonld-URuqsICz.mjs → outgoing-jsonld-4IJqPYos.mjs} +12 -10
- package/dist/{owner-DEvZuyOE.d.cts → owner-BF0L9cMt.d.cts} +11 -0
- package/dist/{owner-CnngXDNJ.d.ts → owner-BSzr5-I8.d.ts} +11 -0
- package/dist/owner-DYgfTFHy.mjs +267 -0
- package/dist/portable-collection-BpsVsRov.js +670 -0
- package/dist/portable-collection-C74P1vtC.mjs +418 -0
- package/dist/portable-collection-DZd2bm7M.cjs +686 -0
- package/dist/portable-key-BIMLrAP4.mjs +549 -0
- package/dist/portable-key-id-Ca30-hOa.mjs +248 -0
- package/dist/proof-Dxy6ByqI.mjs +1299 -0
- package/dist/{public-audience-Cr5MsnMq.mjs → public-audience--klBIOk1.mjs} +15 -10
- package/dist/router-u5O5KCQr.mjs +156 -0
- package/dist/runtime/mod.cjs +1 -1
- package/dist/runtime/mod.js +1 -1
- package/dist/secured-document-CxzZZ9nL.mjs +35 -0
- package/dist/send-C4dDpAxm.mjs +495 -0
- package/dist/sig/accept.test.mjs +1 -1
- package/dist/sig/compound-proof-discovery.test.d.mts +2 -0
- package/dist/sig/compound-proof-discovery.test.mjs +331 -0
- package/dist/sig/compound-proof-producer.test.d.mts +2 -0
- package/dist/sig/compound-proof-producer.test.mjs +107 -0
- package/dist/sig/compound-proof-verification.test.d.mts +2 -0
- package/dist/sig/compound-proof-verification.test.mjs +833 -0
- package/dist/sig/compound-proof.test.d.mts +2 -0
- package/dist/sig/compound-proof.test.mjs +411 -0
- package/dist/sig/http.test.mjs +702 -31
- package/dist/sig/key-owner-evidence.test.d.mts +2 -0
- package/dist/sig/key-owner-evidence.test.mjs +100 -0
- package/dist/sig/key.test.mjs +220 -8
- package/dist/sig/ld.test.mjs +65 -7
- package/dist/sig/mitra-receive-interop.test.d.mts +2 -0
- package/dist/sig/mitra-receive-interop.test.mjs +260 -0
- package/dist/sig/mitra-send-interop.test.d.mts +2 -0
- package/dist/sig/mitra-send-interop.test.mjs +461 -0
- package/dist/sig/mod.cjs +18 -16
- package/dist/sig/mod.d.cts +4 -4
- package/dist/sig/mod.d.ts +4 -4
- package/dist/sig/mod.js +3 -3
- package/dist/sig/owner.test.mjs +108 -9
- package/dist/sig/portable-collection.test.d.mts +2 -0
- package/dist/sig/portable-collection.test.mjs +697 -0
- package/dist/sig/portable-interop.test.d.mts +2 -0
- package/dist/sig/portable-interop.test.mjs +156 -0
- package/dist/sig/portable-key-id.test.d.mts +2 -0
- package/dist/sig/portable-key-id.test.mjs +67 -0
- package/dist/sig/portable-key.test.d.mts +2 -0
- package/dist/sig/portable-key.test.mjs +1439 -0
- package/dist/sig/proof.test.mjs +1753 -16
- package/dist/sig/tootik-request-interop.test.d.mts +2 -0
- package/dist/sig/tootik-request-interop.test.mjs +190 -0
- package/dist/sig/tootik-send-interop.test.d.mts +2 -0
- package/dist/sig/tootik-send-interop.test.mjs +331 -0
- package/dist/sig/verification.test.d.mts +2 -0
- package/dist/sig/verification.test.mjs +310 -0
- package/dist/std__assert-DvMDRPes.mjs +8 -0
- package/dist/{testing-CO7ftO8A.mjs → tasks-IG3pHfvZ.mjs} +2 -2
- package/dist/{temporal-fsXO5IvD.mjs → temporal-D0KJI4NF.mjs} +3 -3
- package/dist/testing/context.test.d.mts +2 -0
- package/dist/testing/context.test.mjs +19 -0
- package/dist/testing/kv.test.d.mts +2 -0
- package/dist/testing/kv.test.mjs +101 -0
- package/dist/testing/mod.d.mts +1617 -48
- package/dist/testing/mod.mjs +6 -3
- package/dist/testing-t8xDeJZ5.mjs +8 -0
- package/dist/{transformers-DGwl-hD1.cjs → transformers-BalCrSsr.cjs} +0 -1
- package/dist/{types-DC1pNgJA.js → types-_S2_MeLF.js} +144 -8
- package/dist/{types-CPym83zc.cjs → types-poIao40f.cjs} +160 -7
- package/dist/utils/docloader.test.mjs +530 -8
- package/dist/utils/kv-cache.test.mjs +2 -2
- package/dist/utils/mod.cjs +1 -1
- package/dist/utils/mod.d.cts +1 -1
- package/dist/utils/mod.d.ts +1 -1
- package/dist/utils/mod.js +1 -1
- package/dist/verification-C3954hQh.mjs +5 -0
- package/dist/verification-Dpfcw8ml.mjs +105 -0
- package/dist/vocab/cjs.test.mjs +1 -1
- package/package.json +8 -8
- package/dist/assert_rejects-EgstcQy5.mjs +0 -82
- package/dist/docloader-e1khNUKy.mjs +0 -49
- package/dist/http-CsmQ-BQ4.js +0 -2483
- package/dist/http-VyDTd4G3.d.cts +0 -553
- package/dist/http-hlIjmewS.cjs +0 -2694
- package/dist/http-lf8Hsd91.d.ts +0 -553
- package/dist/key-DfPsX8hv.mjs +0 -453
- package/dist/keycache-_DHqz5z_.mjs +0 -97
- package/dist/metrics-CoUJLQhZ.d.cts +0 -276
- package/dist/metrics-Dc4jR-IC.d.ts +0 -276
- package/dist/mod-B0hW12_O.d.cts +0 -307
- package/dist/mod-COIAjwRS.d.ts +0 -307
- package/dist/owner-Br9YYzn_.mjs +0 -120
- package/dist/proof-B4ngoaNQ.mjs +0 -316
- package/dist/proof-B6E0XUXJ.js +0 -1415
- package/dist/proof-NN4xCQGL.cjs +0 -1562
- package/dist/send-CxHKUNzv.mjs +0 -239
- package/dist/std__assert-B2NnY9bq.mjs +0 -79
- package/dist/temporal-polyfill-BCdTHexd.cjs +0 -3565
- /package/dist/{accept-8PiolDP7.mjs → accept-CPVqki78.mjs} +0 -0
- /package/dist/{activity-listener-BtTSxZWA.mjs → activity-listener-C-BSbKI7.mjs} +0 -0
- /package/dist/{client-CAM_bQXx.d.cts → client-D-XP_Ccb.d.cts} +0 -0
- /package/dist/{client-CSddvgWN.d.ts → client-D-XP_Ccb.d.ts} +0 -0
- /package/dist/{keys-Dbyd7bhl.mjs → keys-DIJNHW-Y.mjs} +0 -0
- /package/dist/{kv-CXBAok3k.mjs → kv-DCHkaJ9-.mjs} +0 -0
- /package/dist/{negotiation-RuwrPFqi.mjs → negotiation-CCocWT_N.mjs} +0 -0
- /package/dist/{retry-DgFqeS5k.mjs → retry-zX5xw5e0.mjs} +0 -0
- /package/dist/{chunk-D5IdezsZ.js → rolldown-runtime-BC-Eo5Mj.js} +0 -0
- /package/dist/{chunk-Czpeue4M.cjs → rolldown-runtime-Dmm3t92W.cjs} +0 -0
- /package/dist/{transformers-DXze6FUB.js → transformers-DAtXl_Kc.js} +0 -0
- /package/dist/{types-CrtdVuDP.mjs → types-DE_LV1JB.mjs} +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import "temporal-polyfill";
|
|
2
2
|
import "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
|
-
import { t as assertEquals } from "../assert_equals-
|
|
5
|
-
import "../std__assert-
|
|
6
|
-
import { n as digest, t as buildCollectionSynchronizationHeader } from "../collection-
|
|
4
|
+
import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
|
|
5
|
+
import "../std__assert-DvMDRPes.mjs";
|
|
6
|
+
import { n as digest, t as buildCollectionSynchronizationHeader } from "../collection-CnnP1UAv.mjs";
|
|
7
7
|
import { test } from "@fedify/fixture";
|
|
8
8
|
import { decodeHex } from "byte-encodings/hex";
|
|
9
9
|
//#region src/federation/collection.test.ts
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { Temporal } from "temporal-polyfill";
|
|
2
|
+
import "urlpattern-polyfill";
|
|
3
|
+
globalThis.addEventListener = () => {};
|
|
4
|
+
import { t as esm_default } from "../esm-BHXXx4Wx.mjs";
|
|
5
|
+
import { t as MemoryKvStore } from "../kv-DCHkaJ9-.mjs";
|
|
6
|
+
import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
|
|
7
|
+
import "../std__assert-DvMDRPes.mjs";
|
|
8
|
+
import { t as assert } from "../assert-D1Eafmd_.mjs";
|
|
9
|
+
import { n as exportJwk } from "../key-BspPiXqn.mjs";
|
|
10
|
+
import { r as normalizeOutgoingActivityJsonLd } from "../outgoing-jsonld-4IJqPYos.mjs";
|
|
11
|
+
import { a as signObject, d as verifyProof } from "../proof-Dxy6ByqI.mjs";
|
|
12
|
+
import { i as rsaPrivateKey2, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2 } from "../keys-DIJNHW-Y.mjs";
|
|
13
|
+
import { n as FederationImpl } from "../middleware-D7_LO-Vs.mjs";
|
|
14
|
+
import { Create, DataIntegrityProof, Note } from "@fedify/vocab";
|
|
15
|
+
import { exportDidKey, parseIri } from "@fedify/vocab-runtime";
|
|
16
|
+
import { mockDocumentLoader, test } from "@fedify/fixture";
|
|
17
|
+
import serialize from "json-canon";
|
|
18
|
+
//#region src/federation/compound-fanout.test.ts
|
|
19
|
+
const options = {
|
|
20
|
+
contextLoader: mockDocumentLoader,
|
|
21
|
+
documentLoader: mockDocumentLoader
|
|
22
|
+
};
|
|
23
|
+
const context = ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/data-integrity/v1"];
|
|
24
|
+
const childContext = [...context, { ex: "https://example.com/ns#" }];
|
|
25
|
+
async function parseProof(document) {
|
|
26
|
+
const proof = document.proof;
|
|
27
|
+
return await DataIntegrityProof.fromJsonLd({
|
|
28
|
+
"@context": document["@context"],
|
|
29
|
+
...proof
|
|
30
|
+
}, options);
|
|
31
|
+
}
|
|
32
|
+
test("fanout delivery keeps an embedded signed child intact", async () => {
|
|
33
|
+
const created = Temporal.Instant.from("2023-02-24T23:36:38Z");
|
|
34
|
+
const childDid = await exportDidKey(ed25519PublicKey.publicKey);
|
|
35
|
+
const childKeyId = new URL(`${childDid}#${childDid.substring(8)}`);
|
|
36
|
+
const child = await signObject(new Note({
|
|
37
|
+
id: parseIri(`ap+ef61://${childDid}/objects/fanned-out`),
|
|
38
|
+
attribution: parseIri(`ap+ef61://${childDid}/actor`),
|
|
39
|
+
content: "A portable note carried through the fanout queue"
|
|
40
|
+
}), ed25519PrivateKey, childKeyId, {
|
|
41
|
+
...options,
|
|
42
|
+
context: childContext,
|
|
43
|
+
created
|
|
44
|
+
});
|
|
45
|
+
const compound = await normalizeOutgoingActivityJsonLd(await (await signObject(new Create({
|
|
46
|
+
id: new URL("https://example.com/activities/fanned-out"),
|
|
47
|
+
actor: new URL("https://example.com/person"),
|
|
48
|
+
object: child
|
|
49
|
+
}), ed25519PrivateKey, childKeyId, {
|
|
50
|
+
...options,
|
|
51
|
+
context,
|
|
52
|
+
created
|
|
53
|
+
})).toJsonLd({
|
|
54
|
+
format: "compact",
|
|
55
|
+
...options,
|
|
56
|
+
context
|
|
57
|
+
}), mockDocumentLoader, { preserveNestedSecuredDocuments: true });
|
|
58
|
+
const securedChild = compound.object;
|
|
59
|
+
assertEquals(securedChild["@context"], childContext);
|
|
60
|
+
esm_default.spyGlobal();
|
|
61
|
+
try {
|
|
62
|
+
let delivered = null;
|
|
63
|
+
esm_default.post("https://example.com/inbox", async (cl) => {
|
|
64
|
+
delivered = await cl.request.json();
|
|
65
|
+
return new Response(null, { status: 202 });
|
|
66
|
+
});
|
|
67
|
+
const federation = new FederationImpl({
|
|
68
|
+
kv: new MemoryKvStore(),
|
|
69
|
+
contextLoaderFactory: () => mockDocumentLoader
|
|
70
|
+
});
|
|
71
|
+
federation.setInboxListeners("/users/{identifier}/inbox", "/inbox");
|
|
72
|
+
const message = {
|
|
73
|
+
type: "fanout",
|
|
74
|
+
id: crypto.randomUUID(),
|
|
75
|
+
baseUrl: "https://example.com",
|
|
76
|
+
keys: [{
|
|
77
|
+
keyId: rsaPublicKey2.id.href,
|
|
78
|
+
privateKey: await exportJwk(rsaPrivateKey2)
|
|
79
|
+
}],
|
|
80
|
+
inboxes: { "https://example.com/inbox": {
|
|
81
|
+
actorIds: ["https://example.com/recipient"],
|
|
82
|
+
sharedInbox: false
|
|
83
|
+
} },
|
|
84
|
+
activity: compound,
|
|
85
|
+
activityId: "https://example.com/activities/fanned-out",
|
|
86
|
+
activityType: "https://www.w3.org/ns/activitystreams#Create",
|
|
87
|
+
normalizeExistingProofs: true,
|
|
88
|
+
traceContext: {}
|
|
89
|
+
};
|
|
90
|
+
await federation.processQueuedTask(void 0, message);
|
|
91
|
+
assert(delivered != null);
|
|
92
|
+
const body = delivered;
|
|
93
|
+
delete body.signature;
|
|
94
|
+
assertEquals(serialize(body), serialize(compound));
|
|
95
|
+
const embedded = body.object;
|
|
96
|
+
assertEquals((await verifyProof(embedded, await parseProof(embedded), options))?.id, childKeyId);
|
|
97
|
+
assertEquals((await verifyProof(body, await parseProof(body), options))?.id, childKeyId);
|
|
98
|
+
} finally {
|
|
99
|
+
esm_default.hardReset();
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
test("forced fanout signs before serializing so the child survives", async () => {
|
|
103
|
+
const created = Temporal.Instant.from("2023-02-24T23:36:38Z");
|
|
104
|
+
const childDid = await exportDidKey(ed25519PublicKey.publicKey);
|
|
105
|
+
const childKeyId = new URL(`${childDid}#${childDid.substring(8)}`);
|
|
106
|
+
const child = await signObject(new Note({
|
|
107
|
+
id: parseIri(`ap+ef61://${childDid}/objects/forced-fanout`),
|
|
108
|
+
attribution: parseIri(`ap+ef61://${childDid}/actor`),
|
|
109
|
+
content: "A portable note sent through a forced fanout"
|
|
110
|
+
}), ed25519PrivateKey, childKeyId, {
|
|
111
|
+
...options,
|
|
112
|
+
context: childContext,
|
|
113
|
+
created
|
|
114
|
+
});
|
|
115
|
+
const activity = new Create({
|
|
116
|
+
id: new URL("https://example.com/activities/forced-fanout"),
|
|
117
|
+
actor: new URL("https://example.com/person"),
|
|
118
|
+
object: child
|
|
119
|
+
});
|
|
120
|
+
esm_default.spyGlobal();
|
|
121
|
+
try {
|
|
122
|
+
let delivered = null;
|
|
123
|
+
esm_default.post("https://example.com/inbox", async (cl) => {
|
|
124
|
+
delivered = await cl.request.json();
|
|
125
|
+
return new Response(null, { status: 202 });
|
|
126
|
+
});
|
|
127
|
+
const queued = [];
|
|
128
|
+
const federation = new FederationImpl({
|
|
129
|
+
kv: new MemoryKvStore(),
|
|
130
|
+
queue: {
|
|
131
|
+
enqueue(message) {
|
|
132
|
+
queued.push(message);
|
|
133
|
+
return Promise.resolve();
|
|
134
|
+
},
|
|
135
|
+
listen() {
|
|
136
|
+
return Promise.resolve();
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
manuallyStartQueue: true,
|
|
140
|
+
contextLoaderFactory: () => mockDocumentLoader
|
|
141
|
+
});
|
|
142
|
+
federation.setInboxListeners("/users/{identifier}/inbox", "/inbox");
|
|
143
|
+
await federation.createContext(new URL("https://example.com/")).sendActivity({
|
|
144
|
+
keyId: childKeyId,
|
|
145
|
+
privateKey: ed25519PrivateKey
|
|
146
|
+
}, {
|
|
147
|
+
id: new URL("https://example.com/users/bob"),
|
|
148
|
+
inboxId: new URL("https://example.com/inbox")
|
|
149
|
+
}, activity, { fanout: "force" });
|
|
150
|
+
assertEquals(queued.length, 1);
|
|
151
|
+
for (let i = 0; i < queued.length; i++) await federation.processQueuedTask(void 0, queued[i]);
|
|
152
|
+
assert(delivered != null);
|
|
153
|
+
const body = delivered;
|
|
154
|
+
delete body.signature;
|
|
155
|
+
const embedded = body.object;
|
|
156
|
+
assertEquals(embedded["@context"], childContext);
|
|
157
|
+
assertEquals((await verifyProof(embedded, await parseProof(embedded), options))?.id, childKeyId);
|
|
158
|
+
assertEquals((await verifyProof(body, await parseProof(body), options))?.id, childKeyId);
|
|
159
|
+
} finally {
|
|
160
|
+
esm_default.hardReset();
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
test("an already signed activity keeps its single proof through fanout", async () => {
|
|
164
|
+
const created = Temporal.Instant.from("2023-02-24T23:36:38Z");
|
|
165
|
+
const childDid = await exportDidKey(ed25519PublicKey.publicKey);
|
|
166
|
+
const childKeyId = new URL(`${childDid}#${childDid.substring(8)}`);
|
|
167
|
+
const child = await signObject(new Note({
|
|
168
|
+
id: parseIri(`ap+ef61://${childDid}/objects/pre-signed`),
|
|
169
|
+
attribution: parseIri(`ap+ef61://${childDid}/actor`),
|
|
170
|
+
content: "A portable note in a pre-signed activity"
|
|
171
|
+
}), ed25519PrivateKey, childKeyId, {
|
|
172
|
+
...options,
|
|
173
|
+
context: childContext,
|
|
174
|
+
created
|
|
175
|
+
});
|
|
176
|
+
const activity = await signObject(new Create({
|
|
177
|
+
id: new URL("https://example.com/activities/pre-signed"),
|
|
178
|
+
actor: new URL("https://example.com/person"),
|
|
179
|
+
object: child
|
|
180
|
+
}), ed25519PrivateKey, childKeyId, {
|
|
181
|
+
...options,
|
|
182
|
+
created
|
|
183
|
+
});
|
|
184
|
+
esm_default.spyGlobal();
|
|
185
|
+
try {
|
|
186
|
+
let delivered = null;
|
|
187
|
+
esm_default.post("https://example.com/inbox", async (cl) => {
|
|
188
|
+
delivered = await cl.request.json();
|
|
189
|
+
return new Response(null, { status: 202 });
|
|
190
|
+
});
|
|
191
|
+
const queued = [];
|
|
192
|
+
const federation = new FederationImpl({
|
|
193
|
+
kv: new MemoryKvStore(),
|
|
194
|
+
queue: {
|
|
195
|
+
enqueue(message) {
|
|
196
|
+
queued.push(message);
|
|
197
|
+
return Promise.resolve();
|
|
198
|
+
},
|
|
199
|
+
listen() {
|
|
200
|
+
return Promise.resolve();
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
manuallyStartQueue: true,
|
|
204
|
+
contextLoaderFactory: () => mockDocumentLoader
|
|
205
|
+
});
|
|
206
|
+
federation.setInboxListeners("/users/{identifier}/inbox", "/inbox");
|
|
207
|
+
await federation.createContext(new URL("https://example.com/")).sendActivity({
|
|
208
|
+
keyId: childKeyId,
|
|
209
|
+
privateKey: ed25519PrivateKey
|
|
210
|
+
}, {
|
|
211
|
+
id: new URL("https://example.com/users/bob"),
|
|
212
|
+
inboxId: new URL("https://example.com/inbox")
|
|
213
|
+
}, activity, { fanout: "force" });
|
|
214
|
+
for (let i = 0; i < queued.length; i++) await federation.processQueuedTask(void 0, queued[i]);
|
|
215
|
+
assert(delivered != null);
|
|
216
|
+
const body = delivered;
|
|
217
|
+
delete body.signature;
|
|
218
|
+
assert(!Array.isArray(body.proof));
|
|
219
|
+
assertEquals((await verifyProof(body, await parseProof(body), options))?.id, childKeyId);
|
|
220
|
+
const embedded = body.object;
|
|
221
|
+
assertEquals(embedded["@context"], childContext);
|
|
222
|
+
assertEquals((await verifyProof(embedded, await parseProof(embedded), options))?.id, childKeyId);
|
|
223
|
+
} finally {
|
|
224
|
+
esm_default.hardReset();
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
//#endregion
|
|
228
|
+
export {};
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
import "temporal-polyfill";
|
|
2
|
+
import "urlpattern-polyfill";
|
|
3
|
+
globalThis.addEventListener = () => {};
|
|
4
|
+
import { r as createRequestContext, t as createInboxContext } from "../context-8Tu9kea0.mjs";
|
|
5
|
+
import { t as MemoryKvStore } from "../kv-DCHkaJ9-.mjs";
|
|
6
|
+
import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
|
|
7
|
+
import "../std__assert-DvMDRPes.mjs";
|
|
8
|
+
import { a as compactJsonLd, m as verifyCompactJsonLd, p as signJsonLd } from "../ld-CzaP-hJl.mjs";
|
|
9
|
+
import { l as signRequest } from "../http-siciWkmx.mjs";
|
|
10
|
+
import { a as rsaPrivateKey3, c as rsaPublicKey3, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2 } from "../keys-DIJNHW-Y.mjs";
|
|
11
|
+
import { f as handleInbox, o as createFederation } from "../middleware-D7_LO-Vs.mjs";
|
|
12
|
+
import { t as ActivityListenerSet } from "../activity-listener-C-BSbKI7.mjs";
|
|
13
|
+
import { t as map_local_create_note_default } from "../map-local-create-note-B0SeXJms.mjs";
|
|
14
|
+
import { Create, CryptographicKey, Multikey, Person, Update } from "@fedify/vocab";
|
|
15
|
+
import { encodeMultibase, exportDidKey, parseIri } from "@fedify/vocab-runtime";
|
|
16
|
+
import { mockDocumentLoader, test } from "@fedify/fixture";
|
|
17
|
+
import serialize from "json-canon";
|
|
18
|
+
//#region src/federation/handler-compound-proof.test.ts
|
|
19
|
+
async function sha256(value) {
|
|
20
|
+
return new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value)));
|
|
21
|
+
}
|
|
22
|
+
async function secureDocument(unsecuredDocument, privateJwk, verificationMethod) {
|
|
23
|
+
const proofConfiguration = {
|
|
24
|
+
"@context": unsecuredDocument["@context"],
|
|
25
|
+
type: "DataIntegrityProof",
|
|
26
|
+
cryptosuite: "eddsa-jcs-2022",
|
|
27
|
+
verificationMethod,
|
|
28
|
+
proofPurpose: "assertionMethod",
|
|
29
|
+
created: "2023-02-24T23:36:38Z"
|
|
30
|
+
};
|
|
31
|
+
const proofHash = await sha256(serialize(proofConfiguration));
|
|
32
|
+
const documentHash = await sha256(serialize(unsecuredDocument));
|
|
33
|
+
const input = /* @__PURE__ */ new Uint8Array(64);
|
|
34
|
+
input.set(proofHash);
|
|
35
|
+
input.set(documentHash, proofHash.length);
|
|
36
|
+
const privateKey = await crypto.subtle.importKey("jwk", privateJwk, "Ed25519", false, ["sign"]);
|
|
37
|
+
const signature = new Uint8Array(await crypto.subtle.sign("Ed25519", privateKey, input));
|
|
38
|
+
return {
|
|
39
|
+
...unsecuredDocument,
|
|
40
|
+
proof: {
|
|
41
|
+
...proofConfiguration,
|
|
42
|
+
proofValue: new TextDecoder().decode(encodeMultibase("base58btc", signature))
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function requestFor(body) {
|
|
47
|
+
return new Request("https://example.com/inbox", {
|
|
48
|
+
method: "POST",
|
|
49
|
+
body: JSON.stringify(body)
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
async function handle(requestOrBody, dispatched) {
|
|
53
|
+
const request = requestOrBody instanceof Request ? requestOrBody : requestFor(requestOrBody);
|
|
54
|
+
const context = createRequestContext({
|
|
55
|
+
federation: createFederation({ kv: new MemoryKvStore() }),
|
|
56
|
+
request,
|
|
57
|
+
url: new URL(request.url),
|
|
58
|
+
data: void 0,
|
|
59
|
+
documentLoader: mockDocumentLoader,
|
|
60
|
+
contextLoader: mockDocumentLoader
|
|
61
|
+
});
|
|
62
|
+
const inboxListeners = new ActivityListenerSet();
|
|
63
|
+
const count = () => {
|
|
64
|
+
if (dispatched != null) dispatched.count++;
|
|
65
|
+
};
|
|
66
|
+
inboxListeners.add(Create, count);
|
|
67
|
+
inboxListeners.add(Update, count);
|
|
68
|
+
return await handleInbox(request, {
|
|
69
|
+
recipient: null,
|
|
70
|
+
context,
|
|
71
|
+
inboxContextFactory() {
|
|
72
|
+
return createInboxContext({
|
|
73
|
+
...context,
|
|
74
|
+
clone: void 0
|
|
75
|
+
});
|
|
76
|
+
},
|
|
77
|
+
kv: new MemoryKvStore(),
|
|
78
|
+
kvPrefixes: {
|
|
79
|
+
activityIdempotence: ["_fedify", "activityIdempotence"],
|
|
80
|
+
publicKey: ["_fedify", "publicKey"],
|
|
81
|
+
acceptSignatureNonce: ["_fedify", "acceptSignatureNonce"]
|
|
82
|
+
},
|
|
83
|
+
actorDispatcher() {
|
|
84
|
+
return null;
|
|
85
|
+
},
|
|
86
|
+
inboxListeners,
|
|
87
|
+
onNotFound() {
|
|
88
|
+
return new Response("Not found", { status: 404 });
|
|
89
|
+
},
|
|
90
|
+
signatureTimeWindow: { minutes: 5 },
|
|
91
|
+
skipSignatureVerification: false
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
test("handleInbox() enforces portable compound proofs atomically", async () => {
|
|
95
|
+
const valid = structuredClone(map_local_create_note_default.documents.finalSecuredCompound);
|
|
96
|
+
assertEquals((await handle(valid)).status, 202);
|
|
97
|
+
const unsignedInner = structuredClone(map_local_create_note_default.documents.outerUnsecuredDocument);
|
|
98
|
+
delete unsignedInner.object.proof;
|
|
99
|
+
const rejected = await handle(await secureDocument(unsignedInner, map_local_create_note_default.keys.outer.testPrivateKeyJwk, map_local_create_note_default.keys.outer.verificationMethod));
|
|
100
|
+
assertEquals([rejected.status, await rejected.text()], [401, "Failed to verify compound portable Object Integrity Proofs."]);
|
|
101
|
+
const tamperedInner = structuredClone(map_local_create_note_default.documents.outerUnsecuredDocument);
|
|
102
|
+
tamperedInner.object.content = "Tampered";
|
|
103
|
+
assertEquals((await handle(await secureDocument(tamperedInner, map_local_create_note_default.keys.outer.testPrivateKeyJwk, map_local_create_note_default.keys.outer.verificationMethod))).status, 401);
|
|
104
|
+
const changedAfterSigning = structuredClone(valid);
|
|
105
|
+
changedAfterSigning.summary = "Added after signing";
|
|
106
|
+
assertEquals((await handle(changedAfterSigning)).status, 401);
|
|
107
|
+
});
|
|
108
|
+
test("handleInbox() enforces compounds after alternate outer auth", async () => {
|
|
109
|
+
const createBody = (object) => ({
|
|
110
|
+
"@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/data-integrity/v1"],
|
|
111
|
+
id: "https://example.com/activities/compound",
|
|
112
|
+
type: "Create",
|
|
113
|
+
actor: rsaPublicKey3.ownerId.href,
|
|
114
|
+
object
|
|
115
|
+
});
|
|
116
|
+
const validInner = structuredClone(map_local_create_note_default.documents.securedInner);
|
|
117
|
+
const unsignedInner = structuredClone(validInner);
|
|
118
|
+
delete unsignedInner.proof;
|
|
119
|
+
for (const authentication of ["http", "ld"]) {
|
|
120
|
+
const sign = async (body) => {
|
|
121
|
+
if (authentication === "http") return await signRequest(requestFor(body), rsaPrivateKey3, rsaPublicKey3.id);
|
|
122
|
+
return requestFor(await signJsonLd(body, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: mockDocumentLoader }));
|
|
123
|
+
};
|
|
124
|
+
const acceptedDispatch = { count: 0 };
|
|
125
|
+
assertEquals([(await handle(await sign(createBody(structuredClone(validInner))), acceptedDispatch)).status, acceptedDispatch.count], [202, 1]);
|
|
126
|
+
const rejectedDispatch = { count: 0 };
|
|
127
|
+
assertEquals([(await handle(await sign(createBody(structuredClone(unsignedInner))), rejectedDispatch)).status, rejectedDispatch.count], [401, 0]);
|
|
128
|
+
}
|
|
129
|
+
const proofSetInner = structuredClone(validInner);
|
|
130
|
+
proofSetInner.proof = [proofSetInner.proof];
|
|
131
|
+
const unsupportedDispatch = { count: 0 };
|
|
132
|
+
assertEquals([(await handle(await signRequest(requestFor(createBody(proofSetInner)), rsaPrivateKey3, rsaPublicKey3.id), unsupportedDispatch)).status, unsupportedDispatch.count], [401, 0]);
|
|
133
|
+
});
|
|
134
|
+
test("handleInbox() preserves ordinary top-level proof behavior", async () => {
|
|
135
|
+
assertEquals((await handle(await secureDocument({
|
|
136
|
+
"@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/data-integrity/v1"],
|
|
137
|
+
id: "https://example.com/activities/ordinary",
|
|
138
|
+
type: "Create",
|
|
139
|
+
actor: map_local_create_note_default.keys.outer.controller,
|
|
140
|
+
object: {
|
|
141
|
+
id: "https://example.com/notes/ordinary",
|
|
142
|
+
type: "Note",
|
|
143
|
+
attributedTo: map_local_create_note_default.keys.outer.controller,
|
|
144
|
+
content: "An ordinary note"
|
|
145
|
+
}
|
|
146
|
+
}, map_local_create_note_default.keys.outer.testPrivateKeyJwk, map_local_create_note_default.keys.outer.verificationMethod))).status, 202);
|
|
147
|
+
});
|
|
148
|
+
async function signLd(document) {
|
|
149
|
+
return await signJsonLd(document, rsaPrivateKey3, rsaPublicKey3.id, { contextLoader: mockDocumentLoader });
|
|
150
|
+
}
|
|
151
|
+
async function isLdSignatureVerified(document) {
|
|
152
|
+
return await verifyCompactJsonLd(await compactJsonLd(document, mockDocumentLoader), {
|
|
153
|
+
contextLoader: mockDocumentLoader,
|
|
154
|
+
documentLoader: mockDocumentLoader
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
async function handleDispatched(body) {
|
|
158
|
+
const dispatched = { count: 0 };
|
|
159
|
+
const response = await handle(body, dispatched);
|
|
160
|
+
return [
|
|
161
|
+
response.status,
|
|
162
|
+
await response.text(),
|
|
163
|
+
dispatched.count
|
|
164
|
+
];
|
|
165
|
+
}
|
|
166
|
+
const COMPOUND_FAILURE = "Failed to verify compound portable Object Integrity Proofs.";
|
|
167
|
+
test("handleInbox() ignores a portable activity's Linked Data Signature", async () => {
|
|
168
|
+
const signed = await signLd(structuredClone(map_local_create_note_default.documents.finalSecuredCompound));
|
|
169
|
+
assertEquals(await isLdSignatureVerified(signed), false);
|
|
170
|
+
assertEquals(await handleDispatched(signed), [
|
|
171
|
+
202,
|
|
172
|
+
"",
|
|
173
|
+
1
|
|
174
|
+
]);
|
|
175
|
+
const bogus = structuredClone(map_local_create_note_default.documents.finalSecuredCompound);
|
|
176
|
+
bogus.signature = {
|
|
177
|
+
type: "RsaSignature2017",
|
|
178
|
+
creator: "https://example.com/keys/1",
|
|
179
|
+
created: "2023-02-24T23:36:38Z",
|
|
180
|
+
signatureValue: "not-a-signature"
|
|
181
|
+
};
|
|
182
|
+
assertEquals(await handleDispatched(bogus), [
|
|
183
|
+
202,
|
|
184
|
+
"",
|
|
185
|
+
1
|
|
186
|
+
]);
|
|
187
|
+
const tampered = structuredClone(map_local_create_note_default.documents.finalSecuredCompound);
|
|
188
|
+
tampered.object = {
|
|
189
|
+
...tampered.object,
|
|
190
|
+
content: "Tampered"
|
|
191
|
+
};
|
|
192
|
+
assertEquals((await handleDispatched(await signLd(tampered)))[0], 401);
|
|
193
|
+
const invalidInner = structuredClone(map_local_create_note_default.documents.outerUnsecuredDocument);
|
|
194
|
+
invalidInner.object.content = "Tampered";
|
|
195
|
+
assertEquals(await handleDispatched(await signLd(await secureDocument(invalidInner, map_local_create_note_default.keys.outer.testPrivateKeyJwk, map_local_create_note_default.keys.outer.verificationMethod))), [
|
|
196
|
+
401,
|
|
197
|
+
COMPOUND_FAILURE,
|
|
198
|
+
0
|
|
199
|
+
]);
|
|
200
|
+
});
|
|
201
|
+
test("handleInbox() verifies compounds with verified Linked Data Signatures", async () => {
|
|
202
|
+
const createBody = (object) => ({
|
|
203
|
+
"@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/data-integrity/v1"],
|
|
204
|
+
id: "https://example.com/activities/compound-ld",
|
|
205
|
+
type: "Create",
|
|
206
|
+
actor: rsaPublicKey3.ownerId.href,
|
|
207
|
+
object
|
|
208
|
+
});
|
|
209
|
+
const privateJwk = await crypto.subtle.exportKey("jwk", ed25519PrivateKey);
|
|
210
|
+
const sign = async (object) => await signLd(await secureDocument(createBody(object), privateJwk, ed25519PublicKey.id.href));
|
|
211
|
+
const valid = await sign(structuredClone(map_local_create_note_default.documents.securedInner));
|
|
212
|
+
assertEquals(await isLdSignatureVerified(valid), true);
|
|
213
|
+
assertEquals(await handleDispatched(valid), [
|
|
214
|
+
202,
|
|
215
|
+
"",
|
|
216
|
+
1
|
|
217
|
+
]);
|
|
218
|
+
const invalidInner = structuredClone(map_local_create_note_default.documents.securedInner);
|
|
219
|
+
invalidInner.content = "Tampered";
|
|
220
|
+
const invalid = await sign(invalidInner);
|
|
221
|
+
assertEquals(await isLdSignatureVerified(invalid), true);
|
|
222
|
+
assertEquals(await handleDispatched(invalid), [
|
|
223
|
+
401,
|
|
224
|
+
COMPOUND_FAILURE,
|
|
225
|
+
0
|
|
226
|
+
]);
|
|
227
|
+
});
|
|
228
|
+
test("handleInbox() keeps embedded signature properties in proof inputs", async () => {
|
|
229
|
+
const innerWithSignature = await secureDocument({
|
|
230
|
+
...map_local_create_note_default.documents.innerUnsecuredDocument,
|
|
231
|
+
"@context": ["https://w3id.org/security/v1", ...map_local_create_note_default.documents.innerUnsecuredDocument["@context"]],
|
|
232
|
+
signature: {
|
|
233
|
+
type: "https://example.com/ns#ExampleSignature",
|
|
234
|
+
signatureValue: "covered by the inner proof"
|
|
235
|
+
}
|
|
236
|
+
}, map_local_create_note_default.keys.inner.testPrivateKeyJwk, map_local_create_note_default.keys.inner.verificationMethod);
|
|
237
|
+
const sign = async (object) => await signLd(await secureDocument({
|
|
238
|
+
...map_local_create_note_default.documents.outerUnsecuredDocument,
|
|
239
|
+
object
|
|
240
|
+
}, map_local_create_note_default.keys.outer.testPrivateKeyJwk, map_local_create_note_default.keys.outer.verificationMethod));
|
|
241
|
+
assertEquals(await handleDispatched(await sign(structuredClone(innerWithSignature))), [
|
|
242
|
+
202,
|
|
243
|
+
"",
|
|
244
|
+
1
|
|
245
|
+
]);
|
|
246
|
+
const innerWithoutSignature = structuredClone(innerWithSignature);
|
|
247
|
+
delete innerWithoutSignature.signature;
|
|
248
|
+
assertEquals(await handleDispatched(await sign(innerWithoutSignature)), [
|
|
249
|
+
401,
|
|
250
|
+
COMPOUND_FAILURE,
|
|
251
|
+
0
|
|
252
|
+
]);
|
|
253
|
+
});
|
|
254
|
+
test("handleInbox() inspects compounds without the Linked Data Signature", async () => {
|
|
255
|
+
const activity = await secureDocument({
|
|
256
|
+
"@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/data-integrity/v1"],
|
|
257
|
+
id: "https://example.com/activities/ordinary-ld",
|
|
258
|
+
type: "Create",
|
|
259
|
+
actor: map_local_create_note_default.keys.outer.controller,
|
|
260
|
+
object: {
|
|
261
|
+
id: "https://example.com/notes/ordinary-ld",
|
|
262
|
+
type: "Note",
|
|
263
|
+
attributedTo: map_local_create_note_default.keys.outer.controller,
|
|
264
|
+
content: "An ordinary note"
|
|
265
|
+
}
|
|
266
|
+
}, map_local_create_note_default.keys.outer.testPrivateKeyJwk, map_local_create_note_default.keys.outer.verificationMethod);
|
|
267
|
+
activity.signature = {
|
|
268
|
+
id: map_local_create_note_default.documents.innerUnsecuredDocument.id,
|
|
269
|
+
type: "RsaSignature2017",
|
|
270
|
+
creator: "https://example.com/keys/1",
|
|
271
|
+
created: "2023-02-24T23:36:38Z",
|
|
272
|
+
signatureValue: "not-a-signature"
|
|
273
|
+
};
|
|
274
|
+
assertEquals(await handleDispatched(activity), [
|
|
275
|
+
202,
|
|
276
|
+
"",
|
|
277
|
+
1
|
|
278
|
+
]);
|
|
279
|
+
});
|
|
280
|
+
test("handleInbox() accepts portable actors with keys at ap: URIs", async () => {
|
|
281
|
+
const did = await exportDidKey(ed25519PublicKey.publicKey);
|
|
282
|
+
const verificationMethod = `${did}#${did.slice(8)}`;
|
|
283
|
+
const privateJwk = await crypto.subtle.exportKey("jwk", ed25519PrivateKey);
|
|
284
|
+
const actorId = `ap://${did}/actor`;
|
|
285
|
+
const inboxId = `https://gw.example/.well-known/apgateway/${did}/actor/inbox`;
|
|
286
|
+
const actor = await new Person({
|
|
287
|
+
id: parseIri(actorId),
|
|
288
|
+
inbox: new URL(inboxId),
|
|
289
|
+
gateways: [new URL("https://gw.example")],
|
|
290
|
+
publicKey: new CryptographicKey({
|
|
291
|
+
id: parseIri(`${actorId}#main-key`),
|
|
292
|
+
owner: parseIri(actorId),
|
|
293
|
+
publicKey: rsaPublicKey2.publicKey
|
|
294
|
+
}),
|
|
295
|
+
assertionMethods: [new Multikey({
|
|
296
|
+
id: parseIri(`${actorId}#ed25519-key`),
|
|
297
|
+
controller: parseIri(actorId),
|
|
298
|
+
publicKey: ed25519PublicKey.publicKey
|
|
299
|
+
})]
|
|
300
|
+
}).toJsonLd({
|
|
301
|
+
format: "compact",
|
|
302
|
+
contextLoader: mockDocumentLoader
|
|
303
|
+
});
|
|
304
|
+
const update = async (keyBase) => {
|
|
305
|
+
const [multikey] = actor.assertionMethod;
|
|
306
|
+
return await secureDocument({
|
|
307
|
+
"@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/data-integrity/v1"],
|
|
308
|
+
id: `ap://${did}/activities/update`,
|
|
309
|
+
type: "Update",
|
|
310
|
+
actor: actorId,
|
|
311
|
+
object: await secureDocument({
|
|
312
|
+
...actor,
|
|
313
|
+
id: actorId,
|
|
314
|
+
publicKey: {
|
|
315
|
+
...actor.publicKey,
|
|
316
|
+
id: `${keyBase}#main-key`,
|
|
317
|
+
owner: actorId
|
|
318
|
+
},
|
|
319
|
+
assertionMethod: [{
|
|
320
|
+
...multikey,
|
|
321
|
+
id: `${keyBase}#ed25519-key`,
|
|
322
|
+
controller: actorId
|
|
323
|
+
}]
|
|
324
|
+
}, privateJwk, verificationMethod)
|
|
325
|
+
}, privateJwk, verificationMethod);
|
|
326
|
+
};
|
|
327
|
+
for (const keyBase of [actorId, `ap+ef61://${encodeURIComponent(did)}/actor`]) {
|
|
328
|
+
const dispatched = { count: 0 };
|
|
329
|
+
assertEquals([(await handle(await update(keyBase), dispatched)).status, dispatched.count], [202, 1], keyBase);
|
|
330
|
+
}
|
|
331
|
+
const dispatched = { count: 0 };
|
|
332
|
+
assertEquals([(await handle(await update("ap://did:key:z6MkhaXgBZDvotDkL5257faiztiGiC2QtKLGpbnnEGta2doK/actor"), dispatched)).status, dispatched.count], [401, 0]);
|
|
333
|
+
});
|
|
334
|
+
test("handleInbox() rejects embedded portable actors without valid gateways", async () => {
|
|
335
|
+
const did = await exportDidKey(ed25519PublicKey.publicKey);
|
|
336
|
+
const verificationMethod = `${did}#${did.slice(8)}`;
|
|
337
|
+
const privateJwk = await crypto.subtle.exportKey("jwk", ed25519PrivateKey);
|
|
338
|
+
const actorId = `ap://${did}/actor`;
|
|
339
|
+
const update = async (gateways) => await secureDocument({
|
|
340
|
+
"@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/data-integrity/v1"],
|
|
341
|
+
id: `ap://${did}/activities/update`,
|
|
342
|
+
type: "Update",
|
|
343
|
+
actor: actorId,
|
|
344
|
+
object: await secureDocument({
|
|
345
|
+
"@context": [
|
|
346
|
+
"https://www.w3.org/ns/activitystreams",
|
|
347
|
+
"https://w3id.org/security/data-integrity/v1",
|
|
348
|
+
"https://w3id.org/fep/ef61"
|
|
349
|
+
],
|
|
350
|
+
id: actorId,
|
|
351
|
+
type: "Person",
|
|
352
|
+
inbox: `${actorId}/inbox`,
|
|
353
|
+
outbox: `${actorId}/outbox`,
|
|
354
|
+
...gateways
|
|
355
|
+
}, privateJwk, verificationMethod)
|
|
356
|
+
}, privateJwk, verificationMethod);
|
|
357
|
+
for (const [gateways, status, count] of [
|
|
358
|
+
[
|
|
359
|
+
{ gateways: ["https://gw.example"] },
|
|
360
|
+
202,
|
|
361
|
+
1
|
|
362
|
+
],
|
|
363
|
+
[
|
|
364
|
+
{},
|
|
365
|
+
401,
|
|
366
|
+
0
|
|
367
|
+
],
|
|
368
|
+
[
|
|
369
|
+
{ gateways: [] },
|
|
370
|
+
401,
|
|
371
|
+
0
|
|
372
|
+
],
|
|
373
|
+
[
|
|
374
|
+
{ gateways: ["https://gw.example/path"] },
|
|
375
|
+
400,
|
|
376
|
+
0
|
|
377
|
+
]
|
|
378
|
+
]) {
|
|
379
|
+
const dispatched = { count: 0 };
|
|
380
|
+
assertEquals([(await handle(await update(gateways), dispatched)).status, dispatched.count], [status, count], JSON.stringify(gateways));
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
//#endregion
|
|
384
|
+
export {};
|