@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,697 @@
|
|
|
1
|
+
import "temporal-polyfill";
|
|
2
|
+
import "urlpattern-polyfill";
|
|
3
|
+
globalThis.addEventListener = () => {};
|
|
4
|
+
import { n as ed25519PrivateKey, r as ed25519PublicKey } from "../keys-DIJNHW-Y.mjs";
|
|
5
|
+
import { t as verifyPortableObject } from "../portable-collection-C74P1vtC.mjs";
|
|
6
|
+
import { Collection, CollectionPage, Note, OrderedCollection, Person, traverseCollection } from "@fedify/vocab";
|
|
7
|
+
import { FetchError, encodeMultibase, exportDidKey, preloadedContexts } from "@fedify/vocab-runtime";
|
|
8
|
+
import { test } from "@fedify/fixture";
|
|
9
|
+
import { deepStrictEqual, ok, strictEqual } from "node:assert/strict";
|
|
10
|
+
import serialize from "json-canon";
|
|
11
|
+
//#region src/sig/portable-collection.test.ts
|
|
12
|
+
const AS = "https://www.w3.org/ns/activitystreams#";
|
|
13
|
+
const context = [
|
|
14
|
+
"https://www.w3.org/ns/activitystreams",
|
|
15
|
+
"https://w3id.org/security/data-integrity/v1",
|
|
16
|
+
"https://w3id.org/fep/ef61"
|
|
17
|
+
];
|
|
18
|
+
const did = await exportDidKey(ed25519PublicKey.publicKey);
|
|
19
|
+
const keyId = `${did}#${did.slice(8)}`;
|
|
20
|
+
const otherKeyPair = await crypto.subtle.generateKey("Ed25519", true, ["sign", "verify"]);
|
|
21
|
+
const otherDid = await exportDidKey(otherKeyPair.publicKey);
|
|
22
|
+
const actorId = `ap://${did}/actor`;
|
|
23
|
+
const outboxId = `ap://${did}/actor/outbox`;
|
|
24
|
+
const gw1 = "https://gw1.example";
|
|
25
|
+
const gw2 = "https://gw2.example";
|
|
26
|
+
const evil = "https://evil.example";
|
|
27
|
+
function gatewayUrl(gateway, id) {
|
|
28
|
+
return `${gateway}/.well-known/apgateway/${id.replace(/^ap:\/\//, "")}`;
|
|
29
|
+
}
|
|
30
|
+
async function sign(document, privateKey = ed25519PrivateKey, verificationMethod = keyId) {
|
|
31
|
+
const proofConfig = {
|
|
32
|
+
"@context": document["@context"],
|
|
33
|
+
type: "DataIntegrityProof",
|
|
34
|
+
cryptosuite: "eddsa-jcs-2022",
|
|
35
|
+
verificationMethod,
|
|
36
|
+
proofPurpose: "assertionMethod",
|
|
37
|
+
created: "2023-02-24T23:36:38Z"
|
|
38
|
+
};
|
|
39
|
+
const encoder = new TextEncoder();
|
|
40
|
+
const proofDigest = await crypto.subtle.digest("SHA-256", encoder.encode(serialize(proofConfig)));
|
|
41
|
+
const messageDigest = await crypto.subtle.digest("SHA-256", encoder.encode(serialize(document)));
|
|
42
|
+
const digest = /* @__PURE__ */ new Uint8Array(64);
|
|
43
|
+
digest.set(new Uint8Array(proofDigest), 0);
|
|
44
|
+
digest.set(new Uint8Array(messageDigest), 32);
|
|
45
|
+
const signature = await crypto.subtle.sign("Ed25519", privateKey, digest);
|
|
46
|
+
return {
|
|
47
|
+
...document,
|
|
48
|
+
proof: {
|
|
49
|
+
...proofConfig,
|
|
50
|
+
proofValue: new TextDecoder().decode(encodeMultibase("base58btc", new Uint8Array(signature)))
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function actor(overrides = {}) {
|
|
55
|
+
return {
|
|
56
|
+
"@context": context,
|
|
57
|
+
id: actorId,
|
|
58
|
+
type: "Person",
|
|
59
|
+
inbox: `ap://${did}/actor/inbox`,
|
|
60
|
+
outbox: outboxId,
|
|
61
|
+
followers: `ap://${did}/actor/followers`,
|
|
62
|
+
gateways: [gw1, gw2],
|
|
63
|
+
...overrides
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function outbox(overrides = {}) {
|
|
67
|
+
return {
|
|
68
|
+
"@context": context,
|
|
69
|
+
id: outboxId,
|
|
70
|
+
type: "OrderedCollection",
|
|
71
|
+
totalItems: 0,
|
|
72
|
+
...overrides
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
const contextLoader = (url) => {
|
|
76
|
+
const document = preloadedContexts[url];
|
|
77
|
+
if (document == null) return Promise.reject(/* @__PURE__ */ new Error(`No context: ${url}`));
|
|
78
|
+
return Promise.resolve({
|
|
79
|
+
contextUrl: null,
|
|
80
|
+
documentUrl: url,
|
|
81
|
+
document
|
|
82
|
+
});
|
|
83
|
+
};
|
|
84
|
+
function createLoader(responses) {
|
|
85
|
+
const fetched = [];
|
|
86
|
+
const loader = (url) => {
|
|
87
|
+
fetched.push(url);
|
|
88
|
+
let response = responses[url];
|
|
89
|
+
let documentUrl = url;
|
|
90
|
+
if (response != null && "redirect" in response) {
|
|
91
|
+
documentUrl = response.redirect;
|
|
92
|
+
response = responses[documentUrl];
|
|
93
|
+
}
|
|
94
|
+
if (response == null) return Promise.reject(URL.canParse(url) ? new FetchError(url, "HTTP 404", new Response(null, { status: 404 })) : /* @__PURE__ */ new Error(`Not found: ${url}`));
|
|
95
|
+
return Promise.resolve({
|
|
96
|
+
contextUrl: null,
|
|
97
|
+
documentUrl,
|
|
98
|
+
document: structuredClone(response)
|
|
99
|
+
});
|
|
100
|
+
};
|
|
101
|
+
return Object.assign(loader, { fetched });
|
|
102
|
+
}
|
|
103
|
+
function linkFrom(object, property, overrides = {}) {
|
|
104
|
+
return {
|
|
105
|
+
object,
|
|
106
|
+
id: object.id ?? null,
|
|
107
|
+
property: AS + property,
|
|
108
|
+
...overrides
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
async function verify(document, options) {
|
|
112
|
+
return await verifyPortableObject(document, {
|
|
113
|
+
contextLoader,
|
|
114
|
+
...options
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
function assertFailure(result, type) {
|
|
118
|
+
ok(!result.verified, `expected ${type}, got ${JSON.stringify(result)}`);
|
|
119
|
+
strictEqual(result.reason.type, type);
|
|
120
|
+
}
|
|
121
|
+
const signedActor = await sign(actor());
|
|
122
|
+
const actorObject = await Person.fromJsonLd(signedActor, { contextLoader });
|
|
123
|
+
test("verifyPortableObject() trusts unsecured collections from listed gateways", async () => {
|
|
124
|
+
const documentLoader = createLoader({ [gatewayUrl(gw1, actorId)]: signedActor });
|
|
125
|
+
const result = await verify(outbox(), {
|
|
126
|
+
documentLoader,
|
|
127
|
+
documentUrl: new URL(gatewayUrl(gw2, outboxId)),
|
|
128
|
+
gatewayHints: [new URL(gw1)],
|
|
129
|
+
referrer: linkFrom(actorObject, "outbox")
|
|
130
|
+
});
|
|
131
|
+
ok(result.verified);
|
|
132
|
+
strictEqual(result.method, "gateway");
|
|
133
|
+
if (result.method !== "gateway") return;
|
|
134
|
+
deepStrictEqual(result.gateway, new URL(gw2));
|
|
135
|
+
strictEqual(result.owner.href, actorObject.id?.href);
|
|
136
|
+
deepStrictEqual(documentLoader.fetched, [gatewayUrl(gw1, actorId)]);
|
|
137
|
+
});
|
|
138
|
+
test("verifyPortableObject() rejects unsecured collections from unlisted gateways", async () => {
|
|
139
|
+
const result = await verify(outbox(), {
|
|
140
|
+
documentLoader: createLoader({ [gatewayUrl(gw1, actorId)]: signedActor }),
|
|
141
|
+
documentUrl: new URL(gatewayUrl(evil, outboxId)),
|
|
142
|
+
gatewayHints: [new URL(evil), new URL(gw1)],
|
|
143
|
+
referrer: linkFrom(actorObject, "outbox")
|
|
144
|
+
});
|
|
145
|
+
assertFailure(result, "untrustedGateway");
|
|
146
|
+
if (result.verified || result.reason.type !== "untrustedGateway") return;
|
|
147
|
+
deepStrictEqual(result.reason.gateway, new URL(evil));
|
|
148
|
+
deepStrictEqual(result.reason.gateways, [new URL(gw1), new URL(gw2)]);
|
|
149
|
+
});
|
|
150
|
+
test("verifyPortableObject() uses the actual fetch source", async () => {
|
|
151
|
+
const documentLoader = createLoader({ [gatewayUrl(gw1, actorId)]: signedActor });
|
|
152
|
+
const referrer = linkFrom(actorObject, "outbox");
|
|
153
|
+
const gateways = [new URL(gw1)];
|
|
154
|
+
assertFailure(await verify(outbox(), {
|
|
155
|
+
documentLoader,
|
|
156
|
+
gateways,
|
|
157
|
+
referrer
|
|
158
|
+
}), "unknownCollectionSource");
|
|
159
|
+
assertFailure(await verify(outbox(), {
|
|
160
|
+
documentLoader,
|
|
161
|
+
gateways,
|
|
162
|
+
referrer,
|
|
163
|
+
documentUrl: new URL("https://gw1.example/cdn/outbox.json")
|
|
164
|
+
}), "unknownCollectionSource");
|
|
165
|
+
assertFailure(await verify(outbox(), {
|
|
166
|
+
documentLoader,
|
|
167
|
+
gateways,
|
|
168
|
+
referrer,
|
|
169
|
+
documentUrl: new URL(gatewayUrl(gw1, `ap://${did}/actor/inbox`))
|
|
170
|
+
}), "unknownCollectionSource");
|
|
171
|
+
assertFailure(await verify(outbox(), {
|
|
172
|
+
documentLoader,
|
|
173
|
+
gateways,
|
|
174
|
+
referrer,
|
|
175
|
+
documentUrl: new URL(`https://user@gw1.example/.well-known/apgateway/${did}/actor/outbox`)
|
|
176
|
+
}), "unknownCollectionSource");
|
|
177
|
+
const result = await verify(outbox({ id: gatewayUrl(evil, outboxId) }), {
|
|
178
|
+
documentLoader,
|
|
179
|
+
gateways,
|
|
180
|
+
referrer,
|
|
181
|
+
documentUrl: new URL(gatewayUrl(gw1, outboxId))
|
|
182
|
+
});
|
|
183
|
+
ok(result.verified);
|
|
184
|
+
strictEqual(result.method, "gateway");
|
|
185
|
+
if (result.method !== "gateway") return;
|
|
186
|
+
deepStrictEqual(result.gateway, new URL(gw1));
|
|
187
|
+
assertFailure(await verify(outbox({ id: `${evil}/outbox` }), {
|
|
188
|
+
documentLoader,
|
|
189
|
+
gateways,
|
|
190
|
+
referrer,
|
|
191
|
+
documentUrl: new URL(gatewayUrl(gw1, outboxId))
|
|
192
|
+
}), "notPortableObject");
|
|
193
|
+
});
|
|
194
|
+
test("verifyPortableObject() trusts unsecured collections of compatible-ID actors", async () => {
|
|
195
|
+
const compatibleActorId = gatewayUrl(gw1, actorId);
|
|
196
|
+
const compatibleOutboxId = gatewayUrl(gw1, outboxId);
|
|
197
|
+
const signed = await sign(actor({
|
|
198
|
+
id: compatibleActorId,
|
|
199
|
+
inbox: gatewayUrl(gw1, `ap://${did}/actor/inbox`),
|
|
200
|
+
outbox: compatibleOutboxId,
|
|
201
|
+
followers: gatewayUrl(gw1, `ap://${did}/actor/followers`)
|
|
202
|
+
}));
|
|
203
|
+
const collection = outbox({
|
|
204
|
+
id: compatibleOutboxId,
|
|
205
|
+
attributedTo: compatibleActorId
|
|
206
|
+
});
|
|
207
|
+
const documentLoader = createLoader({ [compatibleActorId]: signed });
|
|
208
|
+
const result = await verify(collection, {
|
|
209
|
+
documentLoader,
|
|
210
|
+
documentUrl: new URL(compatibleOutboxId)
|
|
211
|
+
});
|
|
212
|
+
ok(result.verified, JSON.stringify(result));
|
|
213
|
+
strictEqual(result.method, "gateway");
|
|
214
|
+
if (result.method !== "gateway") return;
|
|
215
|
+
deepStrictEqual(result.gateway, new URL(gw1));
|
|
216
|
+
deepStrictEqual(documentLoader.fetched, [compatibleActorId]);
|
|
217
|
+
const noGateways = createLoader({ [compatibleActorId]: signed });
|
|
218
|
+
assertFailure(await verify(collection, {
|
|
219
|
+
documentLoader: noGateways,
|
|
220
|
+
documentUrl: new URL(compatibleOutboxId),
|
|
221
|
+
gateways: []
|
|
222
|
+
}), "collectionOwnerUnavailable");
|
|
223
|
+
deepStrictEqual(noGateways.fetched.filter((url) => url === compatibleActorId), []);
|
|
224
|
+
assertFailure(await verify(collection, {
|
|
225
|
+
documentLoader: createLoader({ [compatibleActorId]: actor({
|
|
226
|
+
id: compatibleActorId,
|
|
227
|
+
outbox: compatibleOutboxId
|
|
228
|
+
}) }),
|
|
229
|
+
documentUrl: new URL(compatibleOutboxId)
|
|
230
|
+
}), "collectionOwnerUnavailable");
|
|
231
|
+
});
|
|
232
|
+
test("verifyPortableObject() requires an unambiguous owner", async () => {
|
|
233
|
+
const base = {
|
|
234
|
+
documentLoader: createLoader({ [gatewayUrl(gw1, actorId)]: signedActor }),
|
|
235
|
+
documentUrl: new URL(gatewayUrl(gw1, outboxId)),
|
|
236
|
+
gateways: [new URL(gw1)]
|
|
237
|
+
};
|
|
238
|
+
assertFailure(await verify(outbox(), base), "unknownCollectionOwner");
|
|
239
|
+
const note = await Note.fromJsonLd({
|
|
240
|
+
"@context": context,
|
|
241
|
+
id: `ap://${did}/notes/1`,
|
|
242
|
+
type: "Note"
|
|
243
|
+
}, { contextLoader });
|
|
244
|
+
assertFailure(await verify(outbox(), {
|
|
245
|
+
...base,
|
|
246
|
+
referrer: linkFrom(note, "replies")
|
|
247
|
+
}), "unknownCollectionOwner");
|
|
248
|
+
const attributed = await verify(outbox({ attributedTo: actorId }), base);
|
|
249
|
+
ok(attributed.verified);
|
|
250
|
+
strictEqual(attributed.method, "gateway");
|
|
251
|
+
assertFailure(await verify(outbox({ attributedTo: [actorId, `ap://${did}/actor2`] }), base), "conflictingCollectionOwners");
|
|
252
|
+
assertFailure(await verify(outbox({ attributedTo: `ap://${did}/actor2` }), {
|
|
253
|
+
...base,
|
|
254
|
+
referrer: linkFrom(actorObject, "outbox")
|
|
255
|
+
}), "conflictingCollectionOwners");
|
|
256
|
+
assertFailure(await verify(outbox({ attributedTo: "https://example.com/actor" }), base), "unknownCollectionOwner");
|
|
257
|
+
});
|
|
258
|
+
test("verifyPortableObject() requires the owner to share the collection's DID", async () => {
|
|
259
|
+
const foreignActorId = `ap://${otherDid}/actor`;
|
|
260
|
+
const foreignActor = await sign(actor({
|
|
261
|
+
id: foreignActorId,
|
|
262
|
+
gateways: [evil]
|
|
263
|
+
}), otherKeyPair.privateKey, `${otherDid}#${otherDid.slice(8)}`);
|
|
264
|
+
const foreignActorObject = await Person.fromJsonLd(foreignActor, { contextLoader });
|
|
265
|
+
const documentLoader = createLoader({ [gatewayUrl(evil, foreignActorId)]: foreignActor });
|
|
266
|
+
const base = {
|
|
267
|
+
documentLoader,
|
|
268
|
+
documentUrl: new URL(gatewayUrl(evil, outboxId)),
|
|
269
|
+
gateways: [new URL(evil)]
|
|
270
|
+
};
|
|
271
|
+
assertFailure(await verify(outbox(), {
|
|
272
|
+
...base,
|
|
273
|
+
referrer: linkFrom(foreignActorObject, "outbox")
|
|
274
|
+
}), "unknownCollectionOwner");
|
|
275
|
+
assertFailure(await verify(outbox({ attributedTo: foreignActorId }), base), "unknownCollectionOwner");
|
|
276
|
+
deepStrictEqual(documentLoader.fetched, []);
|
|
277
|
+
});
|
|
278
|
+
test("verifyPortableObject() confirms the owner with its actor document", async () => {
|
|
279
|
+
const documentUrl = new URL(gatewayUrl(gw1, outboxId));
|
|
280
|
+
const gateways = [new URL(gw1)];
|
|
281
|
+
const referrer = linkFrom(actorObject, "outbox");
|
|
282
|
+
assertFailure(await verify(outbox(), {
|
|
283
|
+
documentLoader: createLoader({}),
|
|
284
|
+
documentUrl,
|
|
285
|
+
gateways,
|
|
286
|
+
referrer
|
|
287
|
+
}), "collectionOwnerUnavailable");
|
|
288
|
+
assertFailure(await verify(outbox(), {
|
|
289
|
+
documentLoader: createLoader({ [gatewayUrl(gw1, actorId)]: actor() }),
|
|
290
|
+
documentUrl,
|
|
291
|
+
gateways,
|
|
292
|
+
referrer
|
|
293
|
+
}), "collectionOwnerUnavailable");
|
|
294
|
+
const { gateways: _, ...noGateways } = actor();
|
|
295
|
+
assertFailure(await verify(outbox(), {
|
|
296
|
+
documentLoader: createLoader({ [gatewayUrl(gw1, actorId)]: await sign(noGateways) }),
|
|
297
|
+
documentUrl,
|
|
298
|
+
gateways,
|
|
299
|
+
referrer
|
|
300
|
+
}), "collectionOwnerUnavailable");
|
|
301
|
+
const otherCollectionId = `ap://${did}/collections/1`;
|
|
302
|
+
assertFailure(await verify(outbox({
|
|
303
|
+
id: otherCollectionId,
|
|
304
|
+
attributedTo: actorId
|
|
305
|
+
}), {
|
|
306
|
+
documentLoader: createLoader({ [gatewayUrl(gw1, actorId)]: signedActor }),
|
|
307
|
+
documentUrl: new URL(gatewayUrl(gw1, otherCollectionId)),
|
|
308
|
+
gateways
|
|
309
|
+
}), "collectionNotListedByOwner");
|
|
310
|
+
const emptyLoader = createLoader({ [gatewayUrl(gw1, actorId)]: signedActor });
|
|
311
|
+
assertFailure(await verify(outbox(), {
|
|
312
|
+
documentLoader: emptyLoader,
|
|
313
|
+
documentUrl,
|
|
314
|
+
gateways: [],
|
|
315
|
+
gatewayHints: [new URL(gw1)],
|
|
316
|
+
referrer
|
|
317
|
+
}), "collectionOwnerUnavailable");
|
|
318
|
+
deepStrictEqual(emptyLoader.fetched, [`ap+ef61://${did}/actor`]);
|
|
319
|
+
});
|
|
320
|
+
test("verifyPortableObject() reuses a verified referring actor", async () => {
|
|
321
|
+
const documentLoader = createLoader({});
|
|
322
|
+
const result = await verify(outbox(), {
|
|
323
|
+
documentLoader,
|
|
324
|
+
documentUrl: new URL(gatewayUrl(gw1, outboxId)),
|
|
325
|
+
referrer: linkFrom(actorObject, "outbox", { acceptance: "verified" })
|
|
326
|
+
});
|
|
327
|
+
ok(result.verified);
|
|
328
|
+
strictEqual(result.method, "gateway");
|
|
329
|
+
deepStrictEqual(documentLoader.fetched, []);
|
|
330
|
+
});
|
|
331
|
+
test("verifyPortableObject() does not downgrade signed collections", async () => {
|
|
332
|
+
const documentLoader = createLoader({});
|
|
333
|
+
const signedOutbox = await sign(outbox());
|
|
334
|
+
const result = await verify(signedOutbox, {
|
|
335
|
+
documentLoader,
|
|
336
|
+
referrer: linkFrom(actorObject, "outbox")
|
|
337
|
+
});
|
|
338
|
+
ok(result.verified);
|
|
339
|
+
strictEqual(result.method, "proof");
|
|
340
|
+
assertFailure(await verify({
|
|
341
|
+
...signedOutbox,
|
|
342
|
+
totalItems: 1
|
|
343
|
+
}, {
|
|
344
|
+
documentLoader: createLoader({ [gatewayUrl(gw1, actorId)]: signedActor }),
|
|
345
|
+
documentUrl: new URL(gatewayUrl(gw1, outboxId)),
|
|
346
|
+
gateways: [new URL(gw1)],
|
|
347
|
+
referrer: linkFrom(actorObject, "outbox")
|
|
348
|
+
}), "invalidProof");
|
|
349
|
+
assertFailure(await verify(outbox({ id: "https://example.com/outbox" }), { documentLoader }), "notPortableObject");
|
|
350
|
+
});
|
|
351
|
+
test("verifyPortableObject() checks pages against their collection", async () => {
|
|
352
|
+
const documentLoader = createLoader({ [gatewayUrl(gw1, actorId)]: signedActor });
|
|
353
|
+
const collectionResult = await verify(outbox(), {
|
|
354
|
+
documentLoader,
|
|
355
|
+
documentUrl: new URL(gatewayUrl(gw1, outboxId)),
|
|
356
|
+
gateways: [new URL(gw1)],
|
|
357
|
+
referrer: linkFrom(actorObject, "outbox")
|
|
358
|
+
});
|
|
359
|
+
ok(collectionResult.verified);
|
|
360
|
+
const collection = await OrderedCollection.fromJsonLd(outbox(), { contextLoader });
|
|
361
|
+
const collectionContext = collectionResult.collectionContext;
|
|
362
|
+
const pageId = `ap://${did}/actor/outbox?page=1`;
|
|
363
|
+
const page = (overrides = {}) => ({
|
|
364
|
+
"@context": context,
|
|
365
|
+
id: pageId,
|
|
366
|
+
type: "OrderedCollectionPage",
|
|
367
|
+
partOf: outboxId,
|
|
368
|
+
orderedItems: [],
|
|
369
|
+
...overrides
|
|
370
|
+
});
|
|
371
|
+
const pageReferrer = linkFrom(collection, "first", { collectionContext });
|
|
372
|
+
const fetchedBefore = documentLoader.fetched.length;
|
|
373
|
+
const accepted = await verify(page(), {
|
|
374
|
+
documentLoader,
|
|
375
|
+
documentUrl: new URL(gatewayUrl(gw2, pageId)),
|
|
376
|
+
referrer: pageReferrer
|
|
377
|
+
});
|
|
378
|
+
ok(accepted.verified);
|
|
379
|
+
strictEqual(accepted.method, "gateway");
|
|
380
|
+
strictEqual(documentLoader.fetched.length, fetchedBefore);
|
|
381
|
+
assertFailure(await verify(page(), {
|
|
382
|
+
documentLoader,
|
|
383
|
+
documentUrl: new URL(gatewayUrl(evil, pageId)),
|
|
384
|
+
referrer: pageReferrer
|
|
385
|
+
}), "untrustedGateway");
|
|
386
|
+
assertFailure(await verify(page({ partOf: `ap://${did}/actor/inbox` }), {
|
|
387
|
+
documentLoader,
|
|
388
|
+
documentUrl: new URL(gatewayUrl(gw1, pageId)),
|
|
389
|
+
referrer: pageReferrer
|
|
390
|
+
}), "collectionPageMismatch");
|
|
391
|
+
const foreignPageId = `ap://${otherDid}/outbox?page=1`;
|
|
392
|
+
assertFailure(await verify(page({
|
|
393
|
+
id: foreignPageId,
|
|
394
|
+
partOf: void 0
|
|
395
|
+
}), {
|
|
396
|
+
documentLoader,
|
|
397
|
+
documentUrl: new URL(gatewayUrl(gw1, foreignPageId)),
|
|
398
|
+
referrer: pageReferrer
|
|
399
|
+
}), "collectionPageMismatch");
|
|
400
|
+
assertFailure(await verify(page({ attributedTo: `ap://${did}/actor2` }), {
|
|
401
|
+
documentLoader,
|
|
402
|
+
documentUrl: new URL(gatewayUrl(gw1, pageId)),
|
|
403
|
+
referrer: pageReferrer
|
|
404
|
+
}), "conflictingCollectionOwners");
|
|
405
|
+
assertFailure(await verify(page({ attributedTo: actorId }), {
|
|
406
|
+
documentLoader,
|
|
407
|
+
documentUrl: new URL(gatewayUrl(gw1, pageId)),
|
|
408
|
+
referrer: linkFrom(collection, "first")
|
|
409
|
+
}), "unknownCollectionOwner");
|
|
410
|
+
assertFailure(await verify(page(), {
|
|
411
|
+
documentLoader,
|
|
412
|
+
documentUrl: new URL(gatewayUrl(gw1, pageId)),
|
|
413
|
+
referrer: linkFrom(collection, "first", { collectionContext: {
|
|
414
|
+
rootId: outboxId,
|
|
415
|
+
owner: null
|
|
416
|
+
} })
|
|
417
|
+
}), "unknownCollectionOwner");
|
|
418
|
+
});
|
|
419
|
+
test("verifyPortableObject() checks intermediate pages", async () => {
|
|
420
|
+
const documentLoader = createLoader({ [gatewayUrl(gw1, actorId)]: signedActor });
|
|
421
|
+
const collection = await OrderedCollection.fromJsonLd(outbox(), { contextLoader });
|
|
422
|
+
const intermediate = async (partOf) => await CollectionPage.fromJsonLd({
|
|
423
|
+
"@context": context,
|
|
424
|
+
id: `ap://${did}/actor/outbox?page=1`,
|
|
425
|
+
type: "OrderedCollectionPage",
|
|
426
|
+
partOf
|
|
427
|
+
}, { contextLoader });
|
|
428
|
+
const pageId = `ap://${did}/actor/outbox?page=2`;
|
|
429
|
+
const page = {
|
|
430
|
+
"@context": context,
|
|
431
|
+
id: pageId,
|
|
432
|
+
type: "OrderedCollectionPage",
|
|
433
|
+
partOf: outboxId,
|
|
434
|
+
orderedItems: []
|
|
435
|
+
};
|
|
436
|
+
const chain = (first) => linkFrom(first, "next", { referrer: linkFrom(collection, "first", { referrer: linkFrom(actorObject, "outbox") }) });
|
|
437
|
+
const options = {
|
|
438
|
+
documentLoader,
|
|
439
|
+
documentUrl: new URL(gatewayUrl(gw1, pageId)),
|
|
440
|
+
gateways: [new URL(gw1)]
|
|
441
|
+
};
|
|
442
|
+
const result = await verify(page, {
|
|
443
|
+
...options,
|
|
444
|
+
referrer: chain(await intermediate(outboxId))
|
|
445
|
+
});
|
|
446
|
+
ok(result.verified);
|
|
447
|
+
strictEqual(result.method, "gateway");
|
|
448
|
+
assertFailure(await verify(page, {
|
|
449
|
+
...options,
|
|
450
|
+
referrer: chain(await intermediate(`ap://${did}/actor/inbox`))
|
|
451
|
+
}), "collectionPageMismatch");
|
|
452
|
+
assertFailure(await verify(page, {
|
|
453
|
+
...options,
|
|
454
|
+
referrer: linkFrom(await intermediate(outboxId), "next", { truncated: true })
|
|
455
|
+
}), "unknownCollectionOwner");
|
|
456
|
+
});
|
|
457
|
+
test("verifyPortableObject() works with property accessors", async () => {
|
|
458
|
+
const itemId = `ap://${did}/notes/1`;
|
|
459
|
+
const note = await sign({
|
|
460
|
+
"@context": context,
|
|
461
|
+
id: itemId,
|
|
462
|
+
type: "Note",
|
|
463
|
+
attributedTo: actorId,
|
|
464
|
+
content: "Hello"
|
|
465
|
+
});
|
|
466
|
+
const page1Id = `ap://${did}/actor/outbox?page=1`;
|
|
467
|
+
const page2Id = `ap://${did}/actor/outbox?page=2`;
|
|
468
|
+
const page1 = {
|
|
469
|
+
id: page1Id,
|
|
470
|
+
type: "OrderedCollectionPage",
|
|
471
|
+
partOf: outboxId,
|
|
472
|
+
orderedItems: [{
|
|
473
|
+
id: itemId,
|
|
474
|
+
type: "Note",
|
|
475
|
+
content: "Forged"
|
|
476
|
+
}],
|
|
477
|
+
next: page2Id
|
|
478
|
+
};
|
|
479
|
+
const options = {
|
|
480
|
+
documentLoader: createLoader({
|
|
481
|
+
[gatewayUrl(gw1, actorId)]: signedActor,
|
|
482
|
+
[gatewayUrl(gw1, outboxId)]: outbox({ first: page1 }),
|
|
483
|
+
[gatewayUrl(gw1, page1Id)]: {
|
|
484
|
+
"@context": context,
|
|
485
|
+
...page1
|
|
486
|
+
},
|
|
487
|
+
[gatewayUrl(gw1, page2Id)]: {
|
|
488
|
+
"@context": context,
|
|
489
|
+
id: page2Id,
|
|
490
|
+
type: "OrderedCollectionPage",
|
|
491
|
+
partOf: outboxId,
|
|
492
|
+
orderedItems: [itemId]
|
|
493
|
+
},
|
|
494
|
+
[gatewayUrl(gw1, itemId)]: note
|
|
495
|
+
}),
|
|
496
|
+
contextLoader,
|
|
497
|
+
gateways: [gw1],
|
|
498
|
+
verifyPortableObject
|
|
499
|
+
};
|
|
500
|
+
const create = await Collection.fromJsonLd({
|
|
501
|
+
"@context": context,
|
|
502
|
+
type: "Collection",
|
|
503
|
+
items: [actorId]
|
|
504
|
+
}, { contextLoader });
|
|
505
|
+
const items = await Array.fromAsync(create.getItems(options));
|
|
506
|
+
strictEqual(items.length, 1);
|
|
507
|
+
ok(items[0] instanceof Person);
|
|
508
|
+
const outboxCollection = await items[0].getOutbox(options);
|
|
509
|
+
ok(outboxCollection instanceof OrderedCollection);
|
|
510
|
+
deepStrictEqual((await Array.fromAsync(traverseCollection(outboxCollection, options))).map((item) => item instanceof Note ? item.content : null), ["Hello", "Hello"]);
|
|
511
|
+
const evilLoader = createLoader({
|
|
512
|
+
[gatewayUrl(evil, actorId)]: signedActor,
|
|
513
|
+
[gatewayUrl(evil, outboxId)]: outbox()
|
|
514
|
+
});
|
|
515
|
+
strictEqual(await (await Person.fromJsonLd(signedActor, { contextLoader })).getOutbox({
|
|
516
|
+
...options,
|
|
517
|
+
documentLoader: evilLoader,
|
|
518
|
+
gateways: [evil]
|
|
519
|
+
}), null);
|
|
520
|
+
});
|
|
521
|
+
test("verifyPortableObject() lets pages inherit owners across signed and embedded collections", async () => {
|
|
522
|
+
const page1Id = `ap://${did}/actor/outbox?page=1`;
|
|
523
|
+
const page1 = {
|
|
524
|
+
"@context": context,
|
|
525
|
+
id: page1Id,
|
|
526
|
+
type: "OrderedCollectionPage",
|
|
527
|
+
partOf: outboxId,
|
|
528
|
+
orderedItems: []
|
|
529
|
+
};
|
|
530
|
+
const fetchPerson = async (options) => {
|
|
531
|
+
const wrapper = await Collection.fromJsonLd({
|
|
532
|
+
"@context": context,
|
|
533
|
+
type: "Collection",
|
|
534
|
+
items: [actorId]
|
|
535
|
+
}, { contextLoader });
|
|
536
|
+
const [person] = await Array.fromAsync(wrapper.getItems(options));
|
|
537
|
+
ok(person instanceof Person);
|
|
538
|
+
return person;
|
|
539
|
+
};
|
|
540
|
+
const signedOutbox = await sign(outbox({ first: page1Id }));
|
|
541
|
+
let documentLoader = createLoader({
|
|
542
|
+
[gatewayUrl(gw1, actorId)]: signedActor,
|
|
543
|
+
[gatewayUrl(gw1, outboxId)]: signedOutbox,
|
|
544
|
+
[gatewayUrl(gw2, page1Id)]: page1
|
|
545
|
+
});
|
|
546
|
+
let options = {
|
|
547
|
+
documentLoader,
|
|
548
|
+
contextLoader,
|
|
549
|
+
gateways: [gw1, gw2],
|
|
550
|
+
verifyPortableObject
|
|
551
|
+
};
|
|
552
|
+
let person = await fetchPerson(options);
|
|
553
|
+
let collection = await person.getOutbox(options);
|
|
554
|
+
ok(collection instanceof OrderedCollection);
|
|
555
|
+
ok(await collection.getFirst(options) instanceof CollectionPage);
|
|
556
|
+
const embeddingActor = await sign(actor({ outbox: {
|
|
557
|
+
id: outboxId,
|
|
558
|
+
type: "OrderedCollection",
|
|
559
|
+
first: page1Id
|
|
560
|
+
} }));
|
|
561
|
+
documentLoader = createLoader({
|
|
562
|
+
[gatewayUrl(gw1, actorId)]: embeddingActor,
|
|
563
|
+
[gatewayUrl(gw2, page1Id)]: page1,
|
|
564
|
+
[gatewayUrl(evil, page1Id)]: page1
|
|
565
|
+
});
|
|
566
|
+
options = {
|
|
567
|
+
...options,
|
|
568
|
+
documentLoader
|
|
569
|
+
};
|
|
570
|
+
person = await fetchPerson(options);
|
|
571
|
+
collection = await person.getOutbox(options);
|
|
572
|
+
ok(collection instanceof OrderedCollection);
|
|
573
|
+
ok(await collection.getFirst(options) instanceof CollectionPage);
|
|
574
|
+
deepStrictEqual(documentLoader.fetched, [
|
|
575
|
+
gatewayUrl(gw1, actorId),
|
|
576
|
+
gatewayUrl(gw1, page1Id),
|
|
577
|
+
gatewayUrl(gw2, page1Id)
|
|
578
|
+
]);
|
|
579
|
+
person = await fetchPerson(options);
|
|
580
|
+
collection = await person.getOutbox(options);
|
|
581
|
+
ok(collection instanceof OrderedCollection);
|
|
582
|
+
strictEqual(await collection.getFirst({
|
|
583
|
+
...options,
|
|
584
|
+
gateways: [evil]
|
|
585
|
+
}), null);
|
|
586
|
+
});
|
|
587
|
+
test("verifyPortableObject() reuses a verified actor for pages of signed collections", async () => {
|
|
588
|
+
const documentLoader = createLoader({});
|
|
589
|
+
const signed = await verify(await sign(outbox()), {
|
|
590
|
+
documentLoader,
|
|
591
|
+
referrer: linkFrom(actorObject, "outbox", { acceptance: "verified" })
|
|
592
|
+
});
|
|
593
|
+
ok(signed.verified);
|
|
594
|
+
strictEqual(signed.method, "proof");
|
|
595
|
+
const collection = await OrderedCollection.fromJsonLd(outbox(), { contextLoader });
|
|
596
|
+
const pageId = `ap://${did}/actor/outbox?page=1`;
|
|
597
|
+
const result = await verify({
|
|
598
|
+
"@context": context,
|
|
599
|
+
id: pageId,
|
|
600
|
+
type: "OrderedCollectionPage",
|
|
601
|
+
partOf: outboxId,
|
|
602
|
+
orderedItems: []
|
|
603
|
+
}, {
|
|
604
|
+
documentLoader,
|
|
605
|
+
documentUrl: new URL(gatewayUrl(gw1, pageId)),
|
|
606
|
+
referrer: linkFrom(collection, "first", { collectionContext: signed.collectionContext })
|
|
607
|
+
});
|
|
608
|
+
ok(result.verified);
|
|
609
|
+
strictEqual(result.method, "gateway");
|
|
610
|
+
deepStrictEqual(documentLoader.fetched, []);
|
|
611
|
+
});
|
|
612
|
+
test("verifyPortableObject() fetches the owner through attributedTo hints", async () => {
|
|
613
|
+
const documentLoader = createLoader({ [gatewayUrl(gw2, actorId)]: signedActor });
|
|
614
|
+
const result = await verify(outbox({ attributedTo: `${actorId}?@gateway=${encodeURIComponent(gw2)}` }), {
|
|
615
|
+
documentLoader,
|
|
616
|
+
documentUrl: new URL(gatewayUrl(gw1, outboxId)),
|
|
617
|
+
gatewayHints: [new URL(gw1)]
|
|
618
|
+
});
|
|
619
|
+
ok(result.verified);
|
|
620
|
+
strictEqual(result.method, "gateway");
|
|
621
|
+
deepStrictEqual(documentLoader.fetched, [gatewayUrl(gw2, actorId)]);
|
|
622
|
+
});
|
|
623
|
+
test("verifyPortableObject() applies to compatible identifiers in accessors", async (t) => {
|
|
624
|
+
const noteId = `ap://${did}/notes/1`;
|
|
625
|
+
const signedNote = await sign({
|
|
626
|
+
"@context": context,
|
|
627
|
+
id: noteId,
|
|
628
|
+
type: "Note",
|
|
629
|
+
attributedTo: actorId,
|
|
630
|
+
content: "Hello"
|
|
631
|
+
});
|
|
632
|
+
const forgedNote = {
|
|
633
|
+
"@context": context,
|
|
634
|
+
id: noteId,
|
|
635
|
+
type: "Note",
|
|
636
|
+
attributedTo: actorId,
|
|
637
|
+
content: "Forged"
|
|
638
|
+
};
|
|
639
|
+
const collection = (items) => Collection.fromJsonLd({
|
|
640
|
+
"@context": context,
|
|
641
|
+
id: "https://example.com/collection",
|
|
642
|
+
type: "Collection",
|
|
643
|
+
items
|
|
644
|
+
}, { contextLoader });
|
|
645
|
+
const getItems = async (items, documentLoader) => await Array.fromAsync((await collection(items)).getItems({
|
|
646
|
+
documentLoader,
|
|
647
|
+
contextLoader,
|
|
648
|
+
verifyPortableObject
|
|
649
|
+
}));
|
|
650
|
+
await t.step("signed objects", async () => {
|
|
651
|
+
const documentLoader = createLoader({ [gatewayUrl(gw1, noteId)]: signedNote });
|
|
652
|
+
deepStrictEqual((await getItems([gatewayUrl(gw1, noteId)], documentLoader)).map((item) => item instanceof Note ? item.content : null), ["Hello"]);
|
|
653
|
+
});
|
|
654
|
+
await t.step("forged objects", async () => {
|
|
655
|
+
const documentLoader = createLoader({
|
|
656
|
+
[gatewayUrl(evil, noteId)]: forgedNote,
|
|
657
|
+
[gatewayUrl(evil, `ap://${did}/notes/2`)]: {
|
|
658
|
+
...forgedNote,
|
|
659
|
+
id: gatewayUrl(evil, `ap://${did}/notes/2`),
|
|
660
|
+
attributedTo: gatewayUrl(evil, actorId)
|
|
661
|
+
},
|
|
662
|
+
"https://evil.example/notes/1": { redirect: gatewayUrl(evil, noteId) },
|
|
663
|
+
[gatewayUrl("https://example.com", noteId)]: forgedNote
|
|
664
|
+
});
|
|
665
|
+
deepStrictEqual(await getItems([
|
|
666
|
+
gatewayUrl(evil, noteId),
|
|
667
|
+
gatewayUrl(evil, `ap://${did}/notes/2`),
|
|
668
|
+
"https://evil.example/notes/1",
|
|
669
|
+
{
|
|
670
|
+
id: gatewayUrl("https://example.com", noteId),
|
|
671
|
+
type: "Note",
|
|
672
|
+
content: "Forged"
|
|
673
|
+
}
|
|
674
|
+
], documentLoader), []);
|
|
675
|
+
});
|
|
676
|
+
await t.step("unsecured collections", async () => {
|
|
677
|
+
const unsecured = outbox({ attributedTo: actorId });
|
|
678
|
+
const documentLoader = createLoader({
|
|
679
|
+
[gatewayUrl(gw1, actorId)]: signedActor,
|
|
680
|
+
[gatewayUrl(gw1, outboxId)]: unsecured,
|
|
681
|
+
[gatewayUrl(evil, actorId)]: signedActor,
|
|
682
|
+
[gatewayUrl(evil, outboxId)]: unsecured
|
|
683
|
+
});
|
|
684
|
+
const items = await getItems([gatewayUrl(gw1, outboxId)], documentLoader);
|
|
685
|
+
strictEqual(items.length, 1);
|
|
686
|
+
ok(items[0] instanceof OrderedCollection);
|
|
687
|
+
deepStrictEqual(documentLoader.fetched, [gatewayUrl(gw1, outboxId), gatewayUrl(gw1, actorId)]);
|
|
688
|
+
deepStrictEqual(await getItems([gatewayUrl(evil, outboxId)], documentLoader), []);
|
|
689
|
+
deepStrictEqual(await getItems([gatewayUrl(gw1, outboxId)], createLoader({
|
|
690
|
+
[gatewayUrl(gw1, actorId)]: signedActor,
|
|
691
|
+
[gatewayUrl(gw1, outboxId)]: { redirect: gatewayUrl(evil, outboxId) },
|
|
692
|
+
[gatewayUrl(evil, outboxId)]: unsecured
|
|
693
|
+
})), []);
|
|
694
|
+
});
|
|
695
|
+
});
|
|
696
|
+
//#endregion
|
|
697
|
+
export {};
|