@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
|
@@ -0,0 +1,931 @@
|
|
|
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 assertRejects } from "../assert_rejects-CTptdYck.mjs";
|
|
9
|
+
import { t as assert } from "../assert-D1Eafmd_.mjs";
|
|
10
|
+
import { n as exportJwk } from "../key-BspPiXqn.mjs";
|
|
11
|
+
import { s as detachSignature } from "../ld-CzaP-hJl.mjs";
|
|
12
|
+
import { a as signObject } from "../proof-Dxy6ByqI.mjs";
|
|
13
|
+
import { i as rsaPrivateKey2, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2 } from "../keys-DIJNHW-Y.mjs";
|
|
14
|
+
import { C as getEmbeddedFirstGateway, S as getCompactRootId, c as assertPortableActorActivity, n as FederationImpl, o as createFederation, w as warnCompatibleIdsInJson } from "../middleware-D7_LO-Vs.mjs";
|
|
15
|
+
import { o as verifyCompoundPortableObjectProofs } from "../compound-proof-B-9mXxFW.mjs";
|
|
16
|
+
import { configure, reset } from "@logtape/logtape";
|
|
17
|
+
import { Accept, Announce, Create, Follow, Note, Person } from "@fedify/vocab";
|
|
18
|
+
import { exportDidKey, parseIri } from "@fedify/vocab-runtime";
|
|
19
|
+
import { mockDocumentLoader, test } from "@fedify/fixture";
|
|
20
|
+
//#region src/federation/outgoing-proof.test.ts
|
|
21
|
+
const options = {
|
|
22
|
+
contextLoader: mockDocumentLoader,
|
|
23
|
+
documentLoader: mockDocumentLoader
|
|
24
|
+
};
|
|
25
|
+
const created = Temporal.Instant.from("2023-02-24T23:36:38Z");
|
|
26
|
+
const childContext = [
|
|
27
|
+
"https://www.w3.org/ns/activitystreams",
|
|
28
|
+
"https://w3id.org/security/data-integrity/v1",
|
|
29
|
+
{ ex: "https://example.com/ns#" }
|
|
30
|
+
];
|
|
31
|
+
const limits = {
|
|
32
|
+
maxDepth: 64,
|
|
33
|
+
maxMaps: 1e4,
|
|
34
|
+
maxProofs: 32,
|
|
35
|
+
maxBytes: 10 * 1024 * 1024
|
|
36
|
+
};
|
|
37
|
+
const recipient = {
|
|
38
|
+
id: new URL("https://example.com/users/bob"),
|
|
39
|
+
inboxId: new URL("https://example.com/inbox")
|
|
40
|
+
};
|
|
41
|
+
const rsaKey = {
|
|
42
|
+
keyId: rsaPublicKey2.id,
|
|
43
|
+
privateKey: rsaPrivateKey2
|
|
44
|
+
};
|
|
45
|
+
async function didKey(pair) {
|
|
46
|
+
const { privateKey, publicKey } = pair ?? await crypto.subtle.generateKey("Ed25519", true, ["sign", "verify"]);
|
|
47
|
+
const did = await exportDidKey(publicKey);
|
|
48
|
+
return {
|
|
49
|
+
did,
|
|
50
|
+
keyId: new URL(`${did}#${did.substring(8)}`),
|
|
51
|
+
privateKey,
|
|
52
|
+
publicKey
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function sender(key) {
|
|
56
|
+
return {
|
|
57
|
+
keyId: key.keyId,
|
|
58
|
+
privateKey: key.privateKey
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
async function signedChild(owner) {
|
|
62
|
+
return await signObject(new Note({
|
|
63
|
+
id: parseIri(`ap+ef61://${owner.did}/objects/${crypto.randomUUID()}`),
|
|
64
|
+
attribution: parseIri(`ap+ef61://${owner.did}/actor`),
|
|
65
|
+
content: "A portable note"
|
|
66
|
+
}), owner.privateKey, owner.keyId, {
|
|
67
|
+
...options,
|
|
68
|
+
context: childContext,
|
|
69
|
+
created
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
function portableCreate(owner, object) {
|
|
73
|
+
return new Create({
|
|
74
|
+
id: parseIri(`ap+ef61://${owner.did}/activities/${crypto.randomUUID()}`),
|
|
75
|
+
actor: parseIri(`ap+ef61://${owner.did}/actor`),
|
|
76
|
+
object
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
function httpCreate(object) {
|
|
80
|
+
return new Create({
|
|
81
|
+
id: new URL(`https://example.com/activities/${crypto.randomUUID()}`),
|
|
82
|
+
actor: new URL("https://example.com/users/alice"),
|
|
83
|
+
object
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
function createQueue() {
|
|
87
|
+
const queued = [];
|
|
88
|
+
return {
|
|
89
|
+
queued,
|
|
90
|
+
queue: {
|
|
91
|
+
enqueue(message) {
|
|
92
|
+
queued.push(message);
|
|
93
|
+
return Promise.resolve();
|
|
94
|
+
},
|
|
95
|
+
listen() {
|
|
96
|
+
return Promise.resolve();
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function createTestFederation(queue) {
|
|
102
|
+
const federation = new FederationImpl({
|
|
103
|
+
kv: new MemoryKvStore(),
|
|
104
|
+
queue,
|
|
105
|
+
manuallyStartQueue: true,
|
|
106
|
+
contextLoaderFactory: () => mockDocumentLoader,
|
|
107
|
+
documentLoaderFactory: () => mockDocumentLoader
|
|
108
|
+
});
|
|
109
|
+
federation.setInboxListeners("/users/{identifier}/inbox", "/inbox");
|
|
110
|
+
return federation;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Captures what is POSTed to the recipient's inbox while `run` executes.
|
|
114
|
+
*/
|
|
115
|
+
async function capture(run) {
|
|
116
|
+
const bodies = [];
|
|
117
|
+
const requests = [];
|
|
118
|
+
esm_default.spyGlobal();
|
|
119
|
+
try {
|
|
120
|
+
esm_default.post(recipient.inboxId.href, async (cl) => {
|
|
121
|
+
const body = await cl.request.text();
|
|
122
|
+
requests.push(new Request(cl.request.url, {
|
|
123
|
+
method: cl.request.method,
|
|
124
|
+
headers: cl.request.headers,
|
|
125
|
+
body
|
|
126
|
+
}));
|
|
127
|
+
bodies.push(JSON.parse(body));
|
|
128
|
+
return new Response(null, { status: 202 });
|
|
129
|
+
});
|
|
130
|
+
await run();
|
|
131
|
+
} finally {
|
|
132
|
+
esm_default.hardReset();
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
bodies,
|
|
136
|
+
requests
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function proofOf(document) {
|
|
140
|
+
const proof = document.proof;
|
|
141
|
+
assert(proof != null && typeof proof === "object" && !Array.isArray(proof), `expected exactly one direct proof, got ${JSON.stringify(proof)}`);
|
|
142
|
+
return proof;
|
|
143
|
+
}
|
|
144
|
+
async function assertCompoundVerifies(body) {
|
|
145
|
+
const result = await verifyCompoundPortableObjectProofs(detachSignature(body), limits, options);
|
|
146
|
+
assertEquals(result.status, "ok");
|
|
147
|
+
assert(result.status === "ok" && result.verified);
|
|
148
|
+
}
|
|
149
|
+
test("a portable activity with one Ed25519 key gets one direct proof", async () => {
|
|
150
|
+
const owner = await didKey();
|
|
151
|
+
const child = await signedChild(await didKey());
|
|
152
|
+
const ctx = createTestFederation().createContext(new URL("https://example.com/"));
|
|
153
|
+
const { bodies, requests } = await capture(() => ctx.sendActivity([rsaKey, sender(owner)], recipient, portableCreate(owner, child)));
|
|
154
|
+
assertEquals(bodies.length, 1);
|
|
155
|
+
assertEquals(proofOf(bodies[0]).verificationMethod, owner.keyId.href);
|
|
156
|
+
assert(requests[0].headers.has("Signature"));
|
|
157
|
+
assertEquals(bodies[0].signature, void 0);
|
|
158
|
+
await assertCompoundVerifies(bodies[0]);
|
|
159
|
+
});
|
|
160
|
+
test("a portable activity is signed only by the key matching its DID", async () => {
|
|
161
|
+
const owner = await didKey();
|
|
162
|
+
const other = await didKey();
|
|
163
|
+
const childOwner = await didKey();
|
|
164
|
+
const child = await signedChild(childOwner);
|
|
165
|
+
for (const keys of [[other, owner], [owner, other]]) {
|
|
166
|
+
const ctx = createTestFederation().createContext(new URL("https://example.com/"));
|
|
167
|
+
const { bodies, requests } = await capture(() => ctx.sendActivity([
|
|
168
|
+
rsaKey,
|
|
169
|
+
...keys.map(sender),
|
|
170
|
+
sender(childOwner)
|
|
171
|
+
], recipient, portableCreate(owner, child)));
|
|
172
|
+
assertEquals(bodies.length, 1);
|
|
173
|
+
assertEquals(proofOf(bodies[0]).verificationMethod, owner.keyId.href);
|
|
174
|
+
assert(requests[0].headers.has("Signature"));
|
|
175
|
+
const embedded = bodies[0].object;
|
|
176
|
+
assertEquals(embedded["@context"], childContext);
|
|
177
|
+
assertEquals(proofOf(embedded).verificationMethod, childOwner.keyId.href);
|
|
178
|
+
await assertCompoundVerifies(bodies[0]);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
test("a portable activity without exactly one DID-matching key is rejected", async () => {
|
|
182
|
+
const owner = await didKey();
|
|
183
|
+
const child = await signedChild(await didKey());
|
|
184
|
+
const cases = [
|
|
185
|
+
[
|
|
186
|
+
"no key matches",
|
|
187
|
+
[sender(await didKey()), sender(await didKey())],
|
|
188
|
+
"none of its 2 Ed25519 keys"
|
|
189
|
+
],
|
|
190
|
+
[
|
|
191
|
+
"a same-origin key ID is not a DID URL",
|
|
192
|
+
[{
|
|
193
|
+
keyId: parseIri(`ap+ef61://${owner.did}/actor#main-key`),
|
|
194
|
+
privateKey: owner.privateKey
|
|
195
|
+
}, sender(await didKey())],
|
|
196
|
+
"none of its 1 Ed25519 keys"
|
|
197
|
+
],
|
|
198
|
+
[
|
|
199
|
+
"several keys match",
|
|
200
|
+
[sender(owner), {
|
|
201
|
+
keyId: new URL(`${owner.did}#second`),
|
|
202
|
+
privateKey: owner.privateKey
|
|
203
|
+
}],
|
|
204
|
+
"2 of its Ed25519 keys"
|
|
205
|
+
]
|
|
206
|
+
];
|
|
207
|
+
for (const [name, keys, message] of cases) {
|
|
208
|
+
const ctx = createTestFederation().createContext(new URL("https://example.com/"));
|
|
209
|
+
const { bodies } = await capture(() => assertRejects(() => ctx.sendActivity([rsaKey, ...keys], recipient, portableCreate(owner, child)), TypeError, message));
|
|
210
|
+
assertEquals(bodies.length, 0, name);
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
test("a non-portable activity embedding portable objects needs one key", async () => {
|
|
214
|
+
const child = await signedChild(await didKey());
|
|
215
|
+
const ctx = createTestFederation().createContext(new URL("https://example.com/"));
|
|
216
|
+
const keys = [sender(await didKey()), sender(await didKey())];
|
|
217
|
+
assertEquals((await capture(() => assertRejects(() => ctx.sendActivity([rsaKey, ...keys], recipient, httpCreate(child)), TypeError, "exactly one Ed25519 key"))).bodies.length, 0);
|
|
218
|
+
const key = {
|
|
219
|
+
keyId: ed25519PublicKey.id,
|
|
220
|
+
privateKey: ed25519PrivateKey
|
|
221
|
+
};
|
|
222
|
+
const accepted = await capture(() => ctx.sendActivity([rsaKey, key], recipient, httpCreate(child)));
|
|
223
|
+
assertEquals(accepted.bodies.length, 1);
|
|
224
|
+
assertEquals(proofOf(accepted.bodies[0]).verificationMethod, ed25519PublicKey.id.href);
|
|
225
|
+
});
|
|
226
|
+
test("a non-portable activity embedding compatible-ID objects needs one key", async () => {
|
|
227
|
+
const owner = await didKey();
|
|
228
|
+
const compatibleId = (path) => new URL(`https://gw.example/.well-known/apgateway/${owner.did}${path}`);
|
|
229
|
+
const child = await signObject(new Note({
|
|
230
|
+
id: compatibleId(`/objects/${crypto.randomUUID()}`),
|
|
231
|
+
attribution: compatibleId("/actor"),
|
|
232
|
+
content: "A portable note"
|
|
233
|
+
}), owner.privateKey, owner.keyId, {
|
|
234
|
+
...options,
|
|
235
|
+
context: childContext,
|
|
236
|
+
created
|
|
237
|
+
});
|
|
238
|
+
const ctx = createTestFederation().createContext(new URL("https://example.com/"));
|
|
239
|
+
const keys = [sender(await didKey()), sender(await didKey())];
|
|
240
|
+
assertEquals((await capture(() => assertRejects(() => ctx.sendActivity([rsaKey, ...keys], recipient, httpCreate(child)), TypeError, "exactly one Ed25519 key"))).bodies.length, 0);
|
|
241
|
+
const key = {
|
|
242
|
+
keyId: ed25519PublicKey.id,
|
|
243
|
+
privateKey: ed25519PrivateKey
|
|
244
|
+
};
|
|
245
|
+
const accepted = await capture(() => ctx.sendActivity([rsaKey, key], recipient, httpCreate(child)));
|
|
246
|
+
assertEquals(accepted.bodies.length, 1);
|
|
247
|
+
await assertCompoundVerifies(accepted.bodies[0]);
|
|
248
|
+
});
|
|
249
|
+
test("an activity without portable objects is still signed by every key", async () => {
|
|
250
|
+
const ctx = createTestFederation().createContext(new URL("https://example.com/"));
|
|
251
|
+
const keys = [await didKey(), await didKey()];
|
|
252
|
+
const { bodies } = await capture(() => ctx.sendActivity([rsaKey, ...keys.map(sender)], recipient, httpCreate(new Note({ content: "Hello" }))));
|
|
253
|
+
assertEquals(bodies.length, 1);
|
|
254
|
+
const proofs = bodies[0].proof;
|
|
255
|
+
assert(Array.isArray(proofs));
|
|
256
|
+
assertEquals(proofs.map((proof) => proof.verificationMethod), keys.map((key) => key.keyId.href));
|
|
257
|
+
});
|
|
258
|
+
test("a pre-signed portable activity is not re-signed", async () => {
|
|
259
|
+
const owner = await didKey();
|
|
260
|
+
const activity = await signObject(portableCreate(owner, await signedChild(await didKey())), owner.privateKey, owner.keyId, {
|
|
261
|
+
...options,
|
|
262
|
+
created
|
|
263
|
+
});
|
|
264
|
+
const ctx = createTestFederation().createContext(new URL("https://example.com/"));
|
|
265
|
+
const keys = [sender(await didKey()), sender(await didKey())];
|
|
266
|
+
const { bodies } = await capture(() => ctx.sendActivity([rsaKey, ...keys], recipient, activity));
|
|
267
|
+
assertEquals(bodies.length, 1);
|
|
268
|
+
assertEquals(proofOf(bodies[0]).verificationMethod, owner.keyId.href);
|
|
269
|
+
await assertCompoundVerifies(bodies[0]);
|
|
270
|
+
});
|
|
271
|
+
test("a portable activity carrying a proof set is not delivered", async () => {
|
|
272
|
+
const owner = await didKey();
|
|
273
|
+
const second = {
|
|
274
|
+
...owner,
|
|
275
|
+
keyId: new URL(`${owner.did}#second`)
|
|
276
|
+
};
|
|
277
|
+
let activity = portableCreate(owner, await signedChild(owner));
|
|
278
|
+
for (const key of [owner, second]) activity = await signObject(activity, key.privateKey, key.keyId, {
|
|
279
|
+
...options,
|
|
280
|
+
created
|
|
281
|
+
});
|
|
282
|
+
assertEquals((await capture(() => assertRejects(() => createTestFederation().createContext(new URL("https://example.com/")).sendActivity(rsaKey, recipient, activity), TypeError, "JSON Pointer \"/proof\""))).bodies.length, 0);
|
|
283
|
+
const outbox = createQueue();
|
|
284
|
+
await assertRejects(() => createTestFederation(outbox.queue).createContext(new URL("https://example.com/")).sendActivity(rsaKey, recipient, activity, { fanout: "skip" }), TypeError, "JSON Pointer \"/proof\"");
|
|
285
|
+
assertEquals(outbox.queued.length, 0);
|
|
286
|
+
const fanout = createQueue();
|
|
287
|
+
await assertRejects(() => createTestFederation(fanout.queue).createContext(new URL("https://example.com/")).sendActivity(rsaKey, recipient, activity, { fanout: "force" }), TypeError, "JSON Pointer \"/proof\"");
|
|
288
|
+
assertEquals(fanout.queued.length, 0);
|
|
289
|
+
});
|
|
290
|
+
test("a proof set on an embedded map is not delivered either", async () => {
|
|
291
|
+
const owner = await didKey();
|
|
292
|
+
let child = await signedChild(owner);
|
|
293
|
+
child = await signObject(child, owner.privateKey, new URL(`${owner.did}#b`), {
|
|
294
|
+
...options,
|
|
295
|
+
created
|
|
296
|
+
});
|
|
297
|
+
const ctx = createTestFederation().createContext(new URL("https://example.com/"));
|
|
298
|
+
const { bodies } = await capture(() => assertRejects(() => ctx.sendActivity(rsaKey, recipient, httpCreate(child)), TypeError, "JSON Pointer \"/object/proof\""));
|
|
299
|
+
assertEquals(bodies.length, 0);
|
|
300
|
+
});
|
|
301
|
+
test("a received signed object rebuilt inside a portable activity is not delivered", async () => {
|
|
302
|
+
const followerKey = await didKey();
|
|
303
|
+
const signedFollow = await signObject(new Follow({
|
|
304
|
+
id: new URL("https://example.com/follows/1"),
|
|
305
|
+
actor: new URL("https://example.com/users/bob"),
|
|
306
|
+
object: parseIri("ap+ef61://did:key:z6Mkabc/actor")
|
|
307
|
+
}), followerKey.privateKey, new URL("https://example.com/users/bob#key"), {
|
|
308
|
+
...options,
|
|
309
|
+
created
|
|
310
|
+
});
|
|
311
|
+
const follow = await Follow.fromJsonLd(await signedFollow.toJsonLd({
|
|
312
|
+
format: "compact",
|
|
313
|
+
...options
|
|
314
|
+
}), options);
|
|
315
|
+
const owner = await didKey();
|
|
316
|
+
const accept = (object) => new Accept({
|
|
317
|
+
id: parseIri(`ap+ef61://${owner.did}/accepts/${crypto.randomUUID()}`),
|
|
318
|
+
actor: parseIri(`ap+ef61://${owner.did}/actor`),
|
|
319
|
+
object
|
|
320
|
+
});
|
|
321
|
+
const unsigned = accept(follow);
|
|
322
|
+
const presigned = await signObject(accept(follow), owner.privateKey, owner.keyId, {
|
|
323
|
+
...options,
|
|
324
|
+
created
|
|
325
|
+
});
|
|
326
|
+
const cases = [[unsigned, [rsaKey, sender(owner)]], [presigned, [rsaKey]]];
|
|
327
|
+
for (const [activity, keys] of cases) {
|
|
328
|
+
assertEquals((await capture(() => assertRejects(() => createTestFederation().createContext(new URL("https://example.com/")).sendActivity(keys, recipient, activity), TypeError, "JSON Pointer \"/object\""))).bodies.length, 0);
|
|
329
|
+
const fanout = createQueue();
|
|
330
|
+
await assertRejects(() => createTestFederation(fanout.queue).createContext(new URL("https://example.com/")).sendActivity(keys, recipient, activity, { fanout: "force" }), TypeError, "JSON Pointer \"/object\"");
|
|
331
|
+
assertEquals(fanout.queued.length, 0);
|
|
332
|
+
}
|
|
333
|
+
const { bodies } = await capture(() => createTestFederation().createContext(new URL("https://example.com/")).sendActivity([rsaKey, sender(owner)], recipient, accept(follow.id)));
|
|
334
|
+
assertEquals(bodies.length, 1);
|
|
335
|
+
await assertCompoundVerifies(bodies[0]);
|
|
336
|
+
});
|
|
337
|
+
test("forced fanout selects the DID-matching key before enqueueing", async () => {
|
|
338
|
+
const owner = await didKey();
|
|
339
|
+
const childOwner = await didKey();
|
|
340
|
+
const child = await signedChild(childOwner);
|
|
341
|
+
const { queue, queued } = createQueue();
|
|
342
|
+
const federation = createTestFederation(queue);
|
|
343
|
+
const ctx = federation.createContext(new URL("https://example.com/"));
|
|
344
|
+
const { bodies } = await capture(async () => {
|
|
345
|
+
await ctx.sendActivity([
|
|
346
|
+
rsaKey,
|
|
347
|
+
sender(await didKey()),
|
|
348
|
+
sender(owner)
|
|
349
|
+
], recipient, portableCreate(owner, child), { fanout: "force" });
|
|
350
|
+
assertEquals(queued.length, 1);
|
|
351
|
+
const message = queued[0];
|
|
352
|
+
assertEquals(message.type, "fanout");
|
|
353
|
+
assertEquals(message.keys.length, 3);
|
|
354
|
+
assertEquals(message.keys[0].keyId, rsaKey.keyId.href);
|
|
355
|
+
for (let i = 0; i < queued.length; i++) await federation.processQueuedTask(void 0, queued[i]);
|
|
356
|
+
});
|
|
357
|
+
assertEquals(bodies.length, 1);
|
|
358
|
+
assertEquals(proofOf(bodies[0]).verificationMethod, owner.keyId.href);
|
|
359
|
+
const embedded = bodies[0].object;
|
|
360
|
+
assertEquals(embedded["@context"], childContext);
|
|
361
|
+
assertEquals(proofOf(embedded).verificationMethod, childOwner.keyId.href);
|
|
362
|
+
await assertCompoundVerifies(bodies[0]);
|
|
363
|
+
});
|
|
364
|
+
test("the fanout worker selects one key for an unsigned portable activity", async () => {
|
|
365
|
+
const owner = await didKey();
|
|
366
|
+
const child = await signedChild(await didKey());
|
|
367
|
+
const federation = createTestFederation();
|
|
368
|
+
const message = async (activity) => ({
|
|
369
|
+
type: "fanout",
|
|
370
|
+
id: crypto.randomUUID(),
|
|
371
|
+
baseUrl: "https://example.com",
|
|
372
|
+
keys: await Promise.all([
|
|
373
|
+
rsaKey,
|
|
374
|
+
sender(await didKey()),
|
|
375
|
+
sender(owner)
|
|
376
|
+
].map(async (key) => ({
|
|
377
|
+
keyId: key.keyId.href,
|
|
378
|
+
privateKey: await exportJwk(key.privateKey)
|
|
379
|
+
}))),
|
|
380
|
+
inboxes: { [recipient.inboxId.href]: {
|
|
381
|
+
actorIds: [recipient.id.href],
|
|
382
|
+
sharedInbox: false
|
|
383
|
+
} },
|
|
384
|
+
activity: await activity.toJsonLd({
|
|
385
|
+
format: "compact",
|
|
386
|
+
...options
|
|
387
|
+
}),
|
|
388
|
+
activityId: activity.id.href,
|
|
389
|
+
activityType: "https://www.w3.org/ns/activitystreams#Create",
|
|
390
|
+
traceContext: {}
|
|
391
|
+
});
|
|
392
|
+
const byId = new Create({
|
|
393
|
+
id: parseIri(`ap+ef61://${owner.did}/activities/${crypto.randomUUID()}`),
|
|
394
|
+
actor: parseIri(`ap+ef61://${owner.did}/actor`),
|
|
395
|
+
object: child.id
|
|
396
|
+
});
|
|
397
|
+
const { bodies } = await capture(async () => federation.processQueuedTask(void 0, await message(byId)));
|
|
398
|
+
assertEquals(bodies.length, 1);
|
|
399
|
+
assertEquals(proofOf(bodies[0]).verificationMethod, owner.keyId.href);
|
|
400
|
+
await assertCompoundVerifies(bodies[0]);
|
|
401
|
+
const embedded = await message(portableCreate(owner, child));
|
|
402
|
+
assertEquals((await capture(() => assertRejects(() => federation.processQueuedTask(void 0, embedded), TypeError, "JSON Pointer \"/object\""))).bodies.length, 0);
|
|
403
|
+
});
|
|
404
|
+
test("actor key pairs follow the compound-proof key selection too", async () => {
|
|
405
|
+
const dispatched = [];
|
|
406
|
+
const federation = createTestFederation();
|
|
407
|
+
federation.setActorDispatcher("/users/{identifier}", (ctx, identifier) => new Person({
|
|
408
|
+
id: ctx.getActorUri(identifier),
|
|
409
|
+
inbox: ctx.getInboxUri(identifier)
|
|
410
|
+
})).setKeyPairsDispatcher(() => dispatched);
|
|
411
|
+
const ctx = federation.createContext(new URL("https://example.com/"));
|
|
412
|
+
const owner = await didKey();
|
|
413
|
+
const child = await signedChild(await didKey());
|
|
414
|
+
const other = await didKey();
|
|
415
|
+
dispatched.push({
|
|
416
|
+
privateKey: rsaPrivateKey2,
|
|
417
|
+
publicKey: rsaPublicKey2.publicKey
|
|
418
|
+
}, {
|
|
419
|
+
privateKey: owner.privateKey,
|
|
420
|
+
publicKey: owner.publicKey
|
|
421
|
+
}, {
|
|
422
|
+
privateKey: other.privateKey,
|
|
423
|
+
publicKey: other.publicKey
|
|
424
|
+
});
|
|
425
|
+
assertEquals((await capture(() => assertRejects(() => ctx.sendActivity({ identifier: "alice" }, recipient, portableCreate(owner, child)), TypeError, "none of its 2 Ed25519 keys"))).bodies.length, 0);
|
|
426
|
+
dispatched.splice(2);
|
|
427
|
+
const presigned = await signObject(portableCreate(owner, child), owner.privateKey, owner.keyId, {
|
|
428
|
+
...options,
|
|
429
|
+
created
|
|
430
|
+
});
|
|
431
|
+
const kept = await capture(() => ctx.sendActivity({ identifier: "alice" }, recipient, presigned));
|
|
432
|
+
assertEquals(kept.bodies.length, 1);
|
|
433
|
+
assertEquals(proofOf(kept.bodies[0]).verificationMethod, owner.keyId.href);
|
|
434
|
+
const ordinary = await signObject(httpCreate(new Note({ content: "Hello" })), owner.privateKey, owner.keyId, {
|
|
435
|
+
...options,
|
|
436
|
+
created
|
|
437
|
+
});
|
|
438
|
+
const appended = await capture(() => ctx.sendActivity({ identifier: "alice" }, recipient, ordinary));
|
|
439
|
+
assertEquals(appended.bodies.length, 1);
|
|
440
|
+
const proofs = appended.bodies[0].proof;
|
|
441
|
+
assert(Array.isArray(proofs));
|
|
442
|
+
assertEquals(proofs.map((proof) => proof.verificationMethod), [owner.keyId.href, "https://example.com/users/alice#multikey-2"]);
|
|
443
|
+
});
|
|
444
|
+
test("a Fedify inbox accepts a portable activity Fedify produced", async () => {
|
|
445
|
+
const owner = await didKey();
|
|
446
|
+
const child = await signedChild(await didKey());
|
|
447
|
+
const ctx = createTestFederation().createContext(new URL("https://example.com/"));
|
|
448
|
+
const other = await didKey();
|
|
449
|
+
const { requests } = await capture(() => ctx.sendActivity([sender(other), sender(owner)], recipient, portableCreate(owner, child)));
|
|
450
|
+
assertEquals(requests.length, 1);
|
|
451
|
+
let received = 0;
|
|
452
|
+
const receiving = createFederation({
|
|
453
|
+
kv: new MemoryKvStore(),
|
|
454
|
+
contextLoaderFactory: () => mockDocumentLoader,
|
|
455
|
+
documentLoaderFactory: () => mockDocumentLoader
|
|
456
|
+
});
|
|
457
|
+
receiving.setActorDispatcher("/users/{identifier}", () => null);
|
|
458
|
+
receiving.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
|
|
459
|
+
received++;
|
|
460
|
+
});
|
|
461
|
+
assertEquals([(await receiving.fetch(requests[0], { contextData: void 0 })).status, received], [202, 1]);
|
|
462
|
+
});
|
|
463
|
+
const gateway = "https://gw.example";
|
|
464
|
+
function compatibleId(did, path, origin = gateway) {
|
|
465
|
+
return new URL(`${origin}/.well-known/apgateway/${did}${path}`);
|
|
466
|
+
}
|
|
467
|
+
function compatibleCreate(owner, object, { id, actor } = {}) {
|
|
468
|
+
return new Create({
|
|
469
|
+
id: id ?? compatibleId(owner.did, `/activities/${crypto.randomUUID()}`),
|
|
470
|
+
actor: actor ?? compatibleId(owner.did, "/actor"),
|
|
471
|
+
object
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
test("a compatible-ID activity is signed only by the key matching its DID", async () => {
|
|
475
|
+
const owner = await didKey();
|
|
476
|
+
const childOwner = await didKey();
|
|
477
|
+
const child = await signedChild(childOwner);
|
|
478
|
+
const ctx = createTestFederation().createContext(new URL("https://example.com/"));
|
|
479
|
+
const { bodies, requests } = await capture(async () => ctx.sendActivity([
|
|
480
|
+
rsaKey,
|
|
481
|
+
sender(await didKey()),
|
|
482
|
+
sender(owner),
|
|
483
|
+
sender(childOwner)
|
|
484
|
+
], recipient, compatibleCreate(owner, child)));
|
|
485
|
+
assertEquals(bodies.length, 1);
|
|
486
|
+
assertEquals(proofOf(bodies[0]).verificationMethod, owner.keyId.href);
|
|
487
|
+
assert(requests[0].headers.has("Signature"));
|
|
488
|
+
assertEquals(bodies[0].signature, void 0);
|
|
489
|
+
await assertCompoundVerifies(bodies[0]);
|
|
490
|
+
});
|
|
491
|
+
test("a compatible-ID actor's activity needs an ID of the actor's DID", async () => {
|
|
492
|
+
const owner = await didKey();
|
|
493
|
+
const other = await didKey();
|
|
494
|
+
const cases = [
|
|
495
|
+
[
|
|
496
|
+
"a compatible activity ID of another DID",
|
|
497
|
+
compatibleCreate(owner, void 0, { id: compatibleId(other.did, "/activities/1") }),
|
|
498
|
+
"with the same DID"
|
|
499
|
+
],
|
|
500
|
+
[
|
|
501
|
+
"an ap: activity ID of another DID",
|
|
502
|
+
compatibleCreate(owner, void 0, { id: parseIri(`ap://${other.did}/activities/1`) }),
|
|
503
|
+
"with the same DID"
|
|
504
|
+
],
|
|
505
|
+
[
|
|
506
|
+
"an ordinary activity ID",
|
|
507
|
+
compatibleCreate(owner, void 0, { id: new URL("https://gw.example/activities/1") }),
|
|
508
|
+
"with the same DID"
|
|
509
|
+
],
|
|
510
|
+
[
|
|
511
|
+
"a malformed compatible activity ID",
|
|
512
|
+
compatibleCreate(owner, void 0, { id: new URL(compatibleId(owner.did, "/activities/1").href + "?@gateway=https%3A%2F%2Fgw.example") }),
|
|
513
|
+
"with the same DID"
|
|
514
|
+
],
|
|
515
|
+
[
|
|
516
|
+
"a malformed compatible actor ID",
|
|
517
|
+
compatibleCreate(owner, void 0, { actor: new URL(compatibleId(owner.did, "/actor").href + "?@gateway=https%3A%2F%2Fgw.example") }),
|
|
518
|
+
"malformed FEP-ef61 portable ID"
|
|
519
|
+
],
|
|
520
|
+
[
|
|
521
|
+
"compatible actors of different DIDs",
|
|
522
|
+
new Create({
|
|
523
|
+
id: compatibleId(owner.did, "/activities/1"),
|
|
524
|
+
actors: [compatibleId(owner.did, "/actor"), compatibleId(other.did, "/actor")]
|
|
525
|
+
}),
|
|
526
|
+
"different DIDs"
|
|
527
|
+
]
|
|
528
|
+
];
|
|
529
|
+
for (const [name, activity, message] of cases) {
|
|
530
|
+
const ctx = createTestFederation().createContext(new URL("https://example.com/"));
|
|
531
|
+
const { bodies } = await capture(() => assertRejects(() => ctx.sendActivity([rsaKey, sender(owner)], recipient, activity), TypeError, message));
|
|
532
|
+
assertEquals(bodies.length, 0, name);
|
|
533
|
+
}
|
|
534
|
+
});
|
|
535
|
+
test("a compatible-ID activity without exactly one DID-matching key is rejected", async () => {
|
|
536
|
+
const owner = await didKey();
|
|
537
|
+
const cases = [
|
|
538
|
+
[
|
|
539
|
+
"no key matches",
|
|
540
|
+
[sender(await didKey())],
|
|
541
|
+
"none of its 1 Ed25519 keys"
|
|
542
|
+
],
|
|
543
|
+
[
|
|
544
|
+
"only a gateway key",
|
|
545
|
+
[{
|
|
546
|
+
keyId: compatibleId(owner.did, "/actor#main-key"),
|
|
547
|
+
privateKey: owner.privateKey
|
|
548
|
+
}],
|
|
549
|
+
"none of its 0 Ed25519 keys"
|
|
550
|
+
],
|
|
551
|
+
[
|
|
552
|
+
"several keys match",
|
|
553
|
+
[sender(owner), {
|
|
554
|
+
keyId: new URL(`${owner.did}#second`),
|
|
555
|
+
privateKey: owner.privateKey
|
|
556
|
+
}],
|
|
557
|
+
"2 of its Ed25519 keys"
|
|
558
|
+
]
|
|
559
|
+
];
|
|
560
|
+
for (const [name, keys, message] of cases) {
|
|
561
|
+
const ctx = createTestFederation().createContext(new URL("https://example.com/"));
|
|
562
|
+
const { bodies } = await capture(() => assertRejects(() => ctx.sendActivity([rsaKey, ...keys], recipient, compatibleCreate(owner)), TypeError, message));
|
|
563
|
+
assertEquals(bodies.length, 0, name);
|
|
564
|
+
}
|
|
565
|
+
});
|
|
566
|
+
test("compatible and ap: IDs of the same DID can be mixed", async () => {
|
|
567
|
+
const owner = await didKey();
|
|
568
|
+
const activities = [
|
|
569
|
+
compatibleCreate(owner, void 0, { id: parseIri(`ap://${owner.did}/activities/${crypto.randomUUID()}`) }),
|
|
570
|
+
compatibleCreate(owner, void 0, { actor: parseIri(`ap+ef61://${owner.did}/actor`) }),
|
|
571
|
+
compatibleCreate(owner, void 0, { id: compatibleId(owner.did, "/activities/1", "https://other.example") })
|
|
572
|
+
];
|
|
573
|
+
for (const activity of activities) {
|
|
574
|
+
const ctx = createTestFederation().createContext(new URL("https://example.com/"));
|
|
575
|
+
const { bodies } = await capture(async () => ctx.sendActivity([
|
|
576
|
+
rsaKey,
|
|
577
|
+
sender(await didKey()),
|
|
578
|
+
sender(owner)
|
|
579
|
+
], recipient, activity));
|
|
580
|
+
assertEquals(bodies.length, 1, activity.id?.href);
|
|
581
|
+
assertEquals(proofOf(bodies[0]).verificationMethod, owner.keyId.href);
|
|
582
|
+
}
|
|
583
|
+
});
|
|
584
|
+
test("a pre-signed compatible-ID activity is not re-signed", async () => {
|
|
585
|
+
const owner = await didKey();
|
|
586
|
+
const activity = await signObject(compatibleCreate(owner, await signedChild(await didKey())), owner.privateKey, owner.keyId, {
|
|
587
|
+
...options,
|
|
588
|
+
created
|
|
589
|
+
});
|
|
590
|
+
const ctx = createTestFederation().createContext(new URL("https://example.com/"));
|
|
591
|
+
const { bodies } = await capture(async () => ctx.sendActivity([
|
|
592
|
+
rsaKey,
|
|
593
|
+
sender(await didKey()),
|
|
594
|
+
sender(await didKey())
|
|
595
|
+
], recipient, activity));
|
|
596
|
+
assertEquals(bodies.length, 1);
|
|
597
|
+
assertEquals(proofOf(bodies[0]).verificationMethod, owner.keyId.href);
|
|
598
|
+
await assertCompoundVerifies(bodies[0]);
|
|
599
|
+
});
|
|
600
|
+
test("forced fanout selects the DID-matching key for a compatible-ID activity", async () => {
|
|
601
|
+
const owner = await didKey();
|
|
602
|
+
const { queue, queued } = createQueue();
|
|
603
|
+
const federation = createTestFederation(queue);
|
|
604
|
+
const ctx = federation.createContext(new URL("https://example.com/"));
|
|
605
|
+
const { bodies } = await capture(async () => {
|
|
606
|
+
await ctx.sendActivity([
|
|
607
|
+
rsaKey,
|
|
608
|
+
sender(await didKey()),
|
|
609
|
+
sender(owner)
|
|
610
|
+
], recipient, compatibleCreate(owner, await signedChild(await didKey())), { fanout: "force" });
|
|
611
|
+
assertEquals(queued.length, 1);
|
|
612
|
+
for (let i = 0; i < queued.length; i++) await federation.processQueuedTask(void 0, queued[i]);
|
|
613
|
+
});
|
|
614
|
+
assertEquals(bodies.length, 1);
|
|
615
|
+
assertEquals(proofOf(bodies[0]).verificationMethod, owner.keyId.href);
|
|
616
|
+
await assertCompoundVerifies(bodies[0]);
|
|
617
|
+
});
|
|
618
|
+
test("a Fedify inbox accepts an activity Fedify produced for a compatible-ID actor", async () => {
|
|
619
|
+
const owner = await didKey();
|
|
620
|
+
const child = await signedChild(await didKey());
|
|
621
|
+
const ctx = createTestFederation().createContext(new URL("https://example.com/"));
|
|
622
|
+
const { requests } = await capture(async () => ctx.sendActivity([sender(await didKey()), sender(owner)], recipient, compatibleCreate(owner, child)));
|
|
623
|
+
assertEquals(requests.length, 1);
|
|
624
|
+
let received = 0;
|
|
625
|
+
const receiving = createFederation({
|
|
626
|
+
kv: new MemoryKvStore(),
|
|
627
|
+
contextLoaderFactory: () => mockDocumentLoader,
|
|
628
|
+
documentLoaderFactory: () => mockDocumentLoader
|
|
629
|
+
});
|
|
630
|
+
receiving.setActorDispatcher("/users/{identifier}", () => null);
|
|
631
|
+
receiving.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(Create, () => {
|
|
632
|
+
received++;
|
|
633
|
+
});
|
|
634
|
+
const body = await requests[0].text();
|
|
635
|
+
const deliver = (json) => receiving.fetch(new Request(requests[0].url, {
|
|
636
|
+
method: "POST",
|
|
637
|
+
headers: requests[0].headers,
|
|
638
|
+
body: json
|
|
639
|
+
}), { contextData: void 0 });
|
|
640
|
+
assertEquals([(await deliver(body)).status, received], [202, 1]);
|
|
641
|
+
const forged = JSON.parse(body);
|
|
642
|
+
forged.actor = compatibleId((await didKey()).did, "/actor").href;
|
|
643
|
+
assertEquals([(await deliver(JSON.stringify(forged))).status, received], [401, 1]);
|
|
644
|
+
});
|
|
645
|
+
async function captureLogs(run) {
|
|
646
|
+
const records = [];
|
|
647
|
+
await reset();
|
|
648
|
+
try {
|
|
649
|
+
await configure({
|
|
650
|
+
sinks: { buffer: (record) => records.push(record) },
|
|
651
|
+
filters: {},
|
|
652
|
+
loggers: [{
|
|
653
|
+
category: ["logtape", "meta"],
|
|
654
|
+
sinks: []
|
|
655
|
+
}, {
|
|
656
|
+
category: [],
|
|
657
|
+
sinks: ["buffer"],
|
|
658
|
+
lowestLevel: "warning"
|
|
659
|
+
}]
|
|
660
|
+
});
|
|
661
|
+
await run();
|
|
662
|
+
} finally {
|
|
663
|
+
await reset();
|
|
664
|
+
}
|
|
665
|
+
return records.filter((record) => record.category[0] === "fedify");
|
|
666
|
+
}
|
|
667
|
+
test("sendActivity warns for compatible IDs off the actor's first gateway", async () => {
|
|
668
|
+
const owner = await didKey();
|
|
669
|
+
const ownerId = parseIri(`ap://${owner.did}/users/alice`);
|
|
670
|
+
const federation = createTestFederation();
|
|
671
|
+
let actorCalls = 0;
|
|
672
|
+
federation.setActorDispatcher("/users/{identifier}", (_ctx, identifier) => {
|
|
673
|
+
actorCalls++;
|
|
674
|
+
return identifier === "alice" ? new Person({
|
|
675
|
+
id: ownerId,
|
|
676
|
+
gateways: [new URL("https://primary.example"), new URL("https://secondary.example")]
|
|
677
|
+
}) : null;
|
|
678
|
+
});
|
|
679
|
+
const ctx = federation.createContext(new Request("https://example.com/"), void 0);
|
|
680
|
+
const child = await signObject(new Note({
|
|
681
|
+
id: compatibleId(owner.did, "/notes/1", "https://secondary.example"),
|
|
682
|
+
attribution: ownerId,
|
|
683
|
+
content: "Hello"
|
|
684
|
+
}), owner.privateKey, owner.keyId, {
|
|
685
|
+
...options,
|
|
686
|
+
context: childContext,
|
|
687
|
+
created
|
|
688
|
+
});
|
|
689
|
+
const activity = new Create({
|
|
690
|
+
id: compatibleId(owner.did, "/activities/1", "https://secondary.example"),
|
|
691
|
+
actor: ownerId,
|
|
692
|
+
object: child
|
|
693
|
+
});
|
|
694
|
+
const records = await captureLogs(async () => {
|
|
695
|
+
const { bodies } = await capture(() => ctx.sendActivity([rsaKey, sender(owner)], recipient, activity));
|
|
696
|
+
assertEquals(bodies.length, 1);
|
|
697
|
+
assertEquals(bodies[0].id, activity.id?.href);
|
|
698
|
+
});
|
|
699
|
+
assertEquals(records.filter((r) => r.properties.kind === "activity").length, 1);
|
|
700
|
+
assertEquals(records.filter((r) => r.properties.kind === "object").length, 1);
|
|
701
|
+
assertEquals(actorCalls, 1);
|
|
702
|
+
});
|
|
703
|
+
test("sendActivity skips first-gateway and portable IDs", async () => {
|
|
704
|
+
const owner = await didKey();
|
|
705
|
+
const ownerId = parseIri(`ap://${owner.did}/users/alice`);
|
|
706
|
+
const federation = createTestFederation();
|
|
707
|
+
federation.setActorDispatcher("/users/{identifier}", (_ctx, identifier) => identifier === "alice" ? new Person({
|
|
708
|
+
id: ownerId,
|
|
709
|
+
gateways: [new URL("https://primary.example")]
|
|
710
|
+
}) : null);
|
|
711
|
+
const ctx = federation.createContext(new Request("https://example.com/"), void 0);
|
|
712
|
+
for (const id of [compatibleId(owner.did, "/activities/first", "https://primary.example"), parseIri(`ap://${owner.did}/activities/portable`)]) assertEquals((await captureLogs(() => capture(() => ctx.sendActivity([rsaKey, sender(owner)], recipient, new Create({
|
|
713
|
+
id,
|
|
714
|
+
actor: ownerId
|
|
715
|
+
}))))).filter((r) => r.properties.kind != null), []);
|
|
716
|
+
const plain = federation.createContext(new URL("https://example.com/"));
|
|
717
|
+
assertEquals((await captureLogs(() => capture(() => plain.sendActivity([rsaKey, sender(owner)], recipient, new Create({
|
|
718
|
+
id: compatibleId(owner.did, "/activities/unknown-gateway", "https://secondary.example"),
|
|
719
|
+
actor: ownerId
|
|
720
|
+
}))))).filter((r) => r.properties.kind != null), []);
|
|
721
|
+
});
|
|
722
|
+
test("fanout warns once before enqueueing a compatible activity", async () => {
|
|
723
|
+
const owner = await didKey();
|
|
724
|
+
const ownerId = parseIri(`ap://${owner.did}/users/alice`);
|
|
725
|
+
const { queue, queued } = createQueue();
|
|
726
|
+
const federation = createTestFederation(queue);
|
|
727
|
+
federation.setActorDispatcher("/users/{identifier}", () => new Person({
|
|
728
|
+
id: ownerId,
|
|
729
|
+
gateways: [new URL("https://primary.example")]
|
|
730
|
+
}));
|
|
731
|
+
const ctx = federation.createContext(new Request("https://example.com/"), void 0);
|
|
732
|
+
assertEquals((await captureLogs(() => capture(async () => {
|
|
733
|
+
await ctx.sendActivity([rsaKey, sender(owner)], recipient, new Create({
|
|
734
|
+
id: compatibleId(owner.did, "/activities/fanout", "https://secondary.example"),
|
|
735
|
+
actor: ownerId
|
|
736
|
+
}), { fanout: "force" });
|
|
737
|
+
assertEquals(queued.length, 1);
|
|
738
|
+
for (let i = 0; i < queued.length; i++) await federation.processQueuedTask(void 0, queued[i]);
|
|
739
|
+
}))).filter((r) => r.properties.kind === "activity").length, 1);
|
|
740
|
+
});
|
|
741
|
+
test("an embedded actor supplies gateways to a plain send context", async () => {
|
|
742
|
+
const owner = await didKey();
|
|
743
|
+
const ownerId = parseIri(`ap://${owner.did}/users/alice`);
|
|
744
|
+
const ctx = new FederationImpl({
|
|
745
|
+
kv: new MemoryKvStore(),
|
|
746
|
+
contextLoaderFactory: () => mockDocumentLoader,
|
|
747
|
+
documentLoaderFactory: () => mockDocumentLoader,
|
|
748
|
+
activityTransformers: []
|
|
749
|
+
}).createContext(new URL("https://example.com/"));
|
|
750
|
+
const activity = await signObject(new Create({
|
|
751
|
+
id: compatibleId(owner.did, "/activities/embedded-actor", "https://secondary.example"),
|
|
752
|
+
actor: new Person({
|
|
753
|
+
id: ownerId,
|
|
754
|
+
gateways: [new URL("https://primary.example")]
|
|
755
|
+
})
|
|
756
|
+
}), owner.privateKey, owner.keyId, {
|
|
757
|
+
...options,
|
|
758
|
+
created
|
|
759
|
+
});
|
|
760
|
+
assertEquals(getEmbeddedFirstGateway(await activity.toJsonLd({
|
|
761
|
+
format: "compact",
|
|
762
|
+
contextLoader: mockDocumentLoader
|
|
763
|
+
}), ownerId, owner.did)?.origin, "https://primary.example");
|
|
764
|
+
assertEquals((await captureLogs(() => capture(() => ctx.sendActivity([rsaKey, sender(owner)], recipient, activity)))).filter((r) => r.properties.kind === "activity").length, 1);
|
|
765
|
+
});
|
|
766
|
+
test("sendActivity checks a locally owned embedded object independently", async () => {
|
|
767
|
+
const alice = await didKey();
|
|
768
|
+
const bob = await didKey();
|
|
769
|
+
const aliceId = parseIri(`ap://${alice.did}/users/alice`);
|
|
770
|
+
const bobId = parseIri(`ap://${bob.did}/users/bob`);
|
|
771
|
+
const federation = createTestFederation();
|
|
772
|
+
federation.setActorDispatcher("/users/{identifier}", (_ctx, identifier) => identifier === "alice" ? new Person({
|
|
773
|
+
id: aliceId,
|
|
774
|
+
gateways: [new URL("https://primary.example")]
|
|
775
|
+
}) : identifier === "bob" ? new Person({
|
|
776
|
+
id: bobId,
|
|
777
|
+
gateways: [new URL("https://bob.example")]
|
|
778
|
+
}) : null);
|
|
779
|
+
const child = await signObject(new Note({
|
|
780
|
+
id: compatibleId(bob.did, "/notes/1", "https://secondary.example"),
|
|
781
|
+
attribution: bobId,
|
|
782
|
+
content: "Bob's note"
|
|
783
|
+
}), bob.privateKey, bob.keyId, {
|
|
784
|
+
...options,
|
|
785
|
+
context: childContext,
|
|
786
|
+
created
|
|
787
|
+
});
|
|
788
|
+
const ctx = federation.createContext(new Request("https://example.com/"), void 0);
|
|
789
|
+
assertEquals((await captureLogs(() => capture(() => ctx.sendActivity([rsaKey, sender(alice)], recipient, new Announce({
|
|
790
|
+
id: parseIri(`ap://${alice.did}/activities/announce`),
|
|
791
|
+
actor: aliceId,
|
|
792
|
+
object: child
|
|
793
|
+
}))))).filter((r) => r.properties.kind === "object").map((r) => r.properties.id), [child.id?.href]);
|
|
794
|
+
});
|
|
795
|
+
test("sendActivity in an actor dispatcher does not re-enter it", async () => {
|
|
796
|
+
const owner = await didKey();
|
|
797
|
+
const ownerId = parseIri(`ap://${owner.did}/users/alice`);
|
|
798
|
+
const federation = createTestFederation();
|
|
799
|
+
let calls = 0;
|
|
800
|
+
federation.setActorDispatcher("/users/{identifier}", async (ctx) => {
|
|
801
|
+
calls++;
|
|
802
|
+
await ctx.sendActivity([rsaKey, sender(owner)], recipient, new Create({
|
|
803
|
+
id: compatibleId(owner.did, "/activities/from-dispatcher", "https://secondary.example"),
|
|
804
|
+
actor: ownerId
|
|
805
|
+
}));
|
|
806
|
+
return new Person({
|
|
807
|
+
id: ownerId,
|
|
808
|
+
gateways: [new URL("https://primary.example")]
|
|
809
|
+
});
|
|
810
|
+
});
|
|
811
|
+
await capture(async () => {
|
|
812
|
+
assertEquals((await federation.fetch(new Request("https://example.com/users/alice", { headers: { Accept: "application/activity+json" } }), { contextData: void 0 })).status, 200);
|
|
813
|
+
});
|
|
814
|
+
assertEquals(calls, 1);
|
|
815
|
+
});
|
|
816
|
+
test("sendActivity still delivers when the diagnostic actor lookup fails", async () => {
|
|
817
|
+
const owner = await didKey();
|
|
818
|
+
const ownerId = parseIri(`ap://${owner.did}/users/alice`);
|
|
819
|
+
const federation = createTestFederation();
|
|
820
|
+
federation.setActorDispatcher("/users/{identifier}", () => {
|
|
821
|
+
throw new Error("Actor storage unavailable");
|
|
822
|
+
});
|
|
823
|
+
const ctx = federation.createContext(new Request("https://example.com/"), void 0);
|
|
824
|
+
const id = compatibleId(owner.did, "/activities/lookup-fails", "https://secondary.example");
|
|
825
|
+
assertEquals((await captureLogs(async () => {
|
|
826
|
+
const { bodies } = await capture(() => ctx.sendActivity([rsaKey, sender(owner)], recipient, new Create({
|
|
827
|
+
id,
|
|
828
|
+
actor: ownerId
|
|
829
|
+
})));
|
|
830
|
+
assertEquals(bodies.length, 1);
|
|
831
|
+
assertEquals(bodies[0].id, id.href);
|
|
832
|
+
})).filter((r) => r.properties.kind != null), []);
|
|
833
|
+
});
|
|
834
|
+
test("warning checks later local actors when the first is missing", async () => {
|
|
835
|
+
const owner = await didKey();
|
|
836
|
+
const missing = parseIri(`ap://${owner.did}/users/missing`);
|
|
837
|
+
const alice = parseIri(`ap://${owner.did}/users/alice`);
|
|
838
|
+
const federation = createTestFederation();
|
|
839
|
+
federation.setActorDispatcher("/users/{identifier}", (_ctx, identifier) => identifier === "alice" ? new Person({
|
|
840
|
+
id: alice,
|
|
841
|
+
gateways: [new URL("https://primary.example")]
|
|
842
|
+
}) : null);
|
|
843
|
+
const ctx = federation.createContext(new Request("https://example.com/"), void 0);
|
|
844
|
+
assertEquals((await captureLogs(() => warnCompatibleIdsInJson({
|
|
845
|
+
id: compatibleId(owner.did, "/activities/multi", "https://secondary.example").href,
|
|
846
|
+
type: "Create",
|
|
847
|
+
actor: [missing.href, alice.href]
|
|
848
|
+
}, ctx))).filter((r) => r.properties.kind === "activity").length, 1);
|
|
849
|
+
});
|
|
850
|
+
test("warning ignores an embedded gateway key", async () => {
|
|
851
|
+
const owner = await didKey();
|
|
852
|
+
const actorId = parseIri(`ap://${owner.did}/users/alice`);
|
|
853
|
+
const records = await captureLogs(() => warnCompatibleIdsInJson({
|
|
854
|
+
id: compatibleId(owner.did, "/activities/1", "https://secondary.example").href,
|
|
855
|
+
type: "Create",
|
|
856
|
+
actor: {
|
|
857
|
+
id: actorId.href,
|
|
858
|
+
type: "Person",
|
|
859
|
+
gateways: ["https://primary.example"],
|
|
860
|
+
publicKey: [{
|
|
861
|
+
id: compatibleId(owner.did, "/users/alice/keys/1", "https://secondary.example").href,
|
|
862
|
+
type: ["CryptographicKey"]
|
|
863
|
+
}, {
|
|
864
|
+
id: compatibleId(owner.did, "/users/alice/keys/2", "https://secondary.example").href,
|
|
865
|
+
type: "sec:Key"
|
|
866
|
+
}]
|
|
867
|
+
}
|
|
868
|
+
}));
|
|
869
|
+
assertEquals(records.filter((r) => r.properties.kind === "activity").length, 1);
|
|
870
|
+
assertEquals(records.filter((r) => r.properties.kind === "object"), []);
|
|
871
|
+
});
|
|
872
|
+
test("warning resolves a signed child's local ownership alias", async () => {
|
|
873
|
+
const alice = await didKey();
|
|
874
|
+
const bob = await didKey();
|
|
875
|
+
const aliceId = parseIri(`ap://${alice.did}/users/alice`);
|
|
876
|
+
const bobId = parseIri(`ap://${bob.did}/users/bob`);
|
|
877
|
+
const federation = createTestFederation();
|
|
878
|
+
federation.setActorDispatcher("/users/{identifier}", (_ctx, identifier) => identifier === "bob" ? new Person({
|
|
879
|
+
id: bobId,
|
|
880
|
+
gateways: [new URL("https://primary.example")]
|
|
881
|
+
}) : null);
|
|
882
|
+
const ctx = federation.createContext(new Request("https://example.com/"), void 0);
|
|
883
|
+
assertEquals((await captureLogs(() => warnCompatibleIdsInJson({
|
|
884
|
+
id: parseIri(`ap://${alice.did}/activities/announce`).href,
|
|
885
|
+
type: "Announce",
|
|
886
|
+
actor: aliceId.href,
|
|
887
|
+
object: {
|
|
888
|
+
"@context": ["https://www.w3.org/ns/activitystreams", {
|
|
889
|
+
by: {
|
|
890
|
+
"@id": "as:attributedTo",
|
|
891
|
+
"@type": "@id"
|
|
892
|
+
},
|
|
893
|
+
identifier: "@id"
|
|
894
|
+
}],
|
|
895
|
+
identifier: compatibleId(bob.did, "/notes/1", "https://secondary.example").href,
|
|
896
|
+
type: "Note",
|
|
897
|
+
by: bobId.href
|
|
898
|
+
}
|
|
899
|
+
}, ctx))).filter((r) => r.properties.kind === "object").length, 1);
|
|
900
|
+
});
|
|
901
|
+
test("warning reads an inline alias of the serialized root ID", async () => {
|
|
902
|
+
const id = compatibleId((await didKey()).did, "/notes/1", "https://secondary.example");
|
|
903
|
+
assertEquals(getCompactRootId({
|
|
904
|
+
"@context": ["https://www.w3.org/ns/activitystreams", { identifier: "@id" }],
|
|
905
|
+
identifier: id.href,
|
|
906
|
+
type: "Note"
|
|
907
|
+
})?.href, id.href);
|
|
908
|
+
});
|
|
909
|
+
test("an activity on another gateway than its compatible-ID actor is warned about", async () => {
|
|
910
|
+
const owner = await didKey();
|
|
911
|
+
const warnings = (activity) => captureLogs(() => assertPortableActorActivity(activity)).then((records) => records.map((r) => r.properties.activityId));
|
|
912
|
+
const other = compatibleId(owner.did, "/activities/1", "https://other.example");
|
|
913
|
+
assertEquals(await warnings(compatibleCreate(owner, void 0, { id: other })), [other.href]);
|
|
914
|
+
assertEquals(await warnings(compatibleCreate(owner)), []);
|
|
915
|
+
assertEquals(await warnings(compatibleCreate(owner, void 0, { id: parseIri(`ap://${owner.did}/activities/1`) })), []);
|
|
916
|
+
assertEquals(await warnings(compatibleCreate(owner, void 0, {
|
|
917
|
+
id: other,
|
|
918
|
+
actor: parseIri(`ap://${owner.did}/actor`)
|
|
919
|
+
})), []);
|
|
920
|
+
});
|
|
921
|
+
test("an unsigned activity with a malformed compatible ID is rejected", async () => {
|
|
922
|
+
const owner = await didKey();
|
|
923
|
+
const ctx = createTestFederation().createContext(new URL("https://example.com/"));
|
|
924
|
+
const { bodies } = await capture(() => assertRejects(() => ctx.sendActivity([rsaKey, sender(owner)], recipient, new Create({
|
|
925
|
+
id: new URL(compatibleId(owner.did, "/activities/1").href + "?@gateway=https%3A%2F%2Fgw.example"),
|
|
926
|
+
actor: new URL("https://example.com/users/alice")
|
|
927
|
+
})), TypeError, "malformed FEP-ef61 portable ID"));
|
|
928
|
+
assertEquals(bodies.length, 0);
|
|
929
|
+
});
|
|
930
|
+
//#endregion
|
|
931
|
+
export {};
|