@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,989 @@
|
|
|
1
|
+
import "temporal-polyfill";
|
|
2
|
+
import "urlpattern-polyfill";
|
|
3
|
+
globalThis.addEventListener = () => {};
|
|
4
|
+
import { a as getRawCanonicalPortableId, l as isPortableId, n as getCanonicalPortableId } from "./portable-key-id-Ca30-hOa.mjs";
|
|
5
|
+
import { a as verificationObservation, t as observeAttempt } from "./verification-Dpfcw8ml.mjs";
|
|
6
|
+
import { n as preloadedOnlyDocumentLoader } from "./public-audience--klBIOk1.mjs";
|
|
7
|
+
import { c as getNormalizationContextLoader } from "./ld-CzaP-hJl.mjs";
|
|
8
|
+
import { l as verifyPortableObjectProofPolicy, n as classifyFep2277CoreType, o as verifyMapLocalProof } from "./proof-Dxy6ByqI.mjs";
|
|
9
|
+
import { parseIri, preloadedContexts } from "@fedify/vocab-runtime";
|
|
10
|
+
import jsonld from "@fedify/vocab-runtime/jsonld";
|
|
11
|
+
//#region src/sig/compound-proof.ts
|
|
12
|
+
const textEncoder = new TextEncoder();
|
|
13
|
+
/**
|
|
14
|
+
* Checks whether a literal `id` or `@id` value makes a map a portable object:
|
|
15
|
+
* an `ap:` or `ap+ef61:` URI, or anything that looks like an FEP-ef61
|
|
16
|
+
* compatible identifier, even a malformed one, which then fails the policy.
|
|
17
|
+
*/
|
|
18
|
+
function isPortableIdValue(value) {
|
|
19
|
+
return typeof value === "string" && isPortableId(value);
|
|
20
|
+
}
|
|
21
|
+
function isJsonMap(value) {
|
|
22
|
+
return typeof value === "object" && value != null && !Array.isArray(value);
|
|
23
|
+
}
|
|
24
|
+
function isCompoundProofJsonObject(value) {
|
|
25
|
+
return typeof value === "object" && value != null && !Array.isArray(value);
|
|
26
|
+
}
|
|
27
|
+
function hasOnlyDataProperties(value) {
|
|
28
|
+
try {
|
|
29
|
+
return Reflect.ownKeys(value).every((key) => {
|
|
30
|
+
if (typeof key !== "string") return false;
|
|
31
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
32
|
+
return descriptor != null && "value" in descriptor && descriptor.enumerable;
|
|
33
|
+
});
|
|
34
|
+
} catch {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function hasOnlyJsonArrayProperties(value) {
|
|
39
|
+
try {
|
|
40
|
+
const keys = Reflect.ownKeys(value);
|
|
41
|
+
if (keys.length !== value.length + 1) return false;
|
|
42
|
+
for (const key of keys) {
|
|
43
|
+
if (key === "length") continue;
|
|
44
|
+
if (typeof key !== "string") return false;
|
|
45
|
+
const index = Number(key);
|
|
46
|
+
if (!Number.isInteger(index) || index < 0 || index >= value.length || String(index) !== key) return false;
|
|
47
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
48
|
+
if (descriptor == null || !descriptor.enumerable || !("value" in descriptor)) return false;
|
|
49
|
+
}
|
|
50
|
+
return true;
|
|
51
|
+
} catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function escapeJsonPointerSegment(segment) {
|
|
56
|
+
return segment.replaceAll("~", "~0").replaceAll("/", "~1");
|
|
57
|
+
}
|
|
58
|
+
function childPath(path, segment) {
|
|
59
|
+
return `${path}/${escapeJsonPointerSegment(segment)}`;
|
|
60
|
+
}
|
|
61
|
+
function comparePaths(left, right) {
|
|
62
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
63
|
+
}
|
|
64
|
+
function encodedLength(value) {
|
|
65
|
+
return textEncoder.encode(value).byteLength;
|
|
66
|
+
}
|
|
67
|
+
function validateLimits(limits) {
|
|
68
|
+
for (const [name, value] of Object.entries(limits)) if (!Number.isSafeInteger(value) || value < 0) throw new RangeError(`${name} must be a non-negative safe integer.`);
|
|
69
|
+
}
|
|
70
|
+
function limitExceeded(limit, maximum, actual, path) {
|
|
71
|
+
return {
|
|
72
|
+
status: "unsupported",
|
|
73
|
+
reason: {
|
|
74
|
+
type: "limitExceeded",
|
|
75
|
+
limit,
|
|
76
|
+
maximum,
|
|
77
|
+
actual,
|
|
78
|
+
path
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function addBytes(statistics, bytes, limits, path) {
|
|
83
|
+
statistics.byteLength += bytes;
|
|
84
|
+
if (statistics.byteLength > limits.maxBytes) return limitExceeded("bytes", limits.maxBytes, statistics.byteLength, path);
|
|
85
|
+
}
|
|
86
|
+
function inspectJsonTree(root, limits) {
|
|
87
|
+
if (!isJsonMap(root)) return {
|
|
88
|
+
status: "unsupported",
|
|
89
|
+
reason: {
|
|
90
|
+
type: "invalidJsonTree",
|
|
91
|
+
path: ""
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
const statistics = {
|
|
95
|
+
byteLength: 0,
|
|
96
|
+
mapCount: 0,
|
|
97
|
+
proofCount: 0,
|
|
98
|
+
maxDepth: 0
|
|
99
|
+
};
|
|
100
|
+
const seen = /* @__PURE__ */ new Set();
|
|
101
|
+
const pending = [{
|
|
102
|
+
value: root,
|
|
103
|
+
path: "",
|
|
104
|
+
depth: 0,
|
|
105
|
+
insideProof: false,
|
|
106
|
+
insideContext: false
|
|
107
|
+
}];
|
|
108
|
+
while (pending.length > 0) {
|
|
109
|
+
const current = pending.pop();
|
|
110
|
+
statistics.maxDepth = Math.max(statistics.maxDepth, current.depth);
|
|
111
|
+
if (current.depth > limits.maxDepth) return limitExceeded("depth", limits.maxDepth, current.depth, current.path);
|
|
112
|
+
const value = current.value;
|
|
113
|
+
if (value === null) {
|
|
114
|
+
const failure = addBytes(statistics, 4, limits, current.path);
|
|
115
|
+
if (failure != null) return failure;
|
|
116
|
+
} else if (typeof value === "boolean") {
|
|
117
|
+
const failure = addBytes(statistics, value ? 4 : 5, limits, current.path);
|
|
118
|
+
if (failure != null) return failure;
|
|
119
|
+
} else if (typeof value === "string") {
|
|
120
|
+
const failure = addBytes(statistics, encodedLength(JSON.stringify(value)), limits, current.path);
|
|
121
|
+
if (failure != null) return failure;
|
|
122
|
+
} else if (typeof value === "number") {
|
|
123
|
+
if (!Number.isFinite(value)) return {
|
|
124
|
+
status: "unsupported",
|
|
125
|
+
reason: {
|
|
126
|
+
type: "invalidJsonTree",
|
|
127
|
+
path: current.path
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
const failure = addBytes(statistics, encodedLength(JSON.stringify(value)), limits, current.path);
|
|
131
|
+
if (failure != null) return failure;
|
|
132
|
+
} else if (Array.isArray(value)) {
|
|
133
|
+
if (seen.has(value) || !hasOnlyJsonArrayProperties(value)) return {
|
|
134
|
+
status: "unsupported",
|
|
135
|
+
reason: {
|
|
136
|
+
type: "invalidJsonTree",
|
|
137
|
+
path: current.path
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
seen.add(value);
|
|
141
|
+
const failure = addBytes(statistics, 2 + Math.max(0, value.length - 1), limits, current.path);
|
|
142
|
+
if (failure != null) return failure;
|
|
143
|
+
for (let index = value.length - 1; index >= 0; index--) pending.push({
|
|
144
|
+
value: value[index],
|
|
145
|
+
path: childPath(current.path, String(index)),
|
|
146
|
+
depth: current.depth + 1,
|
|
147
|
+
insideProof: current.insideProof,
|
|
148
|
+
insideContext: current.insideContext
|
|
149
|
+
});
|
|
150
|
+
} else if (isJsonMap(value)) {
|
|
151
|
+
const prototype = Object.getPrototypeOf(value);
|
|
152
|
+
if (seen.has(value) || !hasOnlyDataProperties(value) || prototype !== Object.prototype && prototype !== null) return {
|
|
153
|
+
status: "unsupported",
|
|
154
|
+
reason: {
|
|
155
|
+
type: "invalidJsonTree",
|
|
156
|
+
path: current.path
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
seen.add(value);
|
|
160
|
+
statistics.mapCount++;
|
|
161
|
+
if (statistics.mapCount > limits.maxMaps) return limitExceeded("maps", limits.maxMaps, statistics.mapCount, current.path);
|
|
162
|
+
const entries = Object.entries(value).sort(([left], [right]) => comparePaths(left, right));
|
|
163
|
+
let objectBytes = 2 + Math.max(0, entries.length - 1);
|
|
164
|
+
for (const [key] of entries) objectBytes += encodedLength(JSON.stringify(key)) + 1;
|
|
165
|
+
const failure = addBytes(statistics, objectBytes, limits, current.path);
|
|
166
|
+
if (failure != null) return failure;
|
|
167
|
+
const hasDirectProof = !current.insideProof && !current.insideContext && Object.hasOwn(value, "proof");
|
|
168
|
+
if (hasDirectProof) {
|
|
169
|
+
statistics.proofCount++;
|
|
170
|
+
if (statistics.proofCount > limits.maxProofs) return limitExceeded("proofs", limits.maxProofs, statistics.proofCount, childPath(current.path, "proof"));
|
|
171
|
+
if (!isJsonMap(value.proof)) return {
|
|
172
|
+
status: "unsupported",
|
|
173
|
+
reason: {
|
|
174
|
+
type: "unsupportedProofShape",
|
|
175
|
+
path: childPath(current.path, "proof")
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
for (let index = entries.length - 1; index >= 0; index--) {
|
|
180
|
+
const [key, child] = entries[index];
|
|
181
|
+
pending.push({
|
|
182
|
+
value: child,
|
|
183
|
+
path: childPath(current.path, key),
|
|
184
|
+
depth: current.depth + 1,
|
|
185
|
+
insideProof: current.insideProof || hasDirectProof && key === "proof",
|
|
186
|
+
insideContext: current.insideContext || key === "@context"
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
} else return {
|
|
190
|
+
status: "unsupported",
|
|
191
|
+
reason: {
|
|
192
|
+
type: "invalidJsonTree",
|
|
193
|
+
path: current.path
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
return statistics;
|
|
198
|
+
}
|
|
199
|
+
function freezeJsonTree(root) {
|
|
200
|
+
const pending = [root];
|
|
201
|
+
while (pending.length > 0) {
|
|
202
|
+
const value = pending.pop();
|
|
203
|
+
if (typeof value !== "object" || value == null) continue;
|
|
204
|
+
if (Array.isArray(value)) for (const child of value) pending.push(child);
|
|
205
|
+
else for (const child of Object.values(value)) pending.push(child);
|
|
206
|
+
Object.freeze(value);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
function collectDocuments(snapshot) {
|
|
210
|
+
const documents = [];
|
|
211
|
+
const pending = [{
|
|
212
|
+
value: snapshot,
|
|
213
|
+
path: "",
|
|
214
|
+
depth: 0
|
|
215
|
+
}];
|
|
216
|
+
while (pending.length > 0) {
|
|
217
|
+
const current = pending.pop();
|
|
218
|
+
if (Array.isArray(current.value)) {
|
|
219
|
+
for (let index = current.value.length - 1; index >= 0; index--) pending.push({
|
|
220
|
+
value: current.value[index],
|
|
221
|
+
path: childPath(current.path, String(index)),
|
|
222
|
+
depth: current.depth + 1
|
|
223
|
+
});
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
if (!isCompoundProofJsonObject(current.value)) continue;
|
|
227
|
+
const document = current.value;
|
|
228
|
+
if (Object.hasOwn(document, "proof")) {
|
|
229
|
+
const proof = document.proof;
|
|
230
|
+
const id = typeof document.id === "string" ? document.id : typeof document["@id"] === "string" ? document["@id"] : void 0;
|
|
231
|
+
documents.push(Object.freeze({
|
|
232
|
+
path: current.path,
|
|
233
|
+
id,
|
|
234
|
+
depth: current.depth,
|
|
235
|
+
securedDocument: document,
|
|
236
|
+
proof
|
|
237
|
+
}));
|
|
238
|
+
}
|
|
239
|
+
const entries = Object.entries(document).sort(([left], [right]) => comparePaths(left, right));
|
|
240
|
+
for (let index = entries.length - 1; index >= 0; index--) {
|
|
241
|
+
const [key, value] = entries[index];
|
|
242
|
+
if (key === "proof" || key === "@context") continue;
|
|
243
|
+
pending.push({
|
|
244
|
+
value,
|
|
245
|
+
path: childPath(current.path, key),
|
|
246
|
+
depth: current.depth + 1
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
documents.sort((left, right) => right.depth - left.depth || comparePaths(left.path, right.path));
|
|
251
|
+
return documents;
|
|
252
|
+
}
|
|
253
|
+
function collectPortableObjects(snapshot) {
|
|
254
|
+
const objects = [];
|
|
255
|
+
const pending = [{
|
|
256
|
+
value: snapshot,
|
|
257
|
+
path: "",
|
|
258
|
+
depth: 0
|
|
259
|
+
}];
|
|
260
|
+
while (pending.length > 0) {
|
|
261
|
+
const current = pending.pop();
|
|
262
|
+
if (Array.isArray(current.value)) {
|
|
263
|
+
for (let index = current.value.length - 1; index >= 0; index--) pending.push({
|
|
264
|
+
value: current.value[index],
|
|
265
|
+
path: childPath(current.path, String(index)),
|
|
266
|
+
depth: current.depth + 1,
|
|
267
|
+
keyOf: current.keyOf
|
|
268
|
+
});
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
if (!isCompoundProofJsonObject(current.value)) continue;
|
|
272
|
+
const document = current.value;
|
|
273
|
+
const id = [document.id, document["@id"]].find(isPortableIdValue);
|
|
274
|
+
if (typeof id === "string") objects.push(Object.freeze({
|
|
275
|
+
path: current.path,
|
|
276
|
+
id,
|
|
277
|
+
depth: current.depth,
|
|
278
|
+
document,
|
|
279
|
+
...current.keyOf == null ? {} : { keyOf: current.keyOf }
|
|
280
|
+
}));
|
|
281
|
+
const entries = Object.entries(document).sort(([left], [right]) => comparePaths(left, right));
|
|
282
|
+
for (let index = entries.length - 1; index >= 0; index--) {
|
|
283
|
+
const [key, value] = entries[index];
|
|
284
|
+
if (key === "proof" || key === "@context") continue;
|
|
285
|
+
pending.push({
|
|
286
|
+
value,
|
|
287
|
+
path: childPath(current.path, key),
|
|
288
|
+
depth: current.depth + 1,
|
|
289
|
+
...typeof id === "string" && (key === "publicKey" || key === "assertionMethod") ? { keyOf: {
|
|
290
|
+
path: current.path,
|
|
291
|
+
member: key
|
|
292
|
+
} } : {}
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
objects.sort((left, right) => right.depth - left.depth || comparePaths(left.path, right.path));
|
|
297
|
+
return objects;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Determines whether a received JSON tree contains a portable object ID, i.e.,
|
|
301
|
+
* an `ap:` or `ap+ef61:` URI or an FEP-ef61 compatible identifier, outside
|
|
302
|
+
* a proof or context definition, without exceeding discovery limits.
|
|
303
|
+
*
|
|
304
|
+
* This is only an applicability check. It does not authenticate the input;
|
|
305
|
+
* callers must still use {@link verifyCompoundPortableObjectProofs} to obtain
|
|
306
|
+
* an atomic result from an immutable snapshot. `indeterminate` means the
|
|
307
|
+
* applicability scan itself reached a limit, so a caller must not treat the
|
|
308
|
+
* document as outside the compound profile.
|
|
309
|
+
*
|
|
310
|
+
* @internal
|
|
311
|
+
*/
|
|
312
|
+
function inspectCompoundPortableObjectApplicability(json, limits) {
|
|
313
|
+
validateLimits(limits);
|
|
314
|
+
let byteLength = 0;
|
|
315
|
+
let mapCount = 0;
|
|
316
|
+
const pending = [{
|
|
317
|
+
iterator: [json].values(),
|
|
318
|
+
depth: 0
|
|
319
|
+
}];
|
|
320
|
+
const addByteLength = (length) => {
|
|
321
|
+
byteLength += length;
|
|
322
|
+
return byteLength <= limits.maxBytes;
|
|
323
|
+
};
|
|
324
|
+
while (pending.length > 0) {
|
|
325
|
+
const frame = pending[pending.length - 1];
|
|
326
|
+
const next = frame.iterator.next();
|
|
327
|
+
if (next.done) {
|
|
328
|
+
pending.pop();
|
|
329
|
+
if (byteLength > limits.maxBytes) return "indeterminate";
|
|
330
|
+
continue;
|
|
331
|
+
}
|
|
332
|
+
const value = next.value;
|
|
333
|
+
if (frame.depth > limits.maxDepth) return "indeterminate";
|
|
334
|
+
if (value === null) {
|
|
335
|
+
if (!addByteLength(4)) return "indeterminate";
|
|
336
|
+
} else if (typeof value === "boolean") {
|
|
337
|
+
if (!addByteLength(value ? 4 : 5)) return "indeterminate";
|
|
338
|
+
} else if (typeof value === "string" || typeof value === "number") {
|
|
339
|
+
if (!addByteLength(encodedLength(JSON.stringify(value)))) return "indeterminate";
|
|
340
|
+
} else if (Array.isArray(value)) {
|
|
341
|
+
if (!addByteLength(2 + Math.max(0, value.length - 1))) return "indeterminate";
|
|
342
|
+
pending.push({
|
|
343
|
+
iterator: value.values(),
|
|
344
|
+
depth: frame.depth + 1
|
|
345
|
+
});
|
|
346
|
+
} else if (isJsonMap(value)) {
|
|
347
|
+
mapCount++;
|
|
348
|
+
if (mapCount > limits.maxMaps) return "indeterminate";
|
|
349
|
+
if ([value.id, value["@id"]].some(isPortableIdValue)) return "present";
|
|
350
|
+
if (!addByteLength(2)) return "indeterminate";
|
|
351
|
+
const children = function* () {
|
|
352
|
+
let first = true;
|
|
353
|
+
for (const key in value) {
|
|
354
|
+
if (!Object.hasOwn(value, key)) continue;
|
|
355
|
+
if (key === "proof" || key === "@context") continue;
|
|
356
|
+
if (!first && !addByteLength(1)) return;
|
|
357
|
+
first = false;
|
|
358
|
+
if (!addByteLength(encodedLength(JSON.stringify(key)) + 1)) return;
|
|
359
|
+
yield value[key];
|
|
360
|
+
}
|
|
361
|
+
}();
|
|
362
|
+
pending.push({
|
|
363
|
+
iterator: children,
|
|
364
|
+
depth: frame.depth + 1
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
if (byteLength > limits.maxBytes) return "indeterminate";
|
|
368
|
+
}
|
|
369
|
+
return "absent";
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Walks every JSON map in a finite, already-materialized JSON tree, depth
|
|
373
|
+
* first in insertion order, skipping the values of `proof` and `@context`
|
|
374
|
+
* members. A map reached a second time through a repeated reference is not
|
|
375
|
+
* visited again, so a cyclic input terminates. The walk stops as soon as
|
|
376
|
+
* the visitor returns a value other than `undefined`.
|
|
377
|
+
*/
|
|
378
|
+
function findInJsonMaps(json, visit) {
|
|
379
|
+
const seen = /* @__PURE__ */ new Set();
|
|
380
|
+
const pending = [{
|
|
381
|
+
value: json,
|
|
382
|
+
path: ""
|
|
383
|
+
}];
|
|
384
|
+
while (pending.length > 0) {
|
|
385
|
+
const { value, path } = pending.pop();
|
|
386
|
+
if (typeof value !== "object" || value == null || seen.has(value)) continue;
|
|
387
|
+
seen.add(value);
|
|
388
|
+
if (Array.isArray(value)) {
|
|
389
|
+
for (let index = value.length - 1; index >= 0; index--) pending.push({
|
|
390
|
+
value: value[index],
|
|
391
|
+
path: childPath(path, String(index))
|
|
392
|
+
});
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
const map = value;
|
|
396
|
+
const result = visit(map, path);
|
|
397
|
+
if (result !== void 0) return result;
|
|
398
|
+
const keys = Object.keys(map);
|
|
399
|
+
for (let index = keys.length - 1; index >= 0; index--) {
|
|
400
|
+
const key = keys[index];
|
|
401
|
+
if (key === "proof" || key === "@context") continue;
|
|
402
|
+
pending.push({
|
|
403
|
+
value: map[key],
|
|
404
|
+
path: childPath(path, key)
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Determines whether an outgoing JSON tree contains portable content that
|
|
411
|
+
* requires producer-side proof-shape checks: a map, outside proof and context
|
|
412
|
+
* values, whose direct `id` or `@id` is a portable ActivityPub URI or an
|
|
413
|
+
* FEP-ef61 compatible identifier.
|
|
414
|
+
*
|
|
415
|
+
* This deliberately differs from
|
|
416
|
+
* {@link inspectCompoundPortableObjectApplicability}: it applies no resource
|
|
417
|
+
* limits, so a portable map or an unsupported proof beyond the inbox limits
|
|
418
|
+
* is still found, and a large document without portable maps stays outside
|
|
419
|
+
* the compound-proof profile. The input must be a finite JSON tree that has
|
|
420
|
+
* already been materialized, such as a compact JSON-LD document about to be
|
|
421
|
+
* sent.
|
|
422
|
+
*
|
|
423
|
+
* @internal
|
|
424
|
+
*/
|
|
425
|
+
function containsCompoundPortableObject(json) {
|
|
426
|
+
return findInJsonMaps(json, (map) => [map.id, map["@id"]].some(isPortableIdValue) ? true : void 0) ?? false;
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Finds the first direct `proof` member whose value the map-local
|
|
430
|
+
* compound-proof profile does not accept, i.e., anything other than a single
|
|
431
|
+
* JSON map: a proof set, `null`, or a scalar. Like the inbox discovery, it
|
|
432
|
+
* does not look inside proof or context values.
|
|
433
|
+
*
|
|
434
|
+
* The input must be a finite, already-materialized JSON tree; see
|
|
435
|
+
* {@link containsCompoundPortableObject}.
|
|
436
|
+
*
|
|
437
|
+
* @returns The RFC 6901 JSON Pointer to the unsupported `proof` member, or
|
|
438
|
+
* `null` if every direct proof is a single JSON map.
|
|
439
|
+
* @internal
|
|
440
|
+
*/
|
|
441
|
+
function findUnsupportedCompoundProofShape(json) {
|
|
442
|
+
return findInJsonMaps(json, (map, path) => Object.hasOwn(map, "proof") && !isJsonMap(map.proof) ? childPath(path, "proof") : void 0) ?? null;
|
|
443
|
+
}
|
|
444
|
+
/**
|
|
445
|
+
* Finds the first embedded map that carries a direct `proof` but not its own
|
|
446
|
+
* `@context`. The map-local compound-proof profile verifies each proof-bearing
|
|
447
|
+
* map as a self-contained document, so the inbox rejects such a map with
|
|
448
|
+
* `missingContext` whatever its proof. An object embedded with its captured
|
|
449
|
+
* signed representation, e.g., by {@link signObject}, always keeps its own
|
|
450
|
+
* `@context`; one rebuilt under its parent's context, e.g., an object parsed
|
|
451
|
+
* from a received document, never does, and its proof no longer covers what
|
|
452
|
+
* it has become anyway.
|
|
453
|
+
*
|
|
454
|
+
* The input must be a finite, already-materialized JSON tree; see
|
|
455
|
+
* {@link containsCompoundPortableObject}.
|
|
456
|
+
*
|
|
457
|
+
* @returns The RFC 6901 JSON Pointer to the map, or `null` if every embedded
|
|
458
|
+
* proof-bearing map has its own `@context`.
|
|
459
|
+
* @internal
|
|
460
|
+
*/
|
|
461
|
+
function findEmbeddedProofWithoutContext(json) {
|
|
462
|
+
return findInJsonMaps(json, (map, path) => path !== "" && Object.hasOwn(map, "proof") && !Object.hasOwn(map, "@context") ? path : void 0) ?? null;
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Discovers direct literal proof-bearing maps in an immutable JSON snapshot.
|
|
466
|
+
*
|
|
467
|
+
* Discovery is bounded and atomic. Unsupported proof shapes, non-JSON input,
|
|
468
|
+
* or a resource-limit failure return no partial candidate list. Proof
|
|
469
|
+
* configuration subtrees are validated and counted as JSON, but are not
|
|
470
|
+
* themselves searched for secured documents.
|
|
471
|
+
*/
|
|
472
|
+
function discoverCompoundProofDocuments(json, limits) {
|
|
473
|
+
validateLimits(limits);
|
|
474
|
+
let inspected;
|
|
475
|
+
try {
|
|
476
|
+
inspected = inspectJsonTree(json, limits);
|
|
477
|
+
} catch {
|
|
478
|
+
return {
|
|
479
|
+
status: "unsupported",
|
|
480
|
+
reason: {
|
|
481
|
+
type: "invalidJsonTree",
|
|
482
|
+
path: ""
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
if ("status" in inspected) return inspected;
|
|
487
|
+
let snapshot;
|
|
488
|
+
try {
|
|
489
|
+
snapshot = structuredClone(json);
|
|
490
|
+
} catch {
|
|
491
|
+
return {
|
|
492
|
+
status: "unsupported",
|
|
493
|
+
reason: {
|
|
494
|
+
type: "invalidJsonTree",
|
|
495
|
+
path: ""
|
|
496
|
+
}
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
freezeJsonTree(snapshot);
|
|
500
|
+
return {
|
|
501
|
+
status: "ok",
|
|
502
|
+
snapshot,
|
|
503
|
+
documents: Object.freeze(collectDocuments(snapshot)),
|
|
504
|
+
statistics: Object.freeze({ ...inspected })
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Verifies every direct literal proof found in one immutable JSON snapshot.
|
|
509
|
+
*
|
|
510
|
+
* Each current map is verified independently. Only its literal `proof`
|
|
511
|
+
* member is removed from its JCS input, so descendant proofs and proof aliases
|
|
512
|
+
* remain unchanged. This is the lower-level compound-document mechanism; it
|
|
513
|
+
* does not apply portable-object policy or authenticate an inbox activity.
|
|
514
|
+
*
|
|
515
|
+
* @internal
|
|
516
|
+
*/
|
|
517
|
+
async function verifyCompoundProofDocuments(json, limits, options = {}) {
|
|
518
|
+
const discovered = discoverCompoundProofDocuments(json, limits);
|
|
519
|
+
if (discovered.status !== "ok") return discovered;
|
|
520
|
+
const documents = await verifyDiscoveredProofDocuments(discovered, options);
|
|
521
|
+
return {
|
|
522
|
+
status: "ok",
|
|
523
|
+
verified: documents.length > 0 && documents.every((document) => document.verified),
|
|
524
|
+
snapshot: discovered.snapshot,
|
|
525
|
+
documents,
|
|
526
|
+
statistics: discovered.statistics
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
async function verifyDiscoveredProofDocuments(discovered, options) {
|
|
530
|
+
const documents = await Promise.all(discovered.documents.map(async (document) => {
|
|
531
|
+
const metadata = {
|
|
532
|
+
path: document.path,
|
|
533
|
+
...document.id == null ? {} : { id: document.id },
|
|
534
|
+
depth: document.depth
|
|
535
|
+
};
|
|
536
|
+
if (document.depth > 0 && !Object.hasOwn(document.securedDocument, "@context")) return Object.freeze({
|
|
537
|
+
...metadata,
|
|
538
|
+
verified: false,
|
|
539
|
+
reason: { type: "missingContext" }
|
|
540
|
+
});
|
|
541
|
+
const inherited = options[verificationObservation];
|
|
542
|
+
let subjectId = null;
|
|
543
|
+
if (inherited != null && document.id != null) try {
|
|
544
|
+
subjectId = parseIri(document.id);
|
|
545
|
+
} catch {}
|
|
546
|
+
const key = await observeAttempt({ [verificationObservation]: inherited == null ? void 0 : {
|
|
547
|
+
...inherited,
|
|
548
|
+
attempt: void 0,
|
|
549
|
+
check: void 0,
|
|
550
|
+
subject: {
|
|
551
|
+
id: subjectId,
|
|
552
|
+
pointer: document.path
|
|
553
|
+
}
|
|
554
|
+
} }, "objectIntegrity", (observation) => verifyMapLocalProof(document.securedDocument, {
|
|
555
|
+
...options,
|
|
556
|
+
[verificationObservation]: inherited == null ? void 0 : observation
|
|
557
|
+
}), (key) => key != null);
|
|
558
|
+
return key == null ? Object.freeze({
|
|
559
|
+
...metadata,
|
|
560
|
+
verified: false,
|
|
561
|
+
reason: { type: "invalidProof" }
|
|
562
|
+
}) : Object.freeze({
|
|
563
|
+
...metadata,
|
|
564
|
+
verified: true,
|
|
565
|
+
key
|
|
566
|
+
});
|
|
567
|
+
}));
|
|
568
|
+
return Object.freeze(documents);
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* Applies FEP-ef61 portable-object policy to every portable map in a compound
|
|
572
|
+
* snapshot without changing the map-local cryptographic inputs.
|
|
573
|
+
*
|
|
574
|
+
* @internal
|
|
575
|
+
*/
|
|
576
|
+
async function verifyCompoundPortableObjectProofs(json, limits, options = {}) {
|
|
577
|
+
const discovered = discoverCompoundProofDocuments(json, limits);
|
|
578
|
+
if (discovered.status !== "ok") return discovered;
|
|
579
|
+
const proofs = await verifyDiscoveredProofDocuments(discovered, options);
|
|
580
|
+
const proofByPath = new Map(proofs.map((proof) => [proof.path, proof]));
|
|
581
|
+
const verifyPortableObject = async (object) => {
|
|
582
|
+
const metadata = {
|
|
583
|
+
path: object.path,
|
|
584
|
+
id: object.id,
|
|
585
|
+
depth: object.depth
|
|
586
|
+
};
|
|
587
|
+
if (object.depth > 0 && !Object.hasOwn(object.document, "@context")) return Object.freeze({
|
|
588
|
+
...metadata,
|
|
589
|
+
verified: false,
|
|
590
|
+
reason: { type: "missingContext" }
|
|
591
|
+
});
|
|
592
|
+
const proof = proofByPath.get(object.path);
|
|
593
|
+
const key = proof?.verified === true ? proof.key : null;
|
|
594
|
+
try {
|
|
595
|
+
const policy = await verifyPortableObjectProofPolicy(object.document, key, options);
|
|
596
|
+
return policy.verified ? Object.freeze({
|
|
597
|
+
...metadata,
|
|
598
|
+
id: policy.objectId,
|
|
599
|
+
verified: true,
|
|
600
|
+
keys: policy.keys
|
|
601
|
+
}) : Object.freeze({
|
|
602
|
+
...metadata,
|
|
603
|
+
verified: false,
|
|
604
|
+
reason: policy.reason
|
|
605
|
+
});
|
|
606
|
+
} catch {
|
|
607
|
+
return Object.freeze({
|
|
608
|
+
...metadata,
|
|
609
|
+
verified: false,
|
|
610
|
+
reason: { type: "invalidPortableObject" }
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
};
|
|
614
|
+
const collected = collectPortableObjects(discovered.snapshot);
|
|
615
|
+
const objectByPath = new Map(collected.map((object) => [object.path, object]));
|
|
616
|
+
const isKeyCandidate = (object) => object.keyOf != null && !Object.hasOwn(object.document, "proof");
|
|
617
|
+
const results = /* @__PURE__ */ new Map();
|
|
618
|
+
for (const result of await Promise.all(collected.filter((object) => !isKeyCandidate(object)).map(verifyPortableObject))) results.set(result.path, result);
|
|
619
|
+
let projections = 0;
|
|
620
|
+
for (const object of collected) {
|
|
621
|
+
if (!isKeyCandidate(object)) continue;
|
|
622
|
+
const { path, member } = object.keyOf;
|
|
623
|
+
const parent = results.get(path);
|
|
624
|
+
const parentObject = objectByPath.get(path);
|
|
625
|
+
if (parent?.verified === true && parentObject != null && projections++ < MAX_KEY_PROJECTIONS && await isEmbeddedPortableKey(parentObject.document, member, object.document, parent.id)) continue;
|
|
626
|
+
results.set(object.path, await verifyPortableObject(object));
|
|
627
|
+
}
|
|
628
|
+
const portableObjects = Object.freeze(collected.flatMap((object) => {
|
|
629
|
+
const result = results.get(object.path);
|
|
630
|
+
return result == null ? [] : [result];
|
|
631
|
+
}));
|
|
632
|
+
return {
|
|
633
|
+
status: "ok",
|
|
634
|
+
verified: portableObjects.length > 0 && proofs.every((proof) => proof.verified) && portableObjects.every((object) => object.verified),
|
|
635
|
+
snapshot: discovered.snapshot,
|
|
636
|
+
proofs,
|
|
637
|
+
portableObjects,
|
|
638
|
+
statistics: discovered.statistics
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* The maximum number of embedded keys per compound document that are checked
|
|
643
|
+
* by {@link isEmbeddedPortableKey}, each of which expands a projection of its
|
|
644
|
+
* parent. Keys beyond it have to carry their own proofs.
|
|
645
|
+
*/
|
|
646
|
+
const MAX_KEY_PROJECTIONS = 64;
|
|
647
|
+
const SECURITY = "https://w3id.org/security#";
|
|
648
|
+
const KEY_TYPES = /* @__PURE__ */ new Set([`${SECURITY}Key`, `${SECURITY}Multikey`]);
|
|
649
|
+
const KEY_PROPERTIES = /* @__PURE__ */ new Set([
|
|
650
|
+
"@id",
|
|
651
|
+
"@type",
|
|
652
|
+
`${SECURITY}owner`,
|
|
653
|
+
`${SECURITY}controller`,
|
|
654
|
+
`${SECURITY}publicKeyPem`,
|
|
655
|
+
`${SECURITY}publicKeyMultibase`
|
|
656
|
+
]);
|
|
657
|
+
/**
|
|
658
|
+
* Tells whether an unsigned map embedded in a verified portable map is just
|
|
659
|
+
* a key of the portable object the parent is, e.g., a gateway key of
|
|
660
|
+
* a portable actor, which the parent's proof covers.
|
|
661
|
+
*
|
|
662
|
+
* The key's meaning depends on the contexts active where it sits, so it is
|
|
663
|
+
* decided on a projection of the parent that keeps only what those contexts
|
|
664
|
+
* depend on: the parent's `@context`, its types and ID, and the member that
|
|
665
|
+
* holds the key. That holds only if nothing else in the parent can change
|
|
666
|
+
* them, so both documents may only use contexts that define no keyword
|
|
667
|
+
* aliases and no scoped contexts; Fedify's preloaded contexts, the only
|
|
668
|
+
* remote ones the compound policy loads, alias just `id` and `type`.
|
|
669
|
+
*
|
|
670
|
+
* The projection must expand to the parent's verified ID with the key as the
|
|
671
|
+
* single value of `publicKey` or `assertionMethod`, and the key must be
|
|
672
|
+
* nothing but a `CryptographicKey` or `Multikey` whose ID is the parent's
|
|
673
|
+
* `ap:` or `ap+ef61:` URI, or its compatible identifier, plus a fragment,
|
|
674
|
+
* and whose owner or controller, if any, is the parent.
|
|
675
|
+
*/
|
|
676
|
+
async function isEmbeddedPortableKey(parent, member, key, parentId) {
|
|
677
|
+
const parentPortableId = getCanonicalPortableIdOf(parentId);
|
|
678
|
+
if (parentPortableId == null) return false;
|
|
679
|
+
if (!isSimpleContext(parent["@context"]) || Object.hasOwn(key, "@context") && !isSimpleContext(key["@context"])) return false;
|
|
680
|
+
const literalIds = [key.id, key["@id"]].filter((id) => id !== void 0);
|
|
681
|
+
if (literalIds.length < 1 || literalIds.some((id) => id !== literalIds[0]) || typeof literalIds[0] !== "string" || !isKeyIdOf(literalIds[0], parentPortableId)) return false;
|
|
682
|
+
const projection = {};
|
|
683
|
+
for (const property of [
|
|
684
|
+
"@context",
|
|
685
|
+
"type",
|
|
686
|
+
"@type",
|
|
687
|
+
"id",
|
|
688
|
+
"@id"
|
|
689
|
+
]) if (Object.hasOwn(parent, property)) projection[property] = parent[property];
|
|
690
|
+
projection[member] = key;
|
|
691
|
+
let expanded;
|
|
692
|
+
try {
|
|
693
|
+
expanded = await jsonld.expand(structuredClone(projection), {
|
|
694
|
+
documentLoader: getNormalizationContextLoader(preloadedOnlyDocumentLoader),
|
|
695
|
+
keepFreeFloatingNodes: true
|
|
696
|
+
});
|
|
697
|
+
} catch {
|
|
698
|
+
return false;
|
|
699
|
+
}
|
|
700
|
+
if (expanded.length !== 1 || hasGraphKeyword(expanded)) return false;
|
|
701
|
+
const node = expanded[0];
|
|
702
|
+
if (!isJsonMap(node) || typeof node["@id"] !== "string" || getCanonicalPortableIdOf(node["@id"]) !== parentPortableId) return false;
|
|
703
|
+
const properties = Object.keys(node).filter((property) => property !== "@id" && property !== "@type");
|
|
704
|
+
if (properties.length !== 1 || properties[0] !== `${SECURITY}${member}`) return false;
|
|
705
|
+
const values = node[properties[0]];
|
|
706
|
+
if (!Array.isArray(values) || values.length !== 1) return false;
|
|
707
|
+
const expandedKey = values[0];
|
|
708
|
+
if (!isJsonMap(expandedKey) || expandedKey["@id"] !== literalIds[0] || Object.keys(expandedKey).some((property) => !KEY_PROPERTIES.has(property))) return false;
|
|
709
|
+
const types = expandedKey["@type"];
|
|
710
|
+
if (types !== void 0 && (!Array.isArray(types) || types.some((type) => typeof type !== "string" || !KEY_TYPES.has(type)))) return false;
|
|
711
|
+
const pem = expandedKey[`${SECURITY}publicKeyPem`];
|
|
712
|
+
const multibase = expandedKey[`${SECURITY}publicKeyMultibase`];
|
|
713
|
+
if (pem === void 0 && multibase === void 0) return false;
|
|
714
|
+
if (pem !== void 0 && !isSingleStringValue(pem)) return false;
|
|
715
|
+
if (multibase !== void 0 && !isSingleStringValue(multibase, `${SECURITY}multibase`)) return false;
|
|
716
|
+
for (const property of [`${SECURITY}owner`, `${SECURITY}controller`]) {
|
|
717
|
+
const owners = expandedKey[property];
|
|
718
|
+
if (owners === void 0) continue;
|
|
719
|
+
if (!Array.isArray(owners) || owners.length !== 1 || !isJsonMap(owners[0]) || Object.keys(owners[0]).length !== 1 || typeof owners[0]["@id"] !== "string" || getCanonicalPortableIdOf(owners[0]["@id"]) !== parentPortableId) return false;
|
|
720
|
+
}
|
|
721
|
+
return true;
|
|
722
|
+
}
|
|
723
|
+
function getCanonicalPortableIdOf(id) {
|
|
724
|
+
try {
|
|
725
|
+
return getCanonicalPortableId(parseIri(id));
|
|
726
|
+
} catch (error) {
|
|
727
|
+
if (error instanceof TypeError) return null;
|
|
728
|
+
throw error;
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
/**
|
|
732
|
+
* Checks whether an ID is the given portable object's `ap:` or `ap+ef61:`
|
|
733
|
+
* URI, or its compatible identifier, plus a non-empty fragment. The ID is
|
|
734
|
+
* compared as it is written, so an ID that URL parsing would turn into
|
|
735
|
+
* the portable object's, e.g., through dot segments, is not one of its keys.
|
|
736
|
+
*/
|
|
737
|
+
function isKeyIdOf(id, portableId) {
|
|
738
|
+
const canonical = getRawCanonicalPortableId(id);
|
|
739
|
+
if (canonical == null) return false;
|
|
740
|
+
const hash = canonical.indexOf("#");
|
|
741
|
+
return hash >= 0 && hash < canonical.length - 1 && canonical.slice(0, hash) === portableId;
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* Checks whether a `@context` value defines no keyword aliases and no scoped
|
|
745
|
+
* contexts: only references to contexts and term definitions that map terms
|
|
746
|
+
* to IRIs, optionally with an `@id` or `@vocab` type coercion or a datatype.
|
|
747
|
+
*/
|
|
748
|
+
function isSimpleContext(context) {
|
|
749
|
+
if (typeof context === "string") return true;
|
|
750
|
+
if (Array.isArray(context)) return context.every(isSimpleContext);
|
|
751
|
+
if (context === void 0 || !isCompoundProofJsonObject(context)) return false;
|
|
752
|
+
return Object.entries(context).every(([term, definition]) => {
|
|
753
|
+
if (term.startsWith("@")) return false;
|
|
754
|
+
if (typeof definition === "string") return !definition.startsWith("@");
|
|
755
|
+
if (!isCompoundProofJsonObject(definition)) return false;
|
|
756
|
+
const { "@id": id, "@type": type, ...rest } = definition;
|
|
757
|
+
return Object.keys(rest).length < 1 && typeof id === "string" && !id.startsWith("@") && (type === void 0 || type === "@id" || type === "@vocab" || typeof type === "string" && !type.startsWith("@"));
|
|
758
|
+
});
|
|
759
|
+
}
|
|
760
|
+
function hasGraphKeyword(value) {
|
|
761
|
+
if (Array.isArray(value)) return value.some(hasGraphKeyword);
|
|
762
|
+
if (!isJsonMap(value)) return false;
|
|
763
|
+
return Object.entries(value).some(([key, child]) => key === "@graph" || key === "@included" || key === "@reverse" || hasGraphKeyword(child));
|
|
764
|
+
}
|
|
765
|
+
/**
|
|
766
|
+
* Checks whether expanded values are a single plain string, optionally with
|
|
767
|
+
* the given datatype.
|
|
768
|
+
*/
|
|
769
|
+
function isSingleStringValue(values, datatype) {
|
|
770
|
+
if (!Array.isArray(values) || values.length !== 1) return false;
|
|
771
|
+
const value = values[0];
|
|
772
|
+
if (!isJsonMap(value) || typeof value["@value"] !== "string") return false;
|
|
773
|
+
const keys = Object.keys(value);
|
|
774
|
+
return keys.length === 1 || datatype != null && keys.length === 2 && value["@type"] === datatype;
|
|
775
|
+
}
|
|
776
|
+
/**
|
|
777
|
+
* FEP-2277 core types that FEP-ef61 does not require to carry proofs.
|
|
778
|
+
*/
|
|
779
|
+
const UNSECURED_CORE_TYPES = /* @__PURE__ */ new Set([
|
|
780
|
+
"collection",
|
|
781
|
+
"link",
|
|
782
|
+
"verificationMethod",
|
|
783
|
+
"publicKey"
|
|
784
|
+
]);
|
|
785
|
+
/**
|
|
786
|
+
* Applies the FEP-ef61 proof policy, under the map-local compound-proof
|
|
787
|
+
* profile, to every portable object embedded in a document that a gateway is
|
|
788
|
+
* about to serve, e.g., the items of a portable collection page. The root
|
|
789
|
+
* map itself is not checked, as the caller built it.
|
|
790
|
+
*
|
|
791
|
+
* Each map below the root whose direct `id` or `@id` is a portable ID or
|
|
792
|
+
* a compatible identifier is checked independently of the others:
|
|
793
|
+
*
|
|
794
|
+
* - A map with a direct `proof` is a separately secured document: the proof
|
|
795
|
+
* has to be a single map, the map has to carry its own `@context`, and
|
|
796
|
+
* the proof has to verify over the map exactly as it is served, with only
|
|
797
|
+
* its own proof removed, and be made with a key of the DID in the map's
|
|
798
|
+
* ID, as the map-local compound-proof profile requires.
|
|
799
|
+
* - A map without a proof has to be a collection, a link, or a key, which
|
|
800
|
+
* FEP-ef61 does not require to be secured. It is classified under its
|
|
801
|
+
* effective context, i.e., the `@context` values of its ancestors and
|
|
802
|
+
* itself, which therefore may only use the simple, preloaded contexts
|
|
803
|
+
* that the compound-proof policy accepts.
|
|
804
|
+
*
|
|
805
|
+
* Maps that merely refer to a portable object, i.e., have no member other
|
|
806
|
+
* than `id` or `@id`, and maps with non-portable IDs, are neither checked nor
|
|
807
|
+
* verified. Every `@context` in the document, however, has to be simple, and
|
|
808
|
+
* every portable object that the expanded document describes has to be
|
|
809
|
+
* a map found this way, so that no context can hide a portable object from
|
|
810
|
+
* this check, e.g., by aliasing `@id` or redefining `proof`. The input must be a finite, already-materialized JSON tree, such
|
|
811
|
+
* as the compact JSON-LD document that is about to be served.
|
|
812
|
+
*
|
|
813
|
+
* @param json The document to check.
|
|
814
|
+
* @param options The options for verifying the proofs. Proofs whose
|
|
815
|
+
* verification methods are not `did:key` DID URLs may make
|
|
816
|
+
* the document loader fetch DID documents.
|
|
817
|
+
* @returns The result, which names the first refused map, if any.
|
|
818
|
+
* @internal
|
|
819
|
+
*/
|
|
820
|
+
async function verifyServedPortableObjects(json, options = {}) {
|
|
821
|
+
json = JSON.parse(JSON.stringify(json));
|
|
822
|
+
const collected = collectServedPortableMaps(json);
|
|
823
|
+
if (!("maps" in collected)) return collected;
|
|
824
|
+
const maps = collected.maps;
|
|
825
|
+
const discovered = /* @__PURE__ */ new Map();
|
|
826
|
+
const ids = maps.map((candidate) => candidate.id);
|
|
827
|
+
const rootId = isJsonMap(json) ? [json.id, json["@id"]].find(isPortableIdValue) : void 0;
|
|
828
|
+
if (rootId != null) ids.push(rootId);
|
|
829
|
+
for (const id of ids) discovered.set(id, (discovered.get(id) ?? 0) + 1);
|
|
830
|
+
let hidden;
|
|
831
|
+
try {
|
|
832
|
+
hidden = findHiddenPortableObject(await jsonld.expand(json, { documentLoader: preloadedOnlyDocumentLoader }), discovered);
|
|
833
|
+
} catch {
|
|
834
|
+
return {
|
|
835
|
+
verified: false,
|
|
836
|
+
path: "",
|
|
837
|
+
id: "",
|
|
838
|
+
reason: "unsupportedContext"
|
|
839
|
+
};
|
|
840
|
+
}
|
|
841
|
+
if (hidden != null) return {
|
|
842
|
+
verified: false,
|
|
843
|
+
path: "",
|
|
844
|
+
id: hidden,
|
|
845
|
+
reason: "hiddenPortableObject"
|
|
846
|
+
};
|
|
847
|
+
for (const candidate of maps) {
|
|
848
|
+
const reason = await checkServedPortableMap(candidate, options);
|
|
849
|
+
if (reason != null) return {
|
|
850
|
+
verified: false,
|
|
851
|
+
path: candidate.path,
|
|
852
|
+
id: candidate.id,
|
|
853
|
+
reason
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
return { verified: true };
|
|
857
|
+
}
|
|
858
|
+
/**
|
|
859
|
+
* Finds a portable object that an expanded JSON-LD document describes, i.e.,
|
|
860
|
+
* a node with a portable `@id` and other members, more times than the given
|
|
861
|
+
* numbers of maps found for its ID.
|
|
862
|
+
*/
|
|
863
|
+
function findHiddenPortableObject(expanded, discovered) {
|
|
864
|
+
const remaining = new Map(discovered);
|
|
865
|
+
const pending = [expanded];
|
|
866
|
+
while (pending.length > 0) {
|
|
867
|
+
const value = pending.pop();
|
|
868
|
+
if (Array.isArray(value)) {
|
|
869
|
+
pending.push(...value);
|
|
870
|
+
continue;
|
|
871
|
+
}
|
|
872
|
+
if (!isJsonMap(value)) continue;
|
|
873
|
+
const id = value["@id"];
|
|
874
|
+
if (isPortableIdValue(id) && Object.keys(value).some((key) => key !== "@id")) {
|
|
875
|
+
const count = remaining.get(id) ?? 0;
|
|
876
|
+
if (count < 1) return id;
|
|
877
|
+
remaining.set(id, count - 1);
|
|
878
|
+
}
|
|
879
|
+
pending.push(...Object.values(value));
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
/**
|
|
883
|
+
* Checks whether a context uses only preloaded remote contexts and simple
|
|
884
|
+
* term definitions, so that the context in effect for a map can be told from
|
|
885
|
+
* the contexts of its ancestors.
|
|
886
|
+
*/
|
|
887
|
+
function isCheckableContext(context) {
|
|
888
|
+
return (Array.isArray(context) ? context : [context]).every((value) => typeof value === "string" ? Object.hasOwn(preloadedContexts, value) : !Array.isArray(value) && isSimpleContext(value));
|
|
889
|
+
}
|
|
890
|
+
/**
|
|
891
|
+
* Checks whether every remote context that a context refers to is preloaded,
|
|
892
|
+
* so that all of its definitions are known.
|
|
893
|
+
*/
|
|
894
|
+
function hasOnlyPreloadedRemoteContexts(context) {
|
|
895
|
+
return (Array.isArray(context) ? context : [context]).every((value) => typeof value === "string" ? Object.hasOwn(preloadedContexts, value) : !Array.isArray(value));
|
|
896
|
+
}
|
|
897
|
+
/**
|
|
898
|
+
* Checks whether a map or any map below it, outside proof and context values,
|
|
899
|
+
* has a portable `id` or `@id` and other members.
|
|
900
|
+
*/
|
|
901
|
+
function hasLiteralPortableObject(json) {
|
|
902
|
+
return findInJsonMaps(json, (map) => [map.id, map["@id"]].some(isPortableIdValue) && Object.keys(map).some((key) => key !== "id" && key !== "@id") ? true : void 0) ?? false;
|
|
903
|
+
}
|
|
904
|
+
function collectServedPortableMaps(json) {
|
|
905
|
+
const maps = [];
|
|
906
|
+
const seen = /* @__PURE__ */ new Set();
|
|
907
|
+
const pending = [{
|
|
908
|
+
value: json,
|
|
909
|
+
path: "",
|
|
910
|
+
depth: 0,
|
|
911
|
+
contexts: []
|
|
912
|
+
}];
|
|
913
|
+
while (pending.length > 0) {
|
|
914
|
+
const { value, path, depth, contexts } = pending.pop();
|
|
915
|
+
if (typeof value !== "object" || value == null || seen.has(value)) continue;
|
|
916
|
+
seen.add(value);
|
|
917
|
+
if (Array.isArray(value)) {
|
|
918
|
+
for (let index = value.length - 1; index >= 0; index--) pending.push({
|
|
919
|
+
value: value[index],
|
|
920
|
+
path: childPath(path, String(index)),
|
|
921
|
+
depth: depth + 1,
|
|
922
|
+
contexts
|
|
923
|
+
});
|
|
924
|
+
continue;
|
|
925
|
+
}
|
|
926
|
+
const map = value;
|
|
927
|
+
const hasContext = Object.hasOwn(map, "@context");
|
|
928
|
+
if (hasContext && !isCheckableContext(map["@context"])) {
|
|
929
|
+
if (hasOnlyPreloadedRemoteContexts(map["@context"]) && !hasLiteralPortableObject(map)) continue;
|
|
930
|
+
const id = [map.id, map["@id"]].find((v) => typeof v === "string");
|
|
931
|
+
return {
|
|
932
|
+
verified: false,
|
|
933
|
+
path,
|
|
934
|
+
id: typeof id === "string" ? id : "",
|
|
935
|
+
reason: "unsupportedContext"
|
|
936
|
+
};
|
|
937
|
+
}
|
|
938
|
+
const mapContexts = hasContext ? [...contexts, map["@context"]] : contexts;
|
|
939
|
+
const id = [map.id, map["@id"]].find(isPortableIdValue);
|
|
940
|
+
if (depth > 0 && id != null && Object.keys(map).some((key) => key !== "id" && key !== "@id")) maps.push({
|
|
941
|
+
path,
|
|
942
|
+
id,
|
|
943
|
+
map,
|
|
944
|
+
contexts: mapContexts
|
|
945
|
+
});
|
|
946
|
+
const keys = Object.keys(map);
|
|
947
|
+
for (let index = keys.length - 1; index >= 0; index--) {
|
|
948
|
+
const key = keys[index];
|
|
949
|
+
if (key === "proof" || key === "@context") continue;
|
|
950
|
+
pending.push({
|
|
951
|
+
value: map[key],
|
|
952
|
+
path: childPath(path, key),
|
|
953
|
+
depth: depth + 1,
|
|
954
|
+
contexts: mapContexts
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
maps.sort((left, right) => comparePaths(left.path, right.path));
|
|
959
|
+
return { maps };
|
|
960
|
+
}
|
|
961
|
+
async function checkServedPortableMap({ map, contexts }, options) {
|
|
962
|
+
if (Object.hasOwn(map, "proof")) {
|
|
963
|
+
if (!isJsonMap(map.proof)) return "unsupportedProofShape";
|
|
964
|
+
if (!Object.hasOwn(map, "@context")) return "missingContext";
|
|
965
|
+
try {
|
|
966
|
+
const policy = await verifyPortableObjectProofPolicy(map, await verifyMapLocalProof(map, options), options);
|
|
967
|
+
return policy.verified ? null : policy.reason.type;
|
|
968
|
+
} catch (error) {
|
|
969
|
+
if (error instanceof TypeError) return "invalidPortableObject";
|
|
970
|
+
throw error;
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
const flattened = contexts.flatMap((context) => Array.isArray(context) ? context : [context]);
|
|
974
|
+
if (flattened.length < 1 || !isSimpleContext(flattened)) return "unsupportedContext";
|
|
975
|
+
let expanded;
|
|
976
|
+
try {
|
|
977
|
+
expanded = await jsonld.expand({
|
|
978
|
+
...map,
|
|
979
|
+
"@context": flattened
|
|
980
|
+
}, { documentLoader: preloadedOnlyDocumentLoader });
|
|
981
|
+
} catch {
|
|
982
|
+
return "unsupportedContext";
|
|
983
|
+
}
|
|
984
|
+
if (expanded.length !== 1 || !isJsonMap(expanded[0])) return "invalidPortableObject";
|
|
985
|
+
const type = classifyFep2277CoreType(expanded[0]);
|
|
986
|
+
return UNSECURED_CORE_TYPES.has(type) ? null : "missingProof";
|
|
987
|
+
}
|
|
988
|
+
//#endregion
|
|
989
|
+
export { inspectCompoundPortableObjectApplicability as a, verifyServedPortableObjects as c, findUnsupportedCompoundProofShape as i, discoverCompoundProofDocuments as n, verifyCompoundPortableObjectProofs as o, findEmbeddedProofWithoutContext as r, verifyCompoundProofDocuments as s, containsCompoundPortableObject as t };
|