@fedify/vocab 2.4.0-dev.2063 → 2.4.0-dev.2071
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/deno.json +1 -1
- package/dist/mod.cjs +15240 -10769
- package/dist/mod.d.cts +5 -2
- package/dist/mod.d.ts +5 -2
- package/dist/mod.js +14449 -9978
- package/dist-tests/{actor-hpNVHQvY.mjs → actor-CPbEFVm1.mjs} +2 -2
- package/dist-tests/actor.test.mjs +3 -3
- package/dist-tests/{esm-CuilqPDu.mjs → esm-C9nxP5_e.mjs} +1 -1
- package/dist-tests/gateway.test.mjs +490 -39
- package/dist-tests/{lookup-Bi6dOTm_.mjs → lookup-e0QSMZGC.mjs} +48 -49
- package/dist-tests/lookup.test.mjs +3 -3
- package/dist-tests/portable-lookup.test.mjs +100 -4
- package/dist-tests/signed-representation.test.mjs +1 -1
- package/dist-tests/translation.test.mjs +3 -3
- package/dist-tests/type.test.mjs +1 -1
- package/dist-tests/{vocab-DZq1JW3s.mjs → vocab-BHtBRusa.mjs} +14847 -10375
- package/dist-tests/vocab.test.mjs +1 -1
- package/package.json +4 -4
- package/src/gateway.test.ts +673 -54
- package/src/lookup.ts +89 -65
- package/src/portable-lookup.test.ts +133 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import "temporal-polyfill";
|
|
2
2
|
globalThis.addEventListener = () => {};
|
|
3
|
-
import { F as Person, P as Organization, U as Service, a as Application, b as Group } from "./vocab-
|
|
3
|
+
import { F as Person, P as Organization, U as Service, a as Application, b as Group } from "./vocab-BHtBRusa.mjs";
|
|
4
4
|
import { t as getTypeId } from "./type-BMdjm4Eg.mjs";
|
|
5
5
|
import { canonicalizePortableUri, formatIri, fromCompatibleEf61Id, isGatewayUrl } from "@fedify/vocab-runtime";
|
|
6
6
|
import { lookupWebFinger } from "@fedify/webfinger";
|
|
@@ -8,7 +8,7 @@ import { SpanStatusCode, trace } from "@opentelemetry/api";
|
|
|
8
8
|
import { domainToASCII, domainToUnicode } from "node:url";
|
|
9
9
|
//#region deno.json
|
|
10
10
|
var name = "@fedify/vocab";
|
|
11
|
-
var version = "2.4.0-dev.
|
|
11
|
+
var version = "2.4.0-dev.2071+8b9dccb9";
|
|
12
12
|
//#endregion
|
|
13
13
|
//#region src/actor.ts
|
|
14
14
|
const ACTOR_DISCOVERY_HISTOGRAM_BUCKETS = [
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import "temporal-polyfill";
|
|
2
2
|
globalThis.addEventListener = () => {};
|
|
3
|
-
import { F as Person, P as Organization, Q as __exportAll, U as Service, a as Application, b as Group } from "./vocab-
|
|
4
|
-
import { t as esm_default } from "./esm-
|
|
5
|
-
import { a as normalizeActorHandle, i as isActor, n as getActorHandle, r as getActorTypeName, t as getActorClassByTypeName } from "./actor-
|
|
3
|
+
import { F as Person, P as Organization, Q as __exportAll, U as Service, a as Application, b as Group } from "./vocab-BHtBRusa.mjs";
|
|
4
|
+
import { t as esm_default } from "./esm-C9nxP5_e.mjs";
|
|
5
|
+
import { a as normalizeActorHandle, i as isActor, n as getActorHandle, r as getActorTypeName, t as getActorClassByTypeName } from "./actor-CPbEFVm1.mjs";
|
|
6
6
|
import { createTestMeterProvider, test } from "@fedify/fixture";
|
|
7
7
|
import { deepStrictEqual, ok, rejects, strictEqual, throws } from "node:assert/strict";
|
|
8
8
|
//#region ../../node_modules/.pnpm/fast-check@3.23.2/node_modules/fast-check/lib/esm/check/precondition/PreconditionFailure.js
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import "temporal-polyfill";
|
|
2
2
|
globalThis.addEventListener = () => {};
|
|
3
|
-
import { $ as __toESM, Z as __commonJSMin } from "./vocab-
|
|
3
|
+
import { $ as __toESM, Z as __commonJSMin } from "./vocab-BHtBRusa.mjs";
|
|
4
4
|
//#endregion
|
|
5
5
|
//#region ../../node_modules/.pnpm/regexparam@3.0.0/node_modules/regexparam/dist/index.mjs
|
|
6
6
|
var import_glob_to_regexp = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import "temporal-polyfill";
|
|
2
2
|
globalThis.addEventListener = () => {};
|
|
3
|
-
import { F as Person, M as OrderedCollection, c as CollectionPage, k as Note, s as Collection, u as Create } from "./vocab-
|
|
4
|
-
import { t as esm_default } from "./esm-
|
|
5
|
-
import { n as traverseCollection } from "./lookup-
|
|
3
|
+
import { F as Person, M as OrderedCollection, c as CollectionPage, k as Note, s as Collection, u as Create } from "./vocab-BHtBRusa.mjs";
|
|
4
|
+
import { t as esm_default } from "./esm-C9nxP5_e.mjs";
|
|
5
|
+
import { n as traverseCollection } from "./lookup-e0QSMZGC.mjs";
|
|
6
6
|
import { t as assertInstanceOf } from "./utils-Df2odC27.mjs";
|
|
7
7
|
import { createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
|
|
8
8
|
import { deepStrictEqual, ok, rejects } from "node:assert/strict";
|
|
9
|
-
import { FetchError, getDocumentLoader } from "@fedify/vocab-runtime";
|
|
9
|
+
import { FetchError, getDocumentLoader, parseIri } from "@fedify/vocab-runtime";
|
|
10
10
|
//#region src/gateway.test.ts
|
|
11
11
|
const did = "did:key:z6Mkabc";
|
|
12
12
|
const objectId = `ap+ef61://${did}/objects/1`;
|
|
@@ -507,11 +507,16 @@ test("getObject() shares context documents within a dereference", async (t) => {
|
|
|
507
507
|
const loaded = [];
|
|
508
508
|
const contextLoader = async (url) => {
|
|
509
509
|
loaded.push(url);
|
|
510
|
+
if (url === contextUrl) return {
|
|
511
|
+
contextUrl: null,
|
|
512
|
+
documentUrl: url,
|
|
513
|
+
document: { "@context": {} }
|
|
514
|
+
};
|
|
510
515
|
return await mockDocumentLoader(url);
|
|
511
516
|
};
|
|
512
517
|
const object = await (await createActivity()).getObject({
|
|
513
518
|
documentLoader: createLoader({ [`https://gw1.example${gatewayPath}`]: {
|
|
514
|
-
...
|
|
519
|
+
...document,
|
|
515
520
|
attributedTo: "https://example.com/person"
|
|
516
521
|
} }),
|
|
517
522
|
contextLoader,
|
|
@@ -519,13 +524,13 @@ test("getObject() shares context documents within a dereference", async (t) => {
|
|
|
519
524
|
verifyPortableObject: createVerifier()
|
|
520
525
|
});
|
|
521
526
|
assertInstanceOf(object, Note);
|
|
522
|
-
deepStrictEqual(loaded, []);
|
|
527
|
+
deepStrictEqual(loaded, [contextUrl]);
|
|
523
528
|
deepStrictEqual((await object.getAttribution({ documentLoader: createLoader({ "https://example.com/person": {
|
|
524
|
-
"@context": "https://www.w3.org/ns/activitystreams",
|
|
529
|
+
"@context": ["https://www.w3.org/ns/activitystreams", contextUrl],
|
|
525
530
|
id: "https://example.com/person",
|
|
526
531
|
type: "Person"
|
|
527
532
|
} }) }))?.id, new URL("https://example.com/person"));
|
|
528
|
-
|
|
533
|
+
deepStrictEqual(loaded, [contextUrl, contextUrl]);
|
|
529
534
|
});
|
|
530
535
|
await t.step("failed context loads are retried", async () => {
|
|
531
536
|
let failures = 1;
|
|
@@ -763,14 +768,222 @@ test("accessors do not trust objects embedded in unsecured collections", async (
|
|
|
763
768
|
suppressError: true
|
|
764
769
|
})), []);
|
|
765
770
|
});
|
|
766
|
-
|
|
767
|
-
const
|
|
768
|
-
const
|
|
771
|
+
function createRedirectingLoader(responses) {
|
|
772
|
+
const fetched = [];
|
|
773
|
+
const loader = async (url) => {
|
|
774
|
+
fetched.push(url);
|
|
775
|
+
const response = responses[url];
|
|
776
|
+
if (response == null) throw new FetchError(url, "HTTP 404", new globalThis.Response(null, { status: 404 }));
|
|
777
|
+
return {
|
|
778
|
+
contextUrl: null,
|
|
779
|
+
...response
|
|
780
|
+
};
|
|
781
|
+
};
|
|
782
|
+
return Object.assign(loader, { fetched });
|
|
783
|
+
}
|
|
784
|
+
function createHttpActivity(object) {
|
|
785
|
+
return Create.fromJsonLd({
|
|
769
786
|
"@context": "https://www.w3.org/ns/activitystreams",
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
787
|
+
type: "Create",
|
|
788
|
+
id: "https://example.com/activities/1",
|
|
789
|
+
object
|
|
790
|
+
}, { contextLoader: mockDocumentLoader });
|
|
791
|
+
}
|
|
792
|
+
const compatibleNoteId = gatewayUrl("https://gw.example", "/objects/1");
|
|
793
|
+
test("accessors dereference compatible identifiers through their gateways", async (t) => {
|
|
794
|
+
await t.step("through the named gateway", async () => {
|
|
795
|
+
const documentLoader = createLoader({ [compatibleNoteId]: note() });
|
|
796
|
+
const verifier = createRecordingVerifier();
|
|
797
|
+
const activity = await createHttpActivity(compatibleNoteId);
|
|
798
|
+
const object = await activity.getObject({
|
|
799
|
+
documentLoader,
|
|
800
|
+
contextLoader: mockDocumentLoader,
|
|
801
|
+
verifyPortableObject: verifier
|
|
802
|
+
});
|
|
803
|
+
assertInstanceOf(object, Note);
|
|
804
|
+
deepStrictEqual(object.id, parseIri(objectId));
|
|
805
|
+
deepStrictEqual(documentLoader.fetched, [compatibleNoteId]);
|
|
806
|
+
deepStrictEqual(verifier.calls.length, 1);
|
|
807
|
+
const { options } = verifier.calls[0];
|
|
808
|
+
deepStrictEqual(options.documentUrl, new URL(compatibleNoteId));
|
|
809
|
+
deepStrictEqual(options.gateways, void 0);
|
|
810
|
+
deepStrictEqual(options.gatewayHints, [new URL("https://gw.example")]);
|
|
811
|
+
ok(options.referrer?.object === activity);
|
|
812
|
+
ok(await activity.getObject({ documentLoader: createLoader({}) }) === object);
|
|
813
|
+
});
|
|
814
|
+
await t.step("then through explicit gateways", async () => {
|
|
815
|
+
const documentLoader = createLoader({ [gatewayUrl("https://gw2.example", "/objects/1")]: note() });
|
|
816
|
+
const verifier = createRecordingVerifier();
|
|
817
|
+
assertInstanceOf(await (await createHttpActivity(compatibleNoteId)).getObject({
|
|
818
|
+
documentLoader,
|
|
819
|
+
contextLoader: mockDocumentLoader,
|
|
820
|
+
gateways: ["https://gw2.example", "https://gw.example"],
|
|
821
|
+
verifyPortableObject: verifier
|
|
822
|
+
}), Note);
|
|
823
|
+
deepStrictEqual(documentLoader.fetched, [compatibleNoteId, gatewayUrl("https://gw2.example", "/objects/1")]);
|
|
824
|
+
deepStrictEqual(verifier.calls[0].options.gateways, [new URL("https://gw2.example"), new URL("https://gw.example")]);
|
|
825
|
+
deepStrictEqual(verifier.calls[0].options.gatewayHints, void 0);
|
|
826
|
+
const documentLoader2 = createLoader({ [compatibleNoteId]: note() });
|
|
827
|
+
assertInstanceOf(await (await createHttpActivity(compatibleNoteId)).getObject({
|
|
828
|
+
documentLoader: documentLoader2,
|
|
829
|
+
contextLoader: mockDocumentLoader,
|
|
830
|
+
gateways: [],
|
|
831
|
+
verifyPortableObject: createVerifier()
|
|
832
|
+
}), Note);
|
|
833
|
+
deepStrictEqual(documentLoader2.fetched, [compatibleNoteId]);
|
|
834
|
+
});
|
|
835
|
+
await t.step("in plural accessors", async () => {
|
|
836
|
+
const documentLoader = createLoader({ [compatibleNoteId]: note() });
|
|
837
|
+
deepStrictEqual((await Array.fromAsync((await createHttpActivity([compatibleNoteId])).getObjects({
|
|
838
|
+
documentLoader,
|
|
839
|
+
contextLoader: mockDocumentLoader,
|
|
840
|
+
verifyPortableObject: createVerifier()
|
|
841
|
+
}))).map((o) => o.id), [parseIri(objectId)]);
|
|
842
|
+
deepStrictEqual(documentLoader.fetched, [compatibleNoteId]);
|
|
773
843
|
});
|
|
844
|
+
});
|
|
845
|
+
test("accessors reject forged objects at compatible identifiers", async () => {
|
|
846
|
+
const forgedId = gatewayUrl("https://evil.example", "/objects/1");
|
|
847
|
+
const forged = {
|
|
848
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
849
|
+
id: forgedId,
|
|
850
|
+
type: "Note",
|
|
851
|
+
attributedTo: gatewayUrl("https://evil.example", "/actor"),
|
|
852
|
+
content: "Forged"
|
|
853
|
+
};
|
|
854
|
+
for (const crossOrigin of [
|
|
855
|
+
void 0,
|
|
856
|
+
"ignore",
|
|
857
|
+
"trust"
|
|
858
|
+
]) {
|
|
859
|
+
const verifier = createRecordingVerifier();
|
|
860
|
+
deepStrictEqual(await (await createHttpActivity(forgedId)).getObject({
|
|
861
|
+
documentLoader: createLoader({ [forgedId]: forged }),
|
|
862
|
+
contextLoader: mockDocumentLoader,
|
|
863
|
+
verifyPortableObject: verifier,
|
|
864
|
+
crossOrigin
|
|
865
|
+
}), null);
|
|
866
|
+
deepStrictEqual(verifier.calls, []);
|
|
867
|
+
deepStrictEqual(await (await createHttpActivity(forgedId)).getObject({
|
|
868
|
+
documentLoader: createLoader({ [forgedId]: note() }),
|
|
869
|
+
contextLoader: mockDocumentLoader,
|
|
870
|
+
verifyPortableObject: createVerifier(false),
|
|
871
|
+
crossOrigin
|
|
872
|
+
}), null);
|
|
873
|
+
}
|
|
874
|
+
await rejects(async () => await (await createHttpActivity(forgedId)).getObject({
|
|
875
|
+
documentLoader: createLoader({ [forgedId]: note() }),
|
|
876
|
+
contextLoader: mockDocumentLoader,
|
|
877
|
+
verifyPortableObject: createVerifier(false),
|
|
878
|
+
crossOrigin: "throw"
|
|
879
|
+
}), /No gateway returned a valid portable object/);
|
|
880
|
+
});
|
|
881
|
+
test("accessors reject malformed compatible identifiers", async () => {
|
|
882
|
+
const malformed = [`${compatibleNoteId}?@gateway=https%3A%2F%2Fgw2.example`, compatibleNoteId.replace("https://", "https://user:pass@")];
|
|
883
|
+
for (const url of malformed) {
|
|
884
|
+
const documentLoader = createLoader({ [url]: note() });
|
|
885
|
+
const verifier = createRecordingVerifier();
|
|
886
|
+
const activity = await createHttpActivity(url);
|
|
887
|
+
deepStrictEqual(await activity.getObject({
|
|
888
|
+
documentLoader,
|
|
889
|
+
contextLoader: mockDocumentLoader,
|
|
890
|
+
verifyPortableObject: verifier
|
|
891
|
+
}), null);
|
|
892
|
+
deepStrictEqual(await activity.getObject({
|
|
893
|
+
documentLoader,
|
|
894
|
+
contextLoader: mockDocumentLoader,
|
|
895
|
+
verifyPortableObject: verifier,
|
|
896
|
+
crossOrigin: "throw",
|
|
897
|
+
suppressError: true
|
|
898
|
+
}), null);
|
|
899
|
+
await rejects(async () => await activity.getObject({
|
|
900
|
+
documentLoader,
|
|
901
|
+
contextLoader: mockDocumentLoader,
|
|
902
|
+
verifyPortableObject: verifier,
|
|
903
|
+
crossOrigin: "throw"
|
|
904
|
+
}), /malformed FEP-ef61 compatible identifier/);
|
|
905
|
+
deepStrictEqual(documentLoader.fetched, []);
|
|
906
|
+
deepStrictEqual(verifier.calls, []);
|
|
907
|
+
}
|
|
908
|
+
});
|
|
909
|
+
test("accessors keep fetching compatible identifiers as HTTP(S) URLs without verifyPortableObject", async (t) => {
|
|
910
|
+
const document = {
|
|
911
|
+
...note(),
|
|
912
|
+
id: compatibleNoteId
|
|
913
|
+
};
|
|
914
|
+
await t.step("singular accessors", async () => {
|
|
915
|
+
const documentLoader = createLoader({ [compatibleNoteId]: document });
|
|
916
|
+
const activity = await createHttpActivity(compatibleNoteId);
|
|
917
|
+
const object = await activity.getObject({
|
|
918
|
+
documentLoader,
|
|
919
|
+
contextLoader: mockDocumentLoader
|
|
920
|
+
});
|
|
921
|
+
assertInstanceOf(object, Note);
|
|
922
|
+
deepStrictEqual(object.id, new URL(compatibleNoteId));
|
|
923
|
+
deepStrictEqual(await activity.getObject({
|
|
924
|
+
documentLoader,
|
|
925
|
+
contextLoader: mockDocumentLoader,
|
|
926
|
+
verifyPortableObject: createVerifier()
|
|
927
|
+
}), null);
|
|
928
|
+
deepStrictEqual(documentLoader.fetched, [compatibleNoteId, compatibleNoteId]);
|
|
929
|
+
});
|
|
930
|
+
await t.step("plural accessors", async () => {
|
|
931
|
+
const documentLoader = createLoader({ [compatibleNoteId]: document });
|
|
932
|
+
const activity = await createHttpActivity([compatibleNoteId]);
|
|
933
|
+
deepStrictEqual((await Array.fromAsync(activity.getObjects({
|
|
934
|
+
documentLoader,
|
|
935
|
+
contextLoader: mockDocumentLoader
|
|
936
|
+
}))).map((o) => o.id), [new URL(compatibleNoteId)]);
|
|
937
|
+
deepStrictEqual(await Array.fromAsync(activity.getObjects({
|
|
938
|
+
documentLoader,
|
|
939
|
+
contextLoader: mockDocumentLoader,
|
|
940
|
+
verifyPortableObject: createVerifier()
|
|
941
|
+
})), []);
|
|
942
|
+
});
|
|
943
|
+
await t.step("redirects to ordinary objects", async () => {
|
|
944
|
+
const plainUrl = "https://gw.example/notes/1";
|
|
945
|
+
const documentLoader = createRedirectingLoader({ [compatibleNoteId]: {
|
|
946
|
+
documentUrl: plainUrl,
|
|
947
|
+
document: {
|
|
948
|
+
...note(),
|
|
949
|
+
id: plainUrl
|
|
950
|
+
}
|
|
951
|
+
} });
|
|
952
|
+
for (const activity of [await createHttpActivity(compatibleNoteId)]) {
|
|
953
|
+
deepStrictEqual((await activity.getObject({
|
|
954
|
+
documentLoader,
|
|
955
|
+
contextLoader: mockDocumentLoader
|
|
956
|
+
}))?.id, new URL(plainUrl));
|
|
957
|
+
deepStrictEqual(await activity.getObject({
|
|
958
|
+
documentLoader,
|
|
959
|
+
contextLoader: mockDocumentLoader,
|
|
960
|
+
verifyPortableObject: createVerifier()
|
|
961
|
+
}), null);
|
|
962
|
+
}
|
|
963
|
+
const activity = await createHttpActivity([compatibleNoteId]);
|
|
964
|
+
deepStrictEqual((await Array.fromAsync(activity.getObjects({
|
|
965
|
+
documentLoader,
|
|
966
|
+
contextLoader: mockDocumentLoader
|
|
967
|
+
}))).length, 1);
|
|
968
|
+
deepStrictEqual(await Array.fromAsync(activity.getObjects({
|
|
969
|
+
documentLoader,
|
|
970
|
+
contextLoader: mockDocumentLoader,
|
|
971
|
+
verifyPortableObject: createVerifier()
|
|
972
|
+
})), []);
|
|
973
|
+
});
|
|
974
|
+
await t.step("embedded objects", async () => {
|
|
975
|
+
deepStrictEqual((await (await createHttpActivity({
|
|
976
|
+
type: "Note",
|
|
977
|
+
id: gatewayUrl("https://example.com", "/objects/1"),
|
|
978
|
+
content: "Embedded"
|
|
979
|
+
})).getObject({
|
|
980
|
+
documentLoader: createLoader({}),
|
|
981
|
+
contextLoader: mockDocumentLoader
|
|
982
|
+
}))?.id, new URL(gatewayUrl("https://example.com", "/objects/1")));
|
|
983
|
+
});
|
|
984
|
+
});
|
|
985
|
+
test("accessors dereference compatible identifiers from portable objects", async () => {
|
|
986
|
+
const compatibleOutbox = gatewayUrl("https://gw.example", "/actor/outbox");
|
|
774
987
|
const person = (outbox) => Person.fromJsonLd({
|
|
775
988
|
"@context": "https://www.w3.org/ns/activitystreams",
|
|
776
989
|
id: `ap://${did}/actor`,
|
|
@@ -778,37 +991,275 @@ test("accessors reject compatible identifiers from portable objects", async () =
|
|
|
778
991
|
inbox: `ap://${did}/actor/inbox`,
|
|
779
992
|
outbox
|
|
780
993
|
}, { contextLoader: mockDocumentLoader });
|
|
781
|
-
const
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
994
|
+
const documentLoader = createRedirectingLoader({
|
|
995
|
+
[compatibleOutbox]: {
|
|
996
|
+
documentUrl: compatibleOutbox,
|
|
997
|
+
document: portableCollection("/actor/outbox")
|
|
998
|
+
},
|
|
999
|
+
"https://example.com/outbox": {
|
|
1000
|
+
documentUrl: compatibleOutbox,
|
|
1001
|
+
document: portableCollection("/actor/outbox")
|
|
1002
|
+
}
|
|
785
1003
|
});
|
|
786
|
-
for (const crossOrigin of [void 0, "trust"])
|
|
787
|
-
|
|
788
|
-
|
|
1004
|
+
for (const crossOrigin of [void 0, "trust"]) for (const outbox of [
|
|
1005
|
+
compatibleOutbox,
|
|
1006
|
+
"https://example.com/outbox",
|
|
1007
|
+
{
|
|
1008
|
+
...portableCollection("/actor/outbox"),
|
|
1009
|
+
id: compatibleOutbox
|
|
1010
|
+
}
|
|
1011
|
+
]) {
|
|
1012
|
+
const verifier = createRecordingVerifier();
|
|
1013
|
+
const collection = await (await person(outbox)).getOutbox({
|
|
1014
|
+
documentLoader,
|
|
789
1015
|
contextLoader: mockDocumentLoader,
|
|
790
|
-
verifyPortableObject:
|
|
1016
|
+
verifyPortableObject: verifier,
|
|
791
1017
|
crossOrigin
|
|
1018
|
+
});
|
|
1019
|
+
assertInstanceOf(collection, OrderedCollection);
|
|
1020
|
+
deepStrictEqual(collection.id, parseIri(`ap://${did}/actor/outbox`));
|
|
1021
|
+
deepStrictEqual(verifier.calls.length, 1);
|
|
1022
|
+
deepStrictEqual(verifier.calls[0].options.documentUrl, new URL(compatibleOutbox));
|
|
1023
|
+
deepStrictEqual(verifier.calls[0].options.gatewayHints, [new URL("https://gw.example")]);
|
|
1024
|
+
}
|
|
1025
|
+
await rejects(async () => await (await person(compatibleOutbox)).getOutbox({
|
|
1026
|
+
documentLoader,
|
|
1027
|
+
contextLoader: mockDocumentLoader
|
|
1028
|
+
}), TypeError);
|
|
1029
|
+
deepStrictEqual(await (await person(compatibleOutbox)).getOutbox({
|
|
1030
|
+
documentLoader,
|
|
1031
|
+
contextLoader: mockDocumentLoader,
|
|
1032
|
+
suppressError: true
|
|
1033
|
+
}), null);
|
|
1034
|
+
});
|
|
1035
|
+
test("accessors verify fetched documents that stand for portable objects", async (t) => {
|
|
1036
|
+
const plainUrl = "https://example.com/notes/1";
|
|
1037
|
+
await t.step("a redirect to a compatible identifier", async () => {
|
|
1038
|
+
const documentLoader = createRedirectingLoader({ [plainUrl]: {
|
|
1039
|
+
documentUrl: compatibleNoteId,
|
|
1040
|
+
document: note()
|
|
1041
|
+
} });
|
|
1042
|
+
const verifier = createRecordingVerifier();
|
|
1043
|
+
const object = await (await createHttpActivity(plainUrl)).getObject({
|
|
1044
|
+
documentLoader,
|
|
1045
|
+
contextLoader: mockDocumentLoader,
|
|
1046
|
+
verifyPortableObject: verifier
|
|
1047
|
+
});
|
|
1048
|
+
assertInstanceOf(object, Note);
|
|
1049
|
+
deepStrictEqual(object.id, parseIri(objectId));
|
|
1050
|
+
deepStrictEqual(documentLoader.fetched, [plainUrl]);
|
|
1051
|
+
deepStrictEqual(verifier.calls.length, 1);
|
|
1052
|
+
deepStrictEqual(verifier.calls[0].options.documentUrl, new URL(compatibleNoteId));
|
|
1053
|
+
deepStrictEqual(verifier.calls[0].options.gatewayHints, [new URL("https://gw.example")]);
|
|
1054
|
+
deepStrictEqual(await (await createHttpActivity(plainUrl)).getObject({
|
|
1055
|
+
documentLoader,
|
|
1056
|
+
contextLoader: mockDocumentLoader,
|
|
1057
|
+
verifyPortableObject: createVerifier(false)
|
|
1058
|
+
}), null);
|
|
1059
|
+
await rejects(async () => await (await createHttpActivity(plainUrl)).getObject({
|
|
1060
|
+
documentLoader,
|
|
1061
|
+
contextLoader: mockDocumentLoader,
|
|
1062
|
+
verifyPortableObject: createVerifier(false),
|
|
1063
|
+
crossOrigin: "throw"
|
|
1064
|
+
}), /No gateway returned a valid portable object/);
|
|
1065
|
+
});
|
|
1066
|
+
await t.step("a document that claims another object", async () => {
|
|
1067
|
+
for (const id of [
|
|
1068
|
+
`ap://${did}/objects/2`,
|
|
1069
|
+
compatibleNoteId,
|
|
1070
|
+
"https://gw.example/notes/1"
|
|
1071
|
+
]) {
|
|
1072
|
+
const verifier = createRecordingVerifier();
|
|
1073
|
+
deepStrictEqual(await (await createHttpActivity(plainUrl)).getObject({
|
|
1074
|
+
documentLoader: createRedirectingLoader({ [plainUrl]: {
|
|
1075
|
+
documentUrl: compatibleNoteId,
|
|
1076
|
+
document: note(id)
|
|
1077
|
+
} }),
|
|
1078
|
+
contextLoader: mockDocumentLoader,
|
|
1079
|
+
verifyPortableObject: verifier
|
|
1080
|
+
}), null);
|
|
1081
|
+
deepStrictEqual(verifier.calls, []);
|
|
1082
|
+
}
|
|
1083
|
+
});
|
|
1084
|
+
await t.step("a portable @id", async () => {
|
|
1085
|
+
const verifier = createRecordingVerifier();
|
|
1086
|
+
assertInstanceOf(await (await createHttpActivity(plainUrl)).getObject({
|
|
1087
|
+
documentLoader: createLoader({ [plainUrl]: note() }),
|
|
1088
|
+
contextLoader: mockDocumentLoader,
|
|
1089
|
+
verifyPortableObject: verifier,
|
|
1090
|
+
crossOrigin: "trust"
|
|
1091
|
+
}), Note);
|
|
1092
|
+
deepStrictEqual(verifier.calls.length, 1);
|
|
1093
|
+
deepStrictEqual(verifier.calls[0].options.documentUrl, new URL(plainUrl));
|
|
1094
|
+
deepStrictEqual(await (await createHttpActivity(plainUrl)).getObject({
|
|
1095
|
+
documentLoader: createLoader({ [plainUrl]: note() }),
|
|
1096
|
+
contextLoader: mockDocumentLoader,
|
|
1097
|
+
verifyPortableObject: createVerifier(false),
|
|
1098
|
+
crossOrigin: "trust"
|
|
1099
|
+
}), null);
|
|
1100
|
+
});
|
|
1101
|
+
await t.step("a compatible @id", async () => {
|
|
1102
|
+
const verifier = createRecordingVerifier();
|
|
1103
|
+
const url = "https://gw.example/notes/1";
|
|
1104
|
+
deepStrictEqual(await (await createHttpActivity(url)).getObject({
|
|
1105
|
+
documentLoader: createLoader({ [url]: note(compatibleNoteId) }),
|
|
1106
|
+
contextLoader: mockDocumentLoader,
|
|
1107
|
+
verifyPortableObject: verifier
|
|
1108
|
+
}), null);
|
|
1109
|
+
deepStrictEqual(verifier.calls, []);
|
|
1110
|
+
});
|
|
1111
|
+
await t.step("a portable final URL", async () => {
|
|
1112
|
+
for (const [id, expected] of [[objectId, true], [`ap://${did}/objects/2`, false]]) deepStrictEqual(await (await createHttpActivity(plainUrl)).getObject({
|
|
1113
|
+
documentLoader: createRedirectingLoader({ [plainUrl]: {
|
|
1114
|
+
documentUrl: `ap://${did}/objects/1`,
|
|
1115
|
+
document: note(id)
|
|
1116
|
+
} }),
|
|
1117
|
+
contextLoader: mockDocumentLoader,
|
|
1118
|
+
verifyPortableObject: createVerifier()
|
|
1119
|
+
}) instanceof Note, expected);
|
|
1120
|
+
deepStrictEqual(await (await createHttpActivity(plainUrl)).getObject({
|
|
1121
|
+
documentLoader: createRedirectingLoader({ [plainUrl]: {
|
|
1122
|
+
documentUrl: `ap://${did}/objects/x/../1`,
|
|
1123
|
+
document: note()
|
|
1124
|
+
} }),
|
|
1125
|
+
contextLoader: mockDocumentLoader,
|
|
1126
|
+
verifyPortableObject: createVerifier()
|
|
1127
|
+
}), null);
|
|
1128
|
+
});
|
|
1129
|
+
await t.step("a malformed compatible final URL", async () => {
|
|
1130
|
+
const malformed = `${compatibleNoteId}?@gateway=https%3A%2F%2Fgw2.example`;
|
|
1131
|
+
const documentLoader = createRedirectingLoader({ [plainUrl]: {
|
|
1132
|
+
documentUrl: malformed,
|
|
1133
|
+
document: note()
|
|
1134
|
+
} });
|
|
1135
|
+
deepStrictEqual(await (await createHttpActivity(plainUrl)).getObject({
|
|
1136
|
+
documentLoader,
|
|
1137
|
+
contextLoader: mockDocumentLoader,
|
|
1138
|
+
verifyPortableObject: createVerifier()
|
|
1139
|
+
}), null);
|
|
1140
|
+
await rejects(async () => await (await createHttpActivity(plainUrl)).getObject({
|
|
1141
|
+
documentLoader,
|
|
1142
|
+
contextLoader: mockDocumentLoader,
|
|
1143
|
+
verifyPortableObject: createVerifier(),
|
|
1144
|
+
crossOrigin: "throw"
|
|
1145
|
+
}), /malformed FEP-ef61 compatible identifier/);
|
|
1146
|
+
});
|
|
1147
|
+
await t.step("context documents are shared", async () => {
|
|
1148
|
+
const contextUrl = "https://example.com/portable-context";
|
|
1149
|
+
let version = 0;
|
|
1150
|
+
const contextLoader = async (url) => {
|
|
1151
|
+
if (url === contextUrl) {
|
|
1152
|
+
version++;
|
|
1153
|
+
return {
|
|
1154
|
+
contextUrl: null,
|
|
1155
|
+
documentUrl: url,
|
|
1156
|
+
document: { "@context": { version: `urn:version:${version}` } }
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1159
|
+
return await mockDocumentLoader(url);
|
|
792
1160
|
};
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
1161
|
+
const contexts = [];
|
|
1162
|
+
assertInstanceOf(await (await createHttpActivity(plainUrl)).getObject({
|
|
1163
|
+
documentLoader: createRedirectingLoader({ [plainUrl]: {
|
|
1164
|
+
documentUrl: compatibleNoteId,
|
|
1165
|
+
document: {
|
|
1166
|
+
...note(),
|
|
1167
|
+
"@context": ["https://www.w3.org/ns/activitystreams", contextUrl]
|
|
1168
|
+
}
|
|
1169
|
+
} }),
|
|
1170
|
+
contextLoader,
|
|
1171
|
+
verifyPortableObject: async (_, { contextLoader }) => {
|
|
1172
|
+
contexts.push((await contextLoader(contextUrl)).document);
|
|
1173
|
+
return { verified: true };
|
|
1174
|
+
}
|
|
1175
|
+
}), Note);
|
|
1176
|
+
deepStrictEqual(version, 1);
|
|
1177
|
+
deepStrictEqual(contexts, [{ "@context": { version: "urn:version:1" } }]);
|
|
1178
|
+
});
|
|
1179
|
+
await t.step("without verifyPortableObject", async () => {
|
|
1180
|
+
deepStrictEqual(await (await createHttpActivity(plainUrl)).getObject({
|
|
1181
|
+
documentLoader: createLoader({ [plainUrl]: note() }),
|
|
1182
|
+
contextLoader: mockDocumentLoader
|
|
1183
|
+
}), null);
|
|
1184
|
+
assertInstanceOf(await (await createHttpActivity(plainUrl)).getObject({
|
|
1185
|
+
documentLoader: createRedirectingLoader({ [plainUrl]: {
|
|
1186
|
+
documentUrl: gatewayUrl("https://example.com", "/objects/1"),
|
|
1187
|
+
document: note(gatewayUrl("https://example.com", "/objects/1"))
|
|
1188
|
+
} }),
|
|
1189
|
+
contextLoader: mockDocumentLoader
|
|
1190
|
+
}), Note);
|
|
1191
|
+
});
|
|
1192
|
+
});
|
|
1193
|
+
test("accessors verify embedded objects that stand for portable objects", async () => {
|
|
1194
|
+
const forgedId = gatewayUrl("https://example.com", "/objects/1");
|
|
1195
|
+
const forged = {
|
|
1196
|
+
type: "Note",
|
|
1197
|
+
id: forgedId,
|
|
1198
|
+
content: "Forged"
|
|
1199
|
+
};
|
|
1200
|
+
for (const crossOrigin of [void 0, "trust"]) {
|
|
1201
|
+
const documentLoader = createLoader({ [forgedId]: note() });
|
|
1202
|
+
const verifier = createRecordingVerifier(() => ({ verified: false }));
|
|
1203
|
+
deepStrictEqual(await (await createHttpActivity(forged)).getObject({
|
|
1204
|
+
documentLoader,
|
|
1205
|
+
contextLoader: mockDocumentLoader,
|
|
1206
|
+
verifyPortableObject: verifier,
|
|
1207
|
+
crossOrigin
|
|
1208
|
+
}), null);
|
|
1209
|
+
deepStrictEqual(documentLoader.fetched, [forgedId]);
|
|
1210
|
+
deepStrictEqual(verifier.calls.length, 1);
|
|
1211
|
+
deepStrictEqual(await Array.fromAsync((await createHttpActivity([forged])).getObjects({
|
|
1212
|
+
documentLoader,
|
|
1213
|
+
contextLoader: mockDocumentLoader,
|
|
1214
|
+
verifyPortableObject: verifier,
|
|
1215
|
+
crossOrigin
|
|
1216
|
+
})), []);
|
|
1217
|
+
const documentLoader2 = createLoader({ [gatewayUrl("https://gw.example", "/objects/1")]: note() });
|
|
1218
|
+
const verifier2 = createRecordingVerifier();
|
|
1219
|
+
const object = await (await createHttpActivity({
|
|
1220
|
+
type: "Note",
|
|
1221
|
+
id: objectId,
|
|
1222
|
+
content: "Forged"
|
|
1223
|
+
})).getObject({
|
|
1224
|
+
documentLoader: documentLoader2,
|
|
1225
|
+
contextLoader: mockDocumentLoader,
|
|
1226
|
+
gateways: ["https://gw.example"],
|
|
1227
|
+
verifyPortableObject: verifier2,
|
|
1228
|
+
crossOrigin
|
|
1229
|
+
});
|
|
1230
|
+
assertInstanceOf(object, Note);
|
|
1231
|
+
deepStrictEqual(object.content, "Portable note");
|
|
1232
|
+
deepStrictEqual(verifier2.calls.length, 1);
|
|
796
1233
|
}
|
|
797
|
-
|
|
798
|
-
|
|
1234
|
+
const otherDid = "did:key:z6Mkother";
|
|
1235
|
+
const otherId = `ap://${otherDid}/objects/1`;
|
|
1236
|
+
const documentLoader = createLoader({ [`https://gw.example/.well-known/apgateway/${otherDid}/objects/1`]: note(otherId) });
|
|
1237
|
+
const verifier = createRecordingVerifier();
|
|
1238
|
+
const crossDid = await (await createActivity({
|
|
1239
|
+
type: "Note",
|
|
1240
|
+
id: otherId,
|
|
1241
|
+
content: "Forged"
|
|
1242
|
+
})).getObject({
|
|
1243
|
+
documentLoader,
|
|
799
1244
|
contextLoader: mockDocumentLoader,
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
})
|
|
1245
|
+
gateways: ["https://gw.example"],
|
|
1246
|
+
verifyPortableObject: verifier,
|
|
1247
|
+
crossOrigin: "trust"
|
|
1248
|
+
});
|
|
1249
|
+
assertInstanceOf(crossDid, Note);
|
|
1250
|
+
deepStrictEqual(crossDid.content, "Portable note");
|
|
1251
|
+
deepStrictEqual(verifier.calls.length, 1);
|
|
1252
|
+
const object = await (await createActivity({
|
|
1253
|
+
type: "Note",
|
|
1254
|
+
id: objectId,
|
|
1255
|
+
content: "Embedded"
|
|
1256
|
+
})).getObject({
|
|
1257
|
+
documentLoader: createLoader({}),
|
|
1258
|
+
contextLoader: mockDocumentLoader,
|
|
1259
|
+
verifyPortableObject: createVerifier(false)
|
|
1260
|
+
});
|
|
1261
|
+
assertInstanceOf(object, Note);
|
|
1262
|
+
deepStrictEqual(object.content, "Embedded");
|
|
812
1263
|
});
|
|
813
1264
|
test("singular accessors drop anonymous objects embedded in unsecured collections", async () => {
|
|
814
1265
|
const options = {
|