@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,686 @@
|
|
|
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 { r as createRequestContext, t as createInboxContext } from "../context-8Tu9kea0.mjs";
|
|
6
|
+
import { t as MemoryKvStore } from "../kv-DCHkaJ9-.mjs";
|
|
7
|
+
import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
|
|
8
|
+
import "../std__assert-DvMDRPes.mjs";
|
|
9
|
+
import { t as assertRejects } from "../assert_rejects-CTptdYck.mjs";
|
|
10
|
+
import { r as getVerifiedKeyOwnerEvidence } from "../key-owner-evidence-APB6NpSk.mjs";
|
|
11
|
+
import { p as signJsonLd } from "../ld-CzaP-hJl.mjs";
|
|
12
|
+
import { f as verifyRequest, l as signRequest, p as verifyRequestDetailed } from "../http-siciWkmx.mjs";
|
|
13
|
+
import { a as rsaPrivateKey3, i as rsaPrivateKey2, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2 } from "../keys-DIJNHW-Y.mjs";
|
|
14
|
+
import { f as handleInbox, n as FederationImpl, o as createFederation } from "../middleware-D7_LO-Vs.mjs";
|
|
15
|
+
import { n as getKeyOwner, t as doesActorOwnKey } from "../owner-DYgfTFHy.mjs";
|
|
16
|
+
import { t as ActivityListenerSet } from "../activity-listener-C-BSbKI7.mjs";
|
|
17
|
+
import { t as KvKeyCache } from "../keycache-Da8A8mZa.mjs";
|
|
18
|
+
import { Create, CryptographicKey, Person } from "@fedify/vocab";
|
|
19
|
+
import { FetchError, encodeMultibase, exportDidKey, parseIri } from "@fedify/vocab-runtime";
|
|
20
|
+
import { mockDocumentLoader, test } from "@fedify/fixture";
|
|
21
|
+
import serialize from "json-canon";
|
|
22
|
+
//#region src/federation/portable-gateway-keys.test.ts
|
|
23
|
+
const did = await exportDidKey(ed25519PublicKey.publicKey);
|
|
24
|
+
const didKeyId = new URL(`${did}#${did.slice(8)}`);
|
|
25
|
+
const actorId = parseIri(`ap+ef61://${did}/actors/alice`);
|
|
26
|
+
const gateway = "https://example.com";
|
|
27
|
+
const otherGateway = "https://other.example";
|
|
28
|
+
const compatibleActorId = (origin) => `${origin}/.well-known/apgateway/${did}/actors/alice`;
|
|
29
|
+
const gatewayKeyId = new URL(`${compatibleActorId(gateway)}#main-key`);
|
|
30
|
+
const ed25519GatewayKeyPair = await crypto.subtle.generateKey("Ed25519", true, ["sign", "verify"]);
|
|
31
|
+
const rsaGatewayKeyPair = {
|
|
32
|
+
privateKey: rsaPrivateKey2,
|
|
33
|
+
publicKey: rsaPublicKey2.publicKey
|
|
34
|
+
};
|
|
35
|
+
const portableContext = [
|
|
36
|
+
"https://www.w3.org/ns/activitystreams",
|
|
37
|
+
"https://w3id.org/security/data-integrity/v1",
|
|
38
|
+
"https://w3id.org/fep/ef61"
|
|
39
|
+
];
|
|
40
|
+
const recipient = {
|
|
41
|
+
id: new URL("https://example.com/users/bob"),
|
|
42
|
+
inboxId: new URL("https://example.com/inbox")
|
|
43
|
+
};
|
|
44
|
+
async function sign(document) {
|
|
45
|
+
const proofConfig = {
|
|
46
|
+
"@context": document["@context"],
|
|
47
|
+
type: "DataIntegrityProof",
|
|
48
|
+
cryptosuite: "eddsa-jcs-2022",
|
|
49
|
+
verificationMethod: didKeyId.href,
|
|
50
|
+
proofPurpose: "assertionMethod",
|
|
51
|
+
created: "2023-02-24T23:36:38Z"
|
|
52
|
+
};
|
|
53
|
+
const encoder = new TextEncoder();
|
|
54
|
+
const digest = /* @__PURE__ */ new Uint8Array(64);
|
|
55
|
+
digest.set(new Uint8Array(await crypto.subtle.digest("SHA-256", encoder.encode(serialize(proofConfig)))), 0);
|
|
56
|
+
digest.set(new Uint8Array(await crypto.subtle.digest("SHA-256", encoder.encode(serialize(document)))), 32);
|
|
57
|
+
const signature = await crypto.subtle.sign("Ed25519", ed25519PrivateKey, digest);
|
|
58
|
+
return {
|
|
59
|
+
...document,
|
|
60
|
+
proof: {
|
|
61
|
+
...proofConfig,
|
|
62
|
+
proofValue: new TextDecoder().decode(encodeMultibase("base58btc", new Uint8Array(signature)))
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function createTestFederation(mapper) {
|
|
67
|
+
const federation = new FederationImpl({
|
|
68
|
+
kv: new MemoryKvStore(),
|
|
69
|
+
manuallyStartQueue: true,
|
|
70
|
+
contextLoaderFactory: () => mockDocumentLoader,
|
|
71
|
+
documentLoaderFactory: () => mockDocumentLoader
|
|
72
|
+
});
|
|
73
|
+
const setters = federation.setActorDispatcher("/users/{identifier}", () => null).setKeyPairsDispatcher(() => [rsaGatewayKeyPair, ed25519GatewayKeyPair]);
|
|
74
|
+
if (mapper != null) setters.mapPortableActorId((_ctx, id) => mapper(id));
|
|
75
|
+
return federation;
|
|
76
|
+
}
|
|
77
|
+
function portableCreate(id = "1") {
|
|
78
|
+
return new Create({
|
|
79
|
+
id: parseIri(`ap+ef61://${did}/activities/${id}`),
|
|
80
|
+
actor: actorId,
|
|
81
|
+
object: new URL("https://example.com/notes/1")
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
async function signedPortableCreate(id = "1") {
|
|
85
|
+
const json = await portableCreate(id).toJsonLd({
|
|
86
|
+
format: "compact",
|
|
87
|
+
context: portableContext,
|
|
88
|
+
contextLoader: mockDocumentLoader
|
|
89
|
+
});
|
|
90
|
+
return await Create.fromJsonLd(await sign(json), {
|
|
91
|
+
contextLoader: mockDocumentLoader,
|
|
92
|
+
documentLoader: mockDocumentLoader
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
async function capture(run) {
|
|
96
|
+
const bodies = [];
|
|
97
|
+
const requests = [];
|
|
98
|
+
esm_default.spyGlobal();
|
|
99
|
+
try {
|
|
100
|
+
esm_default.post(recipient.inboxId.href, async (cl) => {
|
|
101
|
+
const body = await cl.request.text();
|
|
102
|
+
requests.push(new Request(cl.request.url, {
|
|
103
|
+
method: cl.request.method,
|
|
104
|
+
headers: cl.request.headers,
|
|
105
|
+
body
|
|
106
|
+
}));
|
|
107
|
+
bodies.push(JSON.parse(body));
|
|
108
|
+
return new Response(null, { status: 202 });
|
|
109
|
+
});
|
|
110
|
+
await run();
|
|
111
|
+
} finally {
|
|
112
|
+
esm_default.hardReset();
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
bodies,
|
|
116
|
+
requests
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function signatureKeyId(request) {
|
|
120
|
+
return request.headers.get("Signature-Input")?.match(/keyid="([^"]+)"/)?.[1] ?? request.headers.get("Signature")?.match(/keyId="([^"]+)"/)?.[1];
|
|
121
|
+
}
|
|
122
|
+
test("Context.getActorKeyPairs() derives gateway keys for portable actors", async () => {
|
|
123
|
+
const ctx = createTestFederation((id) => id === "alice" ? actorId : null).createContext(new URL(gateway));
|
|
124
|
+
const keys = await ctx.getActorKeyPairs("alice");
|
|
125
|
+
assertEquals(keys.map((k) => k.keyId.href), [gatewayKeyId.href, `${compatibleActorId(gateway)}#key-2`]);
|
|
126
|
+
for (const key of keys) {
|
|
127
|
+
assertEquals(key.cryptographicKey.id?.href, key.keyId.href);
|
|
128
|
+
assertEquals(key.cryptographicKey.ownerId?.href, actorId.href);
|
|
129
|
+
assertEquals(key.multikey.id?.href, key.keyId.href);
|
|
130
|
+
assertEquals(key.multikey.controllerId?.href, actorId.href);
|
|
131
|
+
}
|
|
132
|
+
assertEquals((await ctx.getActorKeyPairs("bob")).map((k) => [k.keyId.href, k.multikey.id?.href]), [["https://example.com/users/bob#main-key", "https://example.com/users/bob#multikey-1"], ["https://example.com/users/bob#key-2", "https://example.com/users/bob#multikey-2"]]);
|
|
133
|
+
const json = await new Person({
|
|
134
|
+
id: actorId,
|
|
135
|
+
gateways: [new URL(gateway)],
|
|
136
|
+
publicKey: keys[0].cryptographicKey,
|
|
137
|
+
assertionMethods: keys.map((k) => k.multikey)
|
|
138
|
+
}).toJsonLd({
|
|
139
|
+
format: "compact",
|
|
140
|
+
contextLoader: mockDocumentLoader
|
|
141
|
+
});
|
|
142
|
+
const assertionMethod = json.assertionMethod;
|
|
143
|
+
assertEquals(assertionMethod.map((k) => [k.id, k.controller]), [[gatewayKeyId.href, `ap+ef61://${did}/actors/alice`], [`${compatibleActorId(gateway)}#key-2`, `ap+ef61://${did}/actors/alice`]]);
|
|
144
|
+
assertEquals(json.publicKey.id, gatewayKeyId.href);
|
|
145
|
+
});
|
|
146
|
+
test("Context.getActorKeyPairs() accepts compatible IDs as portable actor IDs", async () => {
|
|
147
|
+
const published = new URL(compatibleActorId(otherGateway));
|
|
148
|
+
const [key] = await createTestFederation(() => published).createContext(new URL(gateway)).getActorKeyPairs("alice");
|
|
149
|
+
assertEquals(key.keyId.href, gatewayKeyId.href);
|
|
150
|
+
assertEquals(key.cryptographicKey.ownerId?.href, published.href);
|
|
151
|
+
});
|
|
152
|
+
test("Context.getActorKeyPairs() propagates portable actor ID mapper errors", async () => {
|
|
153
|
+
for (const id of [new URL(`${actorId.href}#me`), new URL("https://example.com/users/alice")]) {
|
|
154
|
+
const ctx = createTestFederation(() => id).createContext(new URL(gateway));
|
|
155
|
+
await assertRejects(() => ctx.getActorKeyPairs("alice"), TypeError);
|
|
156
|
+
}
|
|
157
|
+
const ctx = createTestFederation(() => {
|
|
158
|
+
throw new RangeError("mapper failed");
|
|
159
|
+
}).createContext(new URL(gateway));
|
|
160
|
+
await assertRejects(() => ctx.getActorKeyPairs("alice"), RangeError);
|
|
161
|
+
const federation = new FederationImpl({ kv: new MemoryKvStore() });
|
|
162
|
+
federation.setActorDispatcher("/users/{identifier}", () => null).setKeyPairsDispatcher(() => {
|
|
163
|
+
throw new Error("dispatcher failed");
|
|
164
|
+
}).mapPortableActorId(() => actorId);
|
|
165
|
+
assertEquals(await federation.createContext(new URL(gateway)).getActorKeyPairs("alice"), []);
|
|
166
|
+
});
|
|
167
|
+
test("Context.sendActivity() signs requests with gateway keys for portable actors", async () => {
|
|
168
|
+
const ctx = createTestFederation(() => actorId).createContext(new URL(gateway));
|
|
169
|
+
const activity = await signedPortableCreate();
|
|
170
|
+
const { bodies, requests } = await capture(() => ctx.sendActivity({ identifier: "alice" }, recipient, activity));
|
|
171
|
+
assertEquals(bodies.length, 1);
|
|
172
|
+
assertEquals(signatureKeyId(requests[0]), gatewayKeyId.href);
|
|
173
|
+
const proof = bodies[0].proof;
|
|
174
|
+
assertEquals(Array.isArray(proof), false);
|
|
175
|
+
assertEquals(proof.verificationMethod, didKeyId.href);
|
|
176
|
+
assertEquals(bodies[0].signature, void 0);
|
|
177
|
+
});
|
|
178
|
+
test("Context.sendActivity() refuses portable activities gateway keys would sign", async () => {
|
|
179
|
+
const ctx = createTestFederation(() => actorId).createContext(new URL(gateway));
|
|
180
|
+
await assertRejects(() => ctx.sendActivity({ identifier: "alice" }, recipient, portableCreate()), TypeError);
|
|
181
|
+
const httpActivity = new Create({
|
|
182
|
+
id: new URL("https://example.com/activities/1"),
|
|
183
|
+
actor: actorId,
|
|
184
|
+
object: new URL("https://example.com/notes/1")
|
|
185
|
+
});
|
|
186
|
+
await assertRejects(() => ctx.sendActivity({ identifier: "alice" }, recipient, httpActivity), TypeError);
|
|
187
|
+
await assertRejects(() => ctx.sendActivity({
|
|
188
|
+
keyId: new URL("https://example.com/key"),
|
|
189
|
+
privateKey: ed25519PrivateKey
|
|
190
|
+
}, recipient, portableCreate()), TypeError);
|
|
191
|
+
const { bodies } = await capture(() => ctx.sendActivity([{
|
|
192
|
+
keyId: gatewayKeyId,
|
|
193
|
+
privateKey: rsaPrivateKey2
|
|
194
|
+
}, {
|
|
195
|
+
keyId: didKeyId,
|
|
196
|
+
privateKey: ed25519PrivateKey
|
|
197
|
+
}], recipient, portableCreate()));
|
|
198
|
+
assertEquals(bodies[0].proof.verificationMethod, didKeyId.href);
|
|
199
|
+
});
|
|
200
|
+
test("Context.sendActivity() never signs documents with gateway keys", async () => {
|
|
201
|
+
const ctx = createTestFederation().createContext(new URL(gateway));
|
|
202
|
+
const keys = [{
|
|
203
|
+
keyId: gatewayKeyId,
|
|
204
|
+
privateKey: rsaPrivateKey2
|
|
205
|
+
}, {
|
|
206
|
+
keyId: new URL(`${compatibleActorId(gateway)}#key-2`),
|
|
207
|
+
privateKey: ed25519GatewayKeyPair.privateKey
|
|
208
|
+
}];
|
|
209
|
+
const activity = new Create({
|
|
210
|
+
id: new URL("https://example.com/activities/1"),
|
|
211
|
+
actor: new URL("https://example.com/users/bob"),
|
|
212
|
+
object: new URL("https://example.com/notes/1")
|
|
213
|
+
});
|
|
214
|
+
const { bodies, requests } = await capture(() => ctx.sendActivity(keys, recipient, activity));
|
|
215
|
+
assertEquals(signatureKeyId(requests[0]), gatewayKeyId.href);
|
|
216
|
+
assertEquals(bodies[0].proof, void 0);
|
|
217
|
+
assertEquals(bodies[0].signature, void 0);
|
|
218
|
+
});
|
|
219
|
+
test("Context.sendActivity() never signs documents with keys at ap: URIs", async () => {
|
|
220
|
+
const ctx = createTestFederation().createContext(new URL(gateway));
|
|
221
|
+
const keys = [{
|
|
222
|
+
keyId: parseIri(`${actorId.href}#main-key`),
|
|
223
|
+
privateKey: rsaPrivateKey2
|
|
224
|
+
}, {
|
|
225
|
+
keyId: parseIri(`${actorId.href}#key-2`),
|
|
226
|
+
privateKey: ed25519GatewayKeyPair.privateKey
|
|
227
|
+
}];
|
|
228
|
+
const activity = new Create({
|
|
229
|
+
id: new URL("https://example.com/activities/1"),
|
|
230
|
+
actor: new URL("https://example.com/users/bob"),
|
|
231
|
+
object: new URL("https://example.com/notes/1")
|
|
232
|
+
});
|
|
233
|
+
const { bodies } = await capture(() => ctx.sendActivity(keys, recipient, activity));
|
|
234
|
+
assertEquals(bodies[0].proof, void 0);
|
|
235
|
+
assertEquals(bodies[0].signature, void 0);
|
|
236
|
+
});
|
|
237
|
+
test("Context.getDocumentLoader() signs requests with gateway keys for portable actors", async () => {
|
|
238
|
+
const documentLoader = await createTestFederation(() => actorId).createContext(new URL(gateway)).getDocumentLoader({ identifier: "alice" });
|
|
239
|
+
let keyId;
|
|
240
|
+
esm_default.spyGlobal();
|
|
241
|
+
try {
|
|
242
|
+
esm_default.get("https://example.com/notes/1", (cl) => {
|
|
243
|
+
keyId = signatureKeyId(cl.request);
|
|
244
|
+
return new Response(JSON.stringify({ id: cl.request.url }), { headers: { "Content-Type": "application/activity+json" } });
|
|
245
|
+
});
|
|
246
|
+
await documentLoader("https://example.com/notes/1");
|
|
247
|
+
} finally {
|
|
248
|
+
esm_default.hardReset();
|
|
249
|
+
}
|
|
250
|
+
assertEquals(keyId, gatewayKeyId.href);
|
|
251
|
+
});
|
|
252
|
+
async function actorDocument(gateways, id = actorId, signed = true) {
|
|
253
|
+
const keys = await createTestFederation(() => id).createContext(new URL(gateway)).getActorKeyPairs("alice");
|
|
254
|
+
const json = await new Person({
|
|
255
|
+
id,
|
|
256
|
+
inbox: parseIri(`${id.href}/inbox`),
|
|
257
|
+
gateways: gateways.map((g) => new URL(g)),
|
|
258
|
+
publicKey: keys[0].cryptographicKey,
|
|
259
|
+
assertionMethods: keys.map((k) => k.multikey)
|
|
260
|
+
}).toJsonLd({
|
|
261
|
+
format: "compact",
|
|
262
|
+
contextLoader: mockDocumentLoader
|
|
263
|
+
});
|
|
264
|
+
json["@context"] = [...json["@context"], "https://w3id.org/security/data-integrity/v1"];
|
|
265
|
+
return signed ? await sign(json) : json;
|
|
266
|
+
}
|
|
267
|
+
function createLoader(responses) {
|
|
268
|
+
return (url) => {
|
|
269
|
+
const document = responses[url.replace(/#.*$/, "")];
|
|
270
|
+
if (document == null) return mockDocumentLoader(url);
|
|
271
|
+
return Promise.resolve({
|
|
272
|
+
contextUrl: null,
|
|
273
|
+
documentUrl: url,
|
|
274
|
+
document: structuredClone(document)
|
|
275
|
+
});
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
async function deliver(body, gateways, { kv = new MemoryKvStore(), httpSignature = true, actor, documentLoader, keyId = gatewayKeyId } = {}) {
|
|
279
|
+
documentLoader ??= createLoader({ [compatibleActorId(gateway)]: actor ?? await actorDocument(gateways) });
|
|
280
|
+
const unsigned = new Request("https://local.example/inbox", {
|
|
281
|
+
method: "POST",
|
|
282
|
+
body: JSON.stringify(body),
|
|
283
|
+
headers: { "Content-Type": "application/activity+json" }
|
|
284
|
+
});
|
|
285
|
+
const request = httpSignature ? await signRequest(unsigned, rsaPrivateKey2, keyId) : unsigned;
|
|
286
|
+
const context = createRequestContext({
|
|
287
|
+
federation: createFederation({ kv: new MemoryKvStore() }),
|
|
288
|
+
request,
|
|
289
|
+
url: new URL(request.url),
|
|
290
|
+
data: void 0,
|
|
291
|
+
documentLoader,
|
|
292
|
+
contextLoader: mockDocumentLoader
|
|
293
|
+
});
|
|
294
|
+
let dispatched = 0;
|
|
295
|
+
const inboxListeners = new ActivityListenerSet();
|
|
296
|
+
inboxListeners.add(Create, () => {
|
|
297
|
+
dispatched++;
|
|
298
|
+
});
|
|
299
|
+
const response = await handleInbox(request, {
|
|
300
|
+
recipient: null,
|
|
301
|
+
context,
|
|
302
|
+
inboxContextFactory() {
|
|
303
|
+
return createInboxContext({
|
|
304
|
+
...context,
|
|
305
|
+
clone: void 0
|
|
306
|
+
});
|
|
307
|
+
},
|
|
308
|
+
kv,
|
|
309
|
+
kvPrefixes: {
|
|
310
|
+
activityIdempotence: ["_fedify", "activityIdempotence"],
|
|
311
|
+
publicKey: ["_fedify", "publicKey"],
|
|
312
|
+
acceptSignatureNonce: ["_fedify", "acceptSignatureNonce"]
|
|
313
|
+
},
|
|
314
|
+
actorDispatcher() {
|
|
315
|
+
return null;
|
|
316
|
+
},
|
|
317
|
+
inboxListeners,
|
|
318
|
+
onNotFound() {
|
|
319
|
+
return new Response("Not found", { status: 404 });
|
|
320
|
+
},
|
|
321
|
+
signatureTimeWindow: { minutes: 5 },
|
|
322
|
+
skipSignatureVerification: false
|
|
323
|
+
});
|
|
324
|
+
lastResponseText = await response.text();
|
|
325
|
+
return {
|
|
326
|
+
status: response.status,
|
|
327
|
+
dispatched
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
let lastResponseText = "";
|
|
331
|
+
async function createJson(id) {
|
|
332
|
+
return await portableCreate(id).toJsonLd({
|
|
333
|
+
format: "compact",
|
|
334
|
+
context: portableContext,
|
|
335
|
+
contextLoader: mockDocumentLoader
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
test("handleInbox() requires proofs of portable actors despite gateway signatures", async () => {
|
|
339
|
+
assertEquals(await deliver(await createJson("unsigned"), [gateway]), {
|
|
340
|
+
status: 401,
|
|
341
|
+
dispatched: 0
|
|
342
|
+
});
|
|
343
|
+
assertEquals(await deliver({
|
|
344
|
+
...await sign(await createJson("tampered")),
|
|
345
|
+
object: "https://remote.example/notes/2"
|
|
346
|
+
}, [gateway]), {
|
|
347
|
+
status: 401,
|
|
348
|
+
dispatched: 0
|
|
349
|
+
});
|
|
350
|
+
assertEquals(await deliver(await sign(await createJson("signed")), [gateway]), {
|
|
351
|
+
status: 202,
|
|
352
|
+
dispatched: 1
|
|
353
|
+
});
|
|
354
|
+
});
|
|
355
|
+
const apKeyId = parseIri(`${actorId.href}?@gateway=${encodeURIComponent(gateway)}#main-key`);
|
|
356
|
+
async function apActorDocument() {
|
|
357
|
+
const json = await new Person({
|
|
358
|
+
id: actorId,
|
|
359
|
+
inbox: parseIri(`${actorId.href}/inbox`),
|
|
360
|
+
gateways: [new URL(gateway)],
|
|
361
|
+
publicKey: new CryptographicKey({
|
|
362
|
+
id: parseIri(`${actorId.href}#main-key`),
|
|
363
|
+
owner: actorId,
|
|
364
|
+
publicKey: rsaPublicKey2.publicKey
|
|
365
|
+
})
|
|
366
|
+
}).toJsonLd({
|
|
367
|
+
format: "compact",
|
|
368
|
+
contextLoader: mockDocumentLoader
|
|
369
|
+
});
|
|
370
|
+
json["@context"] = [...json["@context"], "https://w3id.org/security/data-integrity/v1"];
|
|
371
|
+
return await sign(json);
|
|
372
|
+
}
|
|
373
|
+
test("handleInbox() requires proofs of portable actors despite signatures with keys at ap: key IDs", async () => {
|
|
374
|
+
const fetched = [];
|
|
375
|
+
const inner = createLoader({ [compatibleActorId(gateway)]: await apActorDocument() });
|
|
376
|
+
const documentLoader = (url, options) => {
|
|
377
|
+
fetched.push(url);
|
|
378
|
+
return inner(url, options);
|
|
379
|
+
};
|
|
380
|
+
assertEquals(await deliver(await createJson("unsigned"), [gateway], {
|
|
381
|
+
documentLoader,
|
|
382
|
+
keyId: apKeyId
|
|
383
|
+
}), {
|
|
384
|
+
status: 401,
|
|
385
|
+
dispatched: 0
|
|
386
|
+
});
|
|
387
|
+
assertEquals(fetched.some((url) => url.startsWith(compatibleActorId(gateway))), true);
|
|
388
|
+
assertEquals(await deliver(await sign(await createJson("signed")), [gateway], {
|
|
389
|
+
documentLoader,
|
|
390
|
+
keyId: apKeyId
|
|
391
|
+
}), {
|
|
392
|
+
status: 202,
|
|
393
|
+
dispatched: 1
|
|
394
|
+
});
|
|
395
|
+
});
|
|
396
|
+
test("RequestContext.getSignedKeyOwner() resolves keys at ap: key IDs", async () => {
|
|
397
|
+
const documentLoader = createLoader({ [compatibleActorId(gateway)]: await apActorDocument() });
|
|
398
|
+
const federation = new FederationImpl({
|
|
399
|
+
kv: new MemoryKvStore(),
|
|
400
|
+
contextLoaderFactory: () => mockDocumentLoader,
|
|
401
|
+
documentLoaderFactory: () => documentLoader
|
|
402
|
+
});
|
|
403
|
+
const request = await signRequest(new Request("https://local.example/notes/1", { headers: { Accept: "application/activity+json" } }), rsaPrivateKey2, apKeyId);
|
|
404
|
+
const ctx = federation.createContext(request, void 0);
|
|
405
|
+
assertEquals((await ctx.getSignedKey())?.id?.href, apKeyId.href);
|
|
406
|
+
assertEquals((await ctx.getSignedKeyOwner())?.id?.href, actorId.href);
|
|
407
|
+
});
|
|
408
|
+
test("handleInbox() fetches an unreachable gateway key once", async () => {
|
|
409
|
+
const fetched = [];
|
|
410
|
+
const documentLoader = (url) => {
|
|
411
|
+
if (url.replace(/#.*$/, "") !== compatibleActorId(gateway)) return mockDocumentLoader(url);
|
|
412
|
+
fetched.push(url);
|
|
413
|
+
return Promise.reject(new FetchError(url, "HTTP 503", new Response(null, { status: 503 })));
|
|
414
|
+
};
|
|
415
|
+
const kv = new MemoryKvStore();
|
|
416
|
+
for (let i = 0; i < 3; i++) {
|
|
417
|
+
const { status } = await deliver(await createJson(`unreachable-${i}`), [gateway], {
|
|
418
|
+
kv,
|
|
419
|
+
documentLoader
|
|
420
|
+
});
|
|
421
|
+
assertEquals(status, 401);
|
|
422
|
+
}
|
|
423
|
+
assertEquals(fetched.length, 1);
|
|
424
|
+
});
|
|
425
|
+
test("handleInbox() does not need gateway signatures for portable actors", async () => {
|
|
426
|
+
assertEquals(await deliver(await sign(await createJson("signed")), [otherGateway]), {
|
|
427
|
+
status: 202,
|
|
428
|
+
dispatched: 1
|
|
429
|
+
});
|
|
430
|
+
assertEquals(await deliver(await createJson("unsigned"), [otherGateway]), {
|
|
431
|
+
status: 401,
|
|
432
|
+
dispatched: 0
|
|
433
|
+
});
|
|
434
|
+
});
|
|
435
|
+
test("handleInbox() does not take Linked Data Signatures for portable actors", async () => {
|
|
436
|
+
const kv = new MemoryKvStore();
|
|
437
|
+
await new KvKeyCache(kv, ["_fedify", "publicKey"]).set(rsaPublicKey2.id, new CryptographicKey({
|
|
438
|
+
id: rsaPublicKey2.id,
|
|
439
|
+
owner: actorId,
|
|
440
|
+
publicKey: rsaPublicKey2.publicKey
|
|
441
|
+
}));
|
|
442
|
+
const ldSign = (document) => signJsonLd(document, rsaPrivateKey2, rsaPublicKey2.id, { contextLoader: mockDocumentLoader });
|
|
443
|
+
assertEquals(await deliver(await ldSign(await createJson("ld-only")), [gateway], {
|
|
444
|
+
kv,
|
|
445
|
+
httpSignature: false
|
|
446
|
+
}), {
|
|
447
|
+
status: 401,
|
|
448
|
+
dispatched: 0
|
|
449
|
+
});
|
|
450
|
+
assertEquals(lastResponseText, "Failed to verify the request signature.");
|
|
451
|
+
});
|
|
452
|
+
const otherDid = await exportDidKey((await crypto.subtle.generateKey("Ed25519", true, ["sign", "verify"])).publicKey);
|
|
453
|
+
function compatibleCreateJson(id, activityDid = did) {
|
|
454
|
+
return {
|
|
455
|
+
"@context": portableContext,
|
|
456
|
+
id: `${gateway}/.well-known/apgateway/${activityDid}/activities/${id}`,
|
|
457
|
+
type: "Create",
|
|
458
|
+
actor: compatibleActorId(gateway),
|
|
459
|
+
object: "https://example.com/notes/1"
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
test("handleInbox() does not trust compatible-ID actors by web origin", async () => {
|
|
463
|
+
const id = new URL(compatibleActorId(gateway));
|
|
464
|
+
for (const signed of [true, false]) {
|
|
465
|
+
const actor = await actorDocument([gateway], id, signed);
|
|
466
|
+
assertEquals(await deliver(compatibleCreateJson(`unsigned-${signed}`), [gateway], { actor }), {
|
|
467
|
+
status: 401,
|
|
468
|
+
dispatched: 0
|
|
469
|
+
}, `actor document signed: ${signed}`);
|
|
470
|
+
}
|
|
471
|
+
assertEquals(await deliver(await sign(compatibleCreateJson("signed")), [gateway], { actor: await actorDocument([gateway], id) }), {
|
|
472
|
+
status: 202,
|
|
473
|
+
dispatched: 1
|
|
474
|
+
});
|
|
475
|
+
});
|
|
476
|
+
test("handleInbox() requires proofs of compatible-ID actors", async () => {
|
|
477
|
+
assertEquals(await deliver(compatibleCreateJson("unsigned"), [gateway]), {
|
|
478
|
+
status: 401,
|
|
479
|
+
dispatched: 0
|
|
480
|
+
});
|
|
481
|
+
assertEquals(await deliver(await sign(compatibleCreateJson("signed")), [gateway]), {
|
|
482
|
+
status: 202,
|
|
483
|
+
dispatched: 1
|
|
484
|
+
});
|
|
485
|
+
assertEquals(await deliver(await sign(compatibleCreateJson("unsigned-request")), [gateway], { httpSignature: false }), {
|
|
486
|
+
status: 202,
|
|
487
|
+
dispatched: 1
|
|
488
|
+
});
|
|
489
|
+
});
|
|
490
|
+
test("handleInbox() checks the DIDs of compatible activity IDs", async () => {
|
|
491
|
+
for (const actor of [actorId.href, compatibleActorId(gateway)]) assertEquals(await deliver(await sign({
|
|
492
|
+
...compatibleCreateJson("forged", otherDid),
|
|
493
|
+
actor
|
|
494
|
+
}), [gateway]), {
|
|
495
|
+
status: 401,
|
|
496
|
+
dispatched: 0
|
|
497
|
+
}, actor);
|
|
498
|
+
assertEquals(await deliver(await sign({
|
|
499
|
+
...compatibleCreateJson("forged-ap"),
|
|
500
|
+
id: `ap://${otherDid}/activities/forged-ap`
|
|
501
|
+
}), [gateway]), {
|
|
502
|
+
status: 401,
|
|
503
|
+
dispatched: 0
|
|
504
|
+
});
|
|
505
|
+
assertEquals(await deliver(await sign({
|
|
506
|
+
...compatibleCreateJson("malformed"),
|
|
507
|
+
id: `https://user@example.com/.well-known/apgateway/${did}/activities/2`
|
|
508
|
+
}), [gateway]), {
|
|
509
|
+
status: 401,
|
|
510
|
+
dispatched: 0
|
|
511
|
+
});
|
|
512
|
+
});
|
|
513
|
+
function countingLoader(responses) {
|
|
514
|
+
const inner = createLoader(responses);
|
|
515
|
+
let count = 0;
|
|
516
|
+
return {
|
|
517
|
+
documentLoader(url, options) {
|
|
518
|
+
if (url.replace(/#.*$/, "") === compatibleActorId(gateway)) count++;
|
|
519
|
+
return inner(url, options);
|
|
520
|
+
},
|
|
521
|
+
fetched: () => count
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
async function signedRequest(keyId = gatewayKeyId, privateKey = rsaPrivateKey2, spec) {
|
|
525
|
+
return await signRequest(new Request("https://local.example/notes/1", { headers: { Accept: "application/activity+json" } }), privateKey, keyId, spec == null ? {} : { spec });
|
|
526
|
+
}
|
|
527
|
+
test("handleInbox() fetches the actor of a gateway key once", async () => {
|
|
528
|
+
for (const [keyId, actor] of [[gatewayKeyId, await actorDocument([gateway])], [apKeyId, await apActorDocument()]]) {
|
|
529
|
+
const { documentLoader, fetched } = countingLoader({ [compatibleActorId(gateway)]: actor });
|
|
530
|
+
const kv = new MemoryKvStore();
|
|
531
|
+
for (let i = 0; i < 2; i++) {
|
|
532
|
+
assertEquals(await deliver(await createJson(`once-${i}`), [gateway], {
|
|
533
|
+
kv,
|
|
534
|
+
documentLoader,
|
|
535
|
+
keyId
|
|
536
|
+
}), {
|
|
537
|
+
status: 401,
|
|
538
|
+
dispatched: 0
|
|
539
|
+
});
|
|
540
|
+
assertEquals(fetched(), 1);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
});
|
|
544
|
+
test("RequestContext.getSignedKeyOwner() does not fetch the actor of a gateway key again", async () => {
|
|
545
|
+
for (const [keyId, actor, gateways] of [[
|
|
546
|
+
gatewayKeyId,
|
|
547
|
+
await actorDocument([gateway, otherGateway]),
|
|
548
|
+
[`${gateway}/`, `${otherGateway}/`]
|
|
549
|
+
], [
|
|
550
|
+
apKeyId,
|
|
551
|
+
await apActorDocument(),
|
|
552
|
+
[`${gateway}/`]
|
|
553
|
+
]]) {
|
|
554
|
+
const { documentLoader, fetched } = countingLoader({ [compatibleActorId(gateway)]: actor });
|
|
555
|
+
const ctx = new FederationImpl({
|
|
556
|
+
kv: new MemoryKvStore(),
|
|
557
|
+
contextLoaderFactory: () => mockDocumentLoader,
|
|
558
|
+
documentLoaderFactory: () => documentLoader
|
|
559
|
+
}).createContext(await signedRequest(keyId), void 0);
|
|
560
|
+
assertEquals((await ctx.getSignedKey())?.id?.href, keyId.href);
|
|
561
|
+
assertEquals(fetched(), 1);
|
|
562
|
+
const owner = await ctx.getSignedKeyOwner();
|
|
563
|
+
assertEquals(owner?.id?.href, actorId.href);
|
|
564
|
+
assertEquals(owner?.gateways.map((g) => g.href), [...gateways]);
|
|
565
|
+
assertEquals(fetched(), 1);
|
|
566
|
+
}
|
|
567
|
+
});
|
|
568
|
+
test("getKeyOwner() reuses the actor of a cached gateway key", async () => {
|
|
569
|
+
for (const spec of [void 0, "rfc9421"]) {
|
|
570
|
+
const { documentLoader, fetched } = countingLoader({ [compatibleActorId(gateway)]: await actorDocument([gateway]) });
|
|
571
|
+
const options = {
|
|
572
|
+
documentLoader,
|
|
573
|
+
contextLoader: mockDocumentLoader,
|
|
574
|
+
keyCache: new KvKeyCache(new MemoryKvStore(), ["pk"])
|
|
575
|
+
};
|
|
576
|
+
const fresh = await verifyRequest(await signedRequest(gatewayKeyId, rsaPrivateKey2, spec), options);
|
|
577
|
+
const cached = await verifyRequest(await signedRequest(gatewayKeyId, rsaPrivateKey2, spec), options);
|
|
578
|
+
assertEquals(fetched(), 1);
|
|
579
|
+
for (const key of [fresh, cached]) assertEquals((await getKeyOwner(key, options))?.id?.href, actorId.href);
|
|
580
|
+
assertEquals(fetched(), 1);
|
|
581
|
+
}
|
|
582
|
+
});
|
|
583
|
+
test("doesActorOwnKey() checks the actor of a verified gateway key without fetching", async () => {
|
|
584
|
+
const { documentLoader, fetched } = countingLoader({ [compatibleActorId(gateway)]: await actorDocument([gateway]) });
|
|
585
|
+
const options = {
|
|
586
|
+
documentLoader,
|
|
587
|
+
contextLoader: mockDocumentLoader
|
|
588
|
+
};
|
|
589
|
+
const key = await verifyRequest(await signedRequest(), options);
|
|
590
|
+
assertEquals(fetched(), 1);
|
|
591
|
+
assertEquals(await doesActorOwnKey(portableCreate(), key, options), true);
|
|
592
|
+
assertEquals(await doesActorOwnKey(new Create({
|
|
593
|
+
id: parseIri(`ap+ef61://${otherDid}/activities/1`),
|
|
594
|
+
actor: parseIri(`ap+ef61://${otherDid}/actors/alice`)
|
|
595
|
+
}), key, options), false);
|
|
596
|
+
assertEquals(fetched(), 1);
|
|
597
|
+
});
|
|
598
|
+
test("doesActorOwnKey() and getKeyOwner() fully check other gateway keys", async () => {
|
|
599
|
+
const { documentLoader, fetched } = countingLoader({ [compatibleActorId(gateway)]: await actorDocument([gateway]) });
|
|
600
|
+
const options = {
|
|
601
|
+
documentLoader,
|
|
602
|
+
contextLoader: mockDocumentLoader
|
|
603
|
+
};
|
|
604
|
+
const verified = await verifyRequest(await signedRequest(), options);
|
|
605
|
+
assertEquals(fetched(), 1);
|
|
606
|
+
const others = [new CryptographicKey({
|
|
607
|
+
id: gatewayKeyId,
|
|
608
|
+
owner: actorId,
|
|
609
|
+
publicKey: rsaPublicKey2.publicKey
|
|
610
|
+
}), verified.clone({})];
|
|
611
|
+
let expected = 1;
|
|
612
|
+
for (const key of others) {
|
|
613
|
+
assertEquals(await doesActorOwnKey(portableCreate(), key, options), true);
|
|
614
|
+
assertEquals(fetched(), ++expected);
|
|
615
|
+
assertEquals((await getKeyOwner(key, options))?.id?.href, actorId.href);
|
|
616
|
+
assertEquals(fetched(), ++expected);
|
|
617
|
+
}
|
|
618
|
+
const mutated = await verifyRequest(await signedRequest(), options);
|
|
619
|
+
mutated.ownerId.href = parseIri(`ap+ef61://${otherDid}/actors/alice`).href;
|
|
620
|
+
assertEquals(getVerifiedKeyOwnerEvidence(mutated), void 0);
|
|
621
|
+
});
|
|
622
|
+
test("verifyRequest() does not vouch for the owners of keys that fail to verify signatures", async () => {
|
|
623
|
+
const documentLoader = createLoader({ [compatibleActorId(gateway)]: await actorDocument([gateway]) });
|
|
624
|
+
const cachedKeys = [];
|
|
625
|
+
const keyCache = new class extends KvKeyCache {
|
|
626
|
+
compatibleKeyScope(scope) {
|
|
627
|
+
const scoped = super.compatibleKeyScope(scope);
|
|
628
|
+
return {
|
|
629
|
+
...scoped,
|
|
630
|
+
set(keyId, key, options) {
|
|
631
|
+
if (key instanceof CryptographicKey) cachedKeys.push(key);
|
|
632
|
+
return scoped.set(keyId, key, options);
|
|
633
|
+
}
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
}(new MemoryKvStore(), ["pk"]);
|
|
637
|
+
assertEquals((await verifyRequestDetailed(await signedRequest(gatewayKeyId, rsaPrivateKey3), {
|
|
638
|
+
documentLoader,
|
|
639
|
+
contextLoader: mockDocumentLoader,
|
|
640
|
+
keyCache
|
|
641
|
+
})).verified, false);
|
|
642
|
+
assertEquals(cachedKeys.length, 1);
|
|
643
|
+
assertEquals(getVerifiedKeyOwnerEvidence(cachedKeys[0]), void 0);
|
|
644
|
+
});
|
|
645
|
+
test("doesActorOwnKey() fully checks cached gateway keys cached without their actors", async () => {
|
|
646
|
+
const { documentLoader, fetched } = countingLoader({ [compatibleActorId(gateway)]: await actorDocument([gateway]) });
|
|
647
|
+
const kv = new MemoryKvStore();
|
|
648
|
+
const options = {
|
|
649
|
+
documentLoader,
|
|
650
|
+
contextLoader: mockDocumentLoader,
|
|
651
|
+
keyCache: new KvKeyCache(kv, ["pk"])
|
|
652
|
+
};
|
|
653
|
+
await verifyRequest(await signedRequest(), options);
|
|
654
|
+
assertEquals(fetched(), 1);
|
|
655
|
+
const entryKey = [
|
|
656
|
+
"pk",
|
|
657
|
+
"__compatible",
|
|
658
|
+
"httpSignature",
|
|
659
|
+
gatewayKeyId.href
|
|
660
|
+
];
|
|
661
|
+
const { owner: _, ...entry } = await kv.get(entryKey) ?? {};
|
|
662
|
+
await kv.set(entryKey, entry);
|
|
663
|
+
const key = await verifyRequest(await signedRequest(), options);
|
|
664
|
+
assertEquals(fetched(), 1);
|
|
665
|
+
assertEquals(getVerifiedKeyOwnerEvidence(key), void 0);
|
|
666
|
+
assertEquals(await doesActorOwnKey(portableCreate(), key, options), true);
|
|
667
|
+
assertEquals(fetched(), 2);
|
|
668
|
+
});
|
|
669
|
+
test("Owners of verified gateway keys expire with their cache entries", async () => {
|
|
670
|
+
const documentLoader = createLoader({ [compatibleActorId(gateway)]: await actorDocument([gateway]) });
|
|
671
|
+
const now = Temporal.Now.instant();
|
|
672
|
+
const keyCache = new KvKeyCache(new MemoryKvStore(), ["pk"], {
|
|
673
|
+
keyTtl: Temporal.Duration.from({ minutes: 5 }),
|
|
674
|
+
now: () => now
|
|
675
|
+
});
|
|
676
|
+
const key = await verifyRequest(await signedRequest(), {
|
|
677
|
+
documentLoader,
|
|
678
|
+
contextLoader: mockDocumentLoader,
|
|
679
|
+
keyCache
|
|
680
|
+
});
|
|
681
|
+
const deadline = now.add({ minutes: 5 });
|
|
682
|
+
assertEquals(getVerifiedKeyOwnerEvidence(key, deadline.subtract({ seconds: 1 }))?.expires, deadline);
|
|
683
|
+
assertEquals(getVerifiedKeyOwnerEvidence(key, deadline), void 0);
|
|
684
|
+
});
|
|
685
|
+
//#endregion
|
|
686
|
+
export {};
|