@fedify/vocab 2.4.0-dev.1583 → 2.4.0-dev.1618
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 +3 -2
- package/dist/mod.cjs +3617 -11
- package/dist/mod.d.cts +10 -0
- package/dist/mod.d.ts +10 -0
- package/dist/mod.js +50 -4
- package/dist-tests/{actor-Br5HgxUd.mjs → actor-COnJgcOw.mjs} +4 -4
- package/dist-tests/actor.test.mjs +3 -3
- package/dist-tests/cjs.test.mjs +28 -0
- package/dist-tests/endpoints.yaml +14 -0
- package/dist-tests/lookup.test.mjs +74 -7
- package/dist-tests/{type-Cf-vxmre.mjs → type-_BlsfT3S.mjs} +1 -1
- package/dist-tests/type.test.mjs +3 -3
- package/dist-tests/{utils-CE8Dk5hm.mjs → utils-CQjrpUkt.mjs} +1 -1
- package/dist-tests/{vocab-DOHLQ4UQ.mjs → vocab-OU5GYbNg.mjs} +49 -2
- package/dist-tests/vocab.test.mjs +83 -4
- package/package.json +5 -5
- package/src/__snapshots__/vocab.test.ts.snap +6 -3
- package/src/cjs.test.ts +31 -0
- package/src/endpoints.yaml +14 -0
- package/src/lookup.test.ts +121 -0
- package/src/lookup.ts +3 -1
- package/src/vocab.test.ts +128 -0
- package/tsdown.config.ts +22 -12
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { Temporal } from "
|
|
1
|
+
import { Temporal } from "temporal-polyfill";
|
|
2
2
|
globalThis.addEventListener = () => {};
|
|
3
|
-
import {
|
|
4
|
-
import { t as assertInstanceOf } from "./utils-
|
|
3
|
+
import { B as Tombstone, C as Link, D as Object$1, E as Note, F as QuoteAuthorization, I as QuoteRequest, L as Reject, M as Place, N as Proposal, O as Offer, P as Question, S as InteractionRule, T as Multikey, U as vocab_exports, _ as Follow, b as Intent, c as Create, d as Document, f as Endpoints, g as FeaturedItem, h as FeaturedCollection, i as Announce, j as Person, k as OrderedCollectionPage, l as CryptographicKey, m as FeatureRequest, n as Activity, o as Collection, p as FeatureAuthorization, r as Agreement, s as Commitment, t as Accept, u as Delete, w as Measure, x as InteractionPolicy, y as Hashtag, z as Source } from "./vocab-OU5GYbNg.mjs";
|
|
4
|
+
import { t as assertInstanceOf } from "./utils-CQjrpUkt.mjs";
|
|
5
5
|
import { mockDocumentLoader, test } from "@fedify/fixture";
|
|
6
6
|
import { deepStrictEqual, notDeepStrictEqual, ok, rejects, throws } from "node:assert/strict";
|
|
7
7
|
import { configure, reset } from "@logtape/logtape";
|
|
@@ -1311,6 +1311,25 @@ test("Endpoints.toJsonLd() omits type", async () => {
|
|
|
1311
1311
|
contextLoader: mockDocumentLoader
|
|
1312
1312
|
}), ep);
|
|
1313
1313
|
});
|
|
1314
|
+
test("Endpoints.uploadMedia round-trips", async () => {
|
|
1315
|
+
const ep = new Endpoints({ uploadMedia: new URL("https://example.com/users/alice/media") });
|
|
1316
|
+
deepStrictEqual(ep.uploadMedia?.href, "https://example.com/users/alice/media");
|
|
1317
|
+
deepStrictEqual((await ep.toJsonLd())["uploadMedia"], "https://example.com/users/alice/media");
|
|
1318
|
+
for (const format of [
|
|
1319
|
+
void 0,
|
|
1320
|
+
"compact",
|
|
1321
|
+
"expand"
|
|
1322
|
+
]) {
|
|
1323
|
+
const jsonLd = await ep.toJsonLd({
|
|
1324
|
+
format,
|
|
1325
|
+
contextLoader: mockDocumentLoader
|
|
1326
|
+
});
|
|
1327
|
+
deepStrictEqual((await Endpoints.fromJsonLd(jsonLd, {
|
|
1328
|
+
documentLoader: mockDocumentLoader,
|
|
1329
|
+
contextLoader: mockDocumentLoader
|
|
1330
|
+
})).uploadMedia?.href, "https://example.com/users/alice/media", `round-trip failed for format=${format ?? "heuristic"}`);
|
|
1331
|
+
}
|
|
1332
|
+
});
|
|
1314
1333
|
test("Source.toJsonLd() omits type", async () => {
|
|
1315
1334
|
const src = new Source({
|
|
1316
1335
|
content: "Hello, world!",
|
|
@@ -1361,7 +1380,8 @@ test("Endpoints with all properties set omits type", async () => {
|
|
|
1361
1380
|
oauthTokenEndpoint: new URL("https://example.com/oauth/token"),
|
|
1362
1381
|
provideClientKey: new URL("https://example.com/provide-key"),
|
|
1363
1382
|
signClientKey: new URL("https://example.com/sign-key"),
|
|
1364
|
-
sharedInbox: new URL("https://example.com/inbox")
|
|
1383
|
+
sharedInbox: new URL("https://example.com/inbox"),
|
|
1384
|
+
uploadMedia: new URL("https://example.com/upload-media")
|
|
1365
1385
|
});
|
|
1366
1386
|
const compact = await ep.toJsonLd();
|
|
1367
1387
|
ok(!("type" in compact), "compact output should not have 'type'");
|
|
@@ -1371,6 +1391,7 @@ test("Endpoints with all properties set omits type", async () => {
|
|
|
1371
1391
|
deepStrictEqual(compact["provideClientKey"], "https://example.com/provide-key");
|
|
1372
1392
|
deepStrictEqual(compact["signClientKey"], "https://example.com/sign-key");
|
|
1373
1393
|
deepStrictEqual(compact["sharedInbox"], "https://example.com/inbox");
|
|
1394
|
+
deepStrictEqual(compact["uploadMedia"], "https://example.com/upload-media");
|
|
1374
1395
|
for (const format of [
|
|
1375
1396
|
void 0,
|
|
1376
1397
|
"compact",
|
|
@@ -2555,6 +2576,23 @@ test("FEP-fe34: Same origin objects are trusted", async () => {
|
|
|
2555
2576
|
deepStrictEqual(result?.id, new URL("https://example.com/note"));
|
|
2556
2577
|
deepStrictEqual(result?.content, "This is a legitimate note");
|
|
2557
2578
|
});
|
|
2579
|
+
test("FEP-fe34: Same-authority non-FE34 embedded objects are trusted", async () => {
|
|
2580
|
+
const result = await (await Create.fromJsonLd({
|
|
2581
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
2582
|
+
"@type": "Create",
|
|
2583
|
+
"@id": "at://did:plc:example/collection/item",
|
|
2584
|
+
"actor": "at://did:plc:example/actor/self",
|
|
2585
|
+
"object": {
|
|
2586
|
+
"@type": "Note",
|
|
2587
|
+
"@id": "at://did:plc:example/collection/reply",
|
|
2588
|
+
"content": "Embedded AT Protocol note"
|
|
2589
|
+
}
|
|
2590
|
+
})).getObject({ documentLoader: async (url) => {
|
|
2591
|
+
throw new Error(`Unexpected fetch: ${url}`);
|
|
2592
|
+
} });
|
|
2593
|
+
assertInstanceOf(result, Note);
|
|
2594
|
+
deepStrictEqual(result.content, "Embedded AT Protocol note");
|
|
2595
|
+
});
|
|
2558
2596
|
test("FEP-fe34: Embedded cross-origin objects from JSON-LD are ignored by default", async () => {
|
|
2559
2597
|
const createDocumentLoader = async (url) => {
|
|
2560
2598
|
if (url === "https://example.com/create") return {
|
|
@@ -2656,6 +2694,47 @@ test("FEP-fe34: Portable DID authorities are cross-origin boundaries", async ()
|
|
|
2656
2694
|
assertInstanceOf(result, Note);
|
|
2657
2695
|
deepStrictEqual(result.content, "Fetched portable note");
|
|
2658
2696
|
});
|
|
2697
|
+
test("FEP-fe34: DID verification methods share portable actor cryptographic origin", async () => {
|
|
2698
|
+
const person = await Person.fromJsonLd({
|
|
2699
|
+
"@id": "ap://did:key:z6MkOwner/actor",
|
|
2700
|
+
"@type": ["https://www.w3.org/ns/activitystreams#Person"],
|
|
2701
|
+
"https://w3id.org/security#assertionMethod": [{
|
|
2702
|
+
"@id": "did:key:z6MkOwner#z6MkOwner",
|
|
2703
|
+
"@type": ["https://w3id.org/security#Multikey"],
|
|
2704
|
+
"https://w3id.org/security#controller": [{ "@id": "did:key:z6MkOwner" }]
|
|
2705
|
+
}]
|
|
2706
|
+
});
|
|
2707
|
+
const documentLoader = async (url) => {
|
|
2708
|
+
throw new Error(`Unexpected fetch: ${url}`);
|
|
2709
|
+
};
|
|
2710
|
+
const methods = [];
|
|
2711
|
+
for await (const method of person.getAssertionMethods({ documentLoader })) methods.push(method);
|
|
2712
|
+
deepStrictEqual(methods.length, 1);
|
|
2713
|
+
assertInstanceOf(methods[0], Multikey);
|
|
2714
|
+
deepStrictEqual(methods[0].id, new URL("did:key:z6MkOwner#z6MkOwner"));
|
|
2715
|
+
});
|
|
2716
|
+
test("FEP-fe34: DID verification methods from another cryptographic origin are untrusted", async () => {
|
|
2717
|
+
const person = await Person.fromJsonLd({
|
|
2718
|
+
"@id": "ap://did:key:z6MkOwner/actor",
|
|
2719
|
+
"@type": ["https://www.w3.org/ns/activitystreams#Person"],
|
|
2720
|
+
"https://w3id.org/security#assertionMethod": [{
|
|
2721
|
+
"@id": "did:key:z6MkOther#z6MkOther",
|
|
2722
|
+
"@type": ["https://w3id.org/security#Multikey"],
|
|
2723
|
+
"https://w3id.org/security#controller": [{ "@id": "did:key:z6MkOther" }]
|
|
2724
|
+
}]
|
|
2725
|
+
});
|
|
2726
|
+
let fetches = 0;
|
|
2727
|
+
const methods = [];
|
|
2728
|
+
for await (const method of person.getAssertionMethods({
|
|
2729
|
+
suppressError: true,
|
|
2730
|
+
documentLoader: async (url) => {
|
|
2731
|
+
fetches++;
|
|
2732
|
+
throw new Error(`Unexpected fetch: ${url}`);
|
|
2733
|
+
}
|
|
2734
|
+
})) methods.push(method);
|
|
2735
|
+
deepStrictEqual(methods, []);
|
|
2736
|
+
deepStrictEqual(fetches, 1);
|
|
2737
|
+
});
|
|
2659
2738
|
test("FEP-fe34: Array properties respect cross-origin policy", async () => {
|
|
2660
2739
|
const crossOriginDocumentLoader = async (url) => {
|
|
2661
2740
|
if (url === "https://different-origin.com/note1") return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fedify/vocab",
|
|
3
|
-
"version": "2.4.0-dev.
|
|
3
|
+
"version": "2.4.0-dev.1618+58858243",
|
|
4
4
|
"homepage": "https://fedify.dev/",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -35,7 +35,6 @@
|
|
|
35
35
|
"./package.json": "./package.json"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@js-temporal/polyfill": "^0.5.1",
|
|
39
38
|
"@multiformats/base-x": "^4.0.1",
|
|
40
39
|
"@logtape/logtape": "^2.2.0",
|
|
41
40
|
"@opentelemetry/api": "^1.9.1",
|
|
@@ -43,9 +42,10 @@
|
|
|
43
42
|
"es-toolkit": "1.46.1",
|
|
44
43
|
"jsonld": "^9.0.0",
|
|
45
44
|
"pkijs": "^3.3.3",
|
|
46
|
-
"
|
|
47
|
-
"@fedify/vocab-
|
|
48
|
-
"@fedify/webfinger": "2.4.0-dev.
|
|
45
|
+
"temporal-polyfill": "^1.0.1",
|
|
46
|
+
"@fedify/vocab-tools": "2.4.0-dev.1618+58858243",
|
|
47
|
+
"@fedify/webfinger": "2.4.0-dev.1618+58858243",
|
|
48
|
+
"@fedify/vocab-runtime": "2.4.0-dev.1618+58858243"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@types/node": "^22.17.0",
|
|
@@ -5003,7 +5003,8 @@ snapshot[`Deno.inspect(Endpoints) [auto] 1`] = `
|
|
|
5003
5003
|
oauthTokenEndpoint: URL "https://example.com/",
|
|
5004
5004
|
provideClientKey: URL "https://example.com/",
|
|
5005
5005
|
signClientKey: URL "https://example.com/",
|
|
5006
|
-
sharedInbox: URL "https://example.com/"
|
|
5006
|
+
sharedInbox: URL "https://example.com/",
|
|
5007
|
+
uploadMedia: URL "https://example.com/"
|
|
5007
5008
|
}'
|
|
5008
5009
|
`;
|
|
5009
5010
|
|
|
@@ -5015,7 +5016,8 @@ snapshot[`Deno.inspect(Endpoints) [auto] 2`] = `
|
|
|
5015
5016
|
oauthTokenEndpoint: URL "https://example.com/",
|
|
5016
5017
|
provideClientKey: URL "https://example.com/",
|
|
5017
5018
|
signClientKey: URL "https://example.com/",
|
|
5018
|
-
sharedInbox: URL "https://example.com/"
|
|
5019
|
+
sharedInbox: URL "https://example.com/",
|
|
5020
|
+
uploadMedia: URL "https://example.com/"
|
|
5019
5021
|
}'
|
|
5020
5022
|
`;
|
|
5021
5023
|
|
|
@@ -5027,7 +5029,8 @@ snapshot[`Deno.inspect(Endpoints) [auto] 3`] = `
|
|
|
5027
5029
|
oauthTokenEndpoint: URL "https://example.com/",
|
|
5028
5030
|
provideClientKey: URL "https://example.com/",
|
|
5029
5031
|
signClientKey: URL "https://example.com/",
|
|
5030
|
-
sharedInbox: URL "https://example.com/"
|
|
5032
|
+
sharedInbox: URL "https://example.com/",
|
|
5033
|
+
uploadMedia: URL "https://example.com/"
|
|
5031
5034
|
}'
|
|
5032
5035
|
`;
|
|
5033
5036
|
|
package/src/cjs.test.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { ok, strictEqual, throws } from "node:assert";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { mockDocumentLoader, test } from "@fedify/fixture";
|
|
4
|
+
|
|
5
|
+
test("CommonJS entry bundles temporal-polyfill internally", {
|
|
6
|
+
ignore: "Deno" in globalThis,
|
|
7
|
+
}, async () => {
|
|
8
|
+
const originalTemporal = globalThis.Temporal;
|
|
9
|
+
|
|
10
|
+
const require = createRequire(import.meta.url);
|
|
11
|
+
throws(
|
|
12
|
+
() => require("temporal-polyfill"),
|
|
13
|
+
(error: unknown) => {
|
|
14
|
+
if (error == null || typeof error !== "object") return false;
|
|
15
|
+
const { code } = error as { code?: unknown };
|
|
16
|
+
return code === "ERR_PACKAGE_PATH_NOT_EXPORTED" ||
|
|
17
|
+
code === "MODULE_NOT_FOUND";
|
|
18
|
+
},
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
const vocab = require("../dist/mod.cjs");
|
|
22
|
+
const obj = await vocab.Object.fromJsonLd({
|
|
23
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
24
|
+
type: "Object",
|
|
25
|
+
published: "2025-01-01T12:34:56Z",
|
|
26
|
+
}, { documentLoader: mockDocumentLoader, contextLoader: mockDocumentLoader });
|
|
27
|
+
|
|
28
|
+
ok(obj.published != null);
|
|
29
|
+
strictEqual(obj.published.toString(), "2025-01-01T12:34:56Z");
|
|
30
|
+
strictEqual(globalThis.Temporal, originalTemporal);
|
|
31
|
+
});
|
package/src/endpoints.yaml
CHANGED
|
@@ -84,3 +84,17 @@ properties:
|
|
|
84
84
|
objects which are not addressed to the `Public`` endpoint.
|
|
85
85
|
range:
|
|
86
86
|
- "http://www.w3.org/2001/XMLSchema#anyURI"
|
|
87
|
+
|
|
88
|
+
- singularName: uploadMedia
|
|
89
|
+
functional: true
|
|
90
|
+
compactName: uploadMedia
|
|
91
|
+
uri: "https://www.w3.org/ns/activitystreams#uploadMedia"
|
|
92
|
+
description: |
|
|
93
|
+
Upload endpoint URI for this user for binary data, such as an image or
|
|
94
|
+
video, as described in the [ActivityPub Media
|
|
95
|
+
Upload](https://www.w3.org/wiki/SocialCG/ActivityPub/MediaUpload) extension.
|
|
96
|
+
Clients `POST` a `multipart/form-data` request containing a `file` part
|
|
97
|
+
(the binary payload) and an `object` part (an ActivityStreams object shell)
|
|
98
|
+
to this endpoint.
|
|
99
|
+
range:
|
|
100
|
+
- "http://www.w3.org/2001/XMLSchema#anyURI"
|
package/src/lookup.test.ts
CHANGED
|
@@ -416,6 +416,127 @@ test("FEP-fe34: lookupObject() cross-origin security", {
|
|
|
416
416
|
);
|
|
417
417
|
});
|
|
418
418
|
|
|
419
|
+
await t.step(
|
|
420
|
+
"same-authority non-FE34 IRIs are still trusted",
|
|
421
|
+
async () => {
|
|
422
|
+
// deno-lint-ignore require-await
|
|
423
|
+
const atProtoDocumentLoader = async (url: string) => {
|
|
424
|
+
if (url === "https://gateway.example/at-item") {
|
|
425
|
+
return {
|
|
426
|
+
documentUrl: "at://did%3Aplc%3Aexample/collection/item",
|
|
427
|
+
contextUrl: null,
|
|
428
|
+
document: {
|
|
429
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
430
|
+
type: "Note",
|
|
431
|
+
id: "at://did:plc:example/collection/reply",
|
|
432
|
+
content: "AT Protocol note",
|
|
433
|
+
},
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
throw new Error(`Unexpected URL: ${url}`);
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
const result = await lookupObject(
|
|
440
|
+
"https://gateway.example/at-item",
|
|
441
|
+
{
|
|
442
|
+
documentLoader: atProtoDocumentLoader,
|
|
443
|
+
contextLoader: mockDocumentLoader,
|
|
444
|
+
},
|
|
445
|
+
);
|
|
446
|
+
|
|
447
|
+
assertInstanceOf(result, Note);
|
|
448
|
+
deepStrictEqual(
|
|
449
|
+
result.id,
|
|
450
|
+
new URL("at://did%3Aplc%3Aexample/collection/reply"),
|
|
451
|
+
);
|
|
452
|
+
deepStrictEqual(result.content, "AT Protocol note");
|
|
453
|
+
},
|
|
454
|
+
);
|
|
455
|
+
|
|
456
|
+
await t.step(
|
|
457
|
+
"portable object IDs are trusted by DID cryptographic origin",
|
|
458
|
+
async () => {
|
|
459
|
+
// deno-lint-ignore require-await
|
|
460
|
+
const portableDocumentLoader = async (url: string) => {
|
|
461
|
+
if (
|
|
462
|
+
url ===
|
|
463
|
+
"https://gateway.example/.well-known/apgateway/did:key:z6Mkabc/note"
|
|
464
|
+
) {
|
|
465
|
+
return {
|
|
466
|
+
documentUrl: "ap+ef61://did:key:z6Mkabc/note",
|
|
467
|
+
contextUrl: null,
|
|
468
|
+
document: {
|
|
469
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
470
|
+
type: "Note",
|
|
471
|
+
id: "ap://did:key:z6Mkabc/note",
|
|
472
|
+
content: "Portable note",
|
|
473
|
+
},
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
throw new Error(`Unexpected URL: ${url}`);
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
const result = await lookupObject(
|
|
480
|
+
"https://gateway.example/.well-known/apgateway/did:key:z6Mkabc/note",
|
|
481
|
+
{
|
|
482
|
+
documentLoader: portableDocumentLoader,
|
|
483
|
+
contextLoader: mockDocumentLoader,
|
|
484
|
+
},
|
|
485
|
+
);
|
|
486
|
+
|
|
487
|
+
assertInstanceOf(result, Note);
|
|
488
|
+
deepStrictEqual(result.content, "Portable note");
|
|
489
|
+
},
|
|
490
|
+
);
|
|
491
|
+
|
|
492
|
+
await t.step(
|
|
493
|
+
"portable object IDs reject mismatched cryptographic origins",
|
|
494
|
+
async () => {
|
|
495
|
+
// deno-lint-ignore require-await
|
|
496
|
+
const portableDocumentLoader = async (url: string) => {
|
|
497
|
+
if (
|
|
498
|
+
url ===
|
|
499
|
+
"https://gateway.example/.well-known/apgateway/did:key:z6Mkabc/note"
|
|
500
|
+
) {
|
|
501
|
+
return {
|
|
502
|
+
documentUrl: "ap+ef61://did:key:z6Mkabc/note",
|
|
503
|
+
contextUrl: null,
|
|
504
|
+
document: {
|
|
505
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
506
|
+
type: "Note",
|
|
507
|
+
id: "ap://did:key:z6Mkdef/note",
|
|
508
|
+
content: "Spoofed portable note",
|
|
509
|
+
},
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
throw new Error(`Unexpected URL: ${url}`);
|
|
513
|
+
};
|
|
514
|
+
|
|
515
|
+
const result = await lookupObject(
|
|
516
|
+
"https://gateway.example/.well-known/apgateway/did:key:z6Mkabc/note",
|
|
517
|
+
{
|
|
518
|
+
documentLoader: portableDocumentLoader,
|
|
519
|
+
contextLoader: mockDocumentLoader,
|
|
520
|
+
},
|
|
521
|
+
);
|
|
522
|
+
|
|
523
|
+
deepStrictEqual(result, null);
|
|
524
|
+
await rejects(
|
|
525
|
+
() =>
|
|
526
|
+
lookupObject(
|
|
527
|
+
"https://gateway.example/.well-known/apgateway/did:key:z6Mkabc/note",
|
|
528
|
+
{
|
|
529
|
+
documentLoader: portableDocumentLoader,
|
|
530
|
+
contextLoader: mockDocumentLoader,
|
|
531
|
+
crossOrigin: "throw",
|
|
532
|
+
},
|
|
533
|
+
),
|
|
534
|
+
Error,
|
|
535
|
+
"The object's @id (ap+ef61://did%3Akey%3Az6Mkdef/note) has a different origin than the document URL (ap+ef61://did:key:z6Mkabc/note)",
|
|
536
|
+
);
|
|
537
|
+
},
|
|
538
|
+
);
|
|
539
|
+
|
|
419
540
|
await t.step("objects without @id are trusted", async () => {
|
|
420
541
|
// deno-lint-ignore require-await
|
|
421
542
|
const noIdDocumentLoader = async (url: string) => {
|
package/src/lookup.ts
CHANGED
|
@@ -2,6 +2,7 @@ import type { GetUserAgentOptions } from "@fedify/vocab-runtime";
|
|
|
2
2
|
import {
|
|
3
3
|
type DocumentLoader,
|
|
4
4
|
getDocumentLoader,
|
|
5
|
+
haveSameFe34Origin,
|
|
5
6
|
haveSameIriOrigin,
|
|
6
7
|
parseIri,
|
|
7
8
|
type RemoteDocument,
|
|
@@ -335,7 +336,8 @@ async function lookupObjectInternal(
|
|
|
335
336
|
}
|
|
336
337
|
if (
|
|
337
338
|
options.crossOrigin !== "trust" && object.id != null &&
|
|
338
|
-
!haveSameIriOrigin(object.id, documentUrl)
|
|
339
|
+
!haveSameIriOrigin(object.id, documentUrl) &&
|
|
340
|
+
!haveSameFe34Origin(object.id, documentUrl)
|
|
339
341
|
) {
|
|
340
342
|
if (options.crossOrigin === "throw") {
|
|
341
343
|
throw new Error(
|
package/src/vocab.test.ts
CHANGED
|
@@ -47,6 +47,7 @@ import {
|
|
|
47
47
|
InteractionRule,
|
|
48
48
|
Link,
|
|
49
49
|
Measure,
|
|
50
|
+
Multikey,
|
|
50
51
|
Note,
|
|
51
52
|
Object,
|
|
52
53
|
Offer,
|
|
@@ -1963,6 +1964,39 @@ test("Endpoints.toJsonLd() omits type", async () => {
|
|
|
1963
1964
|
deepStrictEqual(restored, ep);
|
|
1964
1965
|
});
|
|
1965
1966
|
|
|
1967
|
+
test("Endpoints.uploadMedia round-trips", async () => {
|
|
1968
|
+
const ep = new Endpoints({
|
|
1969
|
+
uploadMedia: new URL("https://example.com/users/alice/media"),
|
|
1970
|
+
});
|
|
1971
|
+
deepStrictEqual(
|
|
1972
|
+
ep.uploadMedia?.href,
|
|
1973
|
+
"https://example.com/users/alice/media",
|
|
1974
|
+
);
|
|
1975
|
+
|
|
1976
|
+
const compact = await ep.toJsonLd() as Record<string, unknown>;
|
|
1977
|
+
deepStrictEqual(
|
|
1978
|
+
compact["uploadMedia"],
|
|
1979
|
+
"https://example.com/users/alice/media",
|
|
1980
|
+
);
|
|
1981
|
+
|
|
1982
|
+
// Round-trip through every format under the standard AS term.
|
|
1983
|
+
for (const format of [undefined, "compact" as const, "expand" as const]) {
|
|
1984
|
+
const jsonLd = await ep.toJsonLd({
|
|
1985
|
+
format,
|
|
1986
|
+
contextLoader: mockDocumentLoader,
|
|
1987
|
+
});
|
|
1988
|
+
const restored = await Endpoints.fromJsonLd(jsonLd, {
|
|
1989
|
+
documentLoader: mockDocumentLoader,
|
|
1990
|
+
contextLoader: mockDocumentLoader,
|
|
1991
|
+
});
|
|
1992
|
+
deepStrictEqual(
|
|
1993
|
+
restored.uploadMedia?.href,
|
|
1994
|
+
"https://example.com/users/alice/media",
|
|
1995
|
+
`round-trip failed for format=${format ?? "heuristic"}`,
|
|
1996
|
+
);
|
|
1997
|
+
}
|
|
1998
|
+
});
|
|
1999
|
+
|
|
1966
2000
|
test("Source.toJsonLd() omits type", async () => {
|
|
1967
2001
|
const src = new Source({
|
|
1968
2002
|
content: "Hello, world!",
|
|
@@ -2029,6 +2063,7 @@ test("Endpoints with all properties set omits type", async () => {
|
|
|
2029
2063
|
provideClientKey: new URL("https://example.com/provide-key"),
|
|
2030
2064
|
signClientKey: new URL("https://example.com/sign-key"),
|
|
2031
2065
|
sharedInbox: new URL("https://example.com/inbox"),
|
|
2066
|
+
uploadMedia: new URL("https://example.com/upload-media"),
|
|
2032
2067
|
});
|
|
2033
2068
|
|
|
2034
2069
|
// Compact heuristic path
|
|
@@ -2049,6 +2084,7 @@ test("Endpoints with all properties set omits type", async () => {
|
|
|
2049
2084
|
);
|
|
2050
2085
|
deepStrictEqual(compact["signClientKey"], "https://example.com/sign-key");
|
|
2051
2086
|
deepStrictEqual(compact["sharedInbox"], "https://example.com/inbox");
|
|
2087
|
+
deepStrictEqual(compact["uploadMedia"], "https://example.com/upload-media");
|
|
2052
2088
|
|
|
2053
2089
|
// Round-trip all three formats
|
|
2054
2090
|
for (
|
|
@@ -3800,6 +3836,30 @@ test("FEP-fe34: Same origin objects are trusted", async () => {
|
|
|
3800
3836
|
deepStrictEqual(result?.content, "This is a legitimate note");
|
|
3801
3837
|
});
|
|
3802
3838
|
|
|
3839
|
+
test("FEP-fe34: Same-authority non-FE34 embedded objects are trusted", async () => {
|
|
3840
|
+
const create = await Create.fromJsonLd({
|
|
3841
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
3842
|
+
"@type": "Create",
|
|
3843
|
+
"@id": "at://did:plc:example/collection/item",
|
|
3844
|
+
"actor": "at://did:plc:example/actor/self",
|
|
3845
|
+
"object": {
|
|
3846
|
+
"@type": "Note",
|
|
3847
|
+
"@id": "at://did:plc:example/collection/reply",
|
|
3848
|
+
"content": "Embedded AT Protocol note",
|
|
3849
|
+
},
|
|
3850
|
+
});
|
|
3851
|
+
|
|
3852
|
+
const result = await create.getObject({
|
|
3853
|
+
// deno-lint-ignore require-await
|
|
3854
|
+
documentLoader: async (url) => {
|
|
3855
|
+
throw new Error(`Unexpected fetch: ${url}`);
|
|
3856
|
+
},
|
|
3857
|
+
});
|
|
3858
|
+
|
|
3859
|
+
assertInstanceOf(result, Note);
|
|
3860
|
+
deepStrictEqual(result.content, "Embedded AT Protocol note");
|
|
3861
|
+
});
|
|
3862
|
+
|
|
3803
3863
|
test(
|
|
3804
3864
|
"FEP-fe34: Embedded cross-origin objects from JSON-LD are ignored by default",
|
|
3805
3865
|
async () => {
|
|
@@ -3951,6 +4011,74 @@ test(
|
|
|
3951
4011
|
},
|
|
3952
4012
|
);
|
|
3953
4013
|
|
|
4014
|
+
test(
|
|
4015
|
+
"FEP-fe34: DID verification methods share portable actor cryptographic origin",
|
|
4016
|
+
async () => {
|
|
4017
|
+
const person = await Person.fromJsonLd({
|
|
4018
|
+
"@id": "ap://did:key:z6MkOwner/actor",
|
|
4019
|
+
"@type": ["https://www.w3.org/ns/activitystreams#Person"],
|
|
4020
|
+
"https://w3id.org/security#assertionMethod": [{
|
|
4021
|
+
"@id": "did:key:z6MkOwner#z6MkOwner",
|
|
4022
|
+
"@type": ["https://w3id.org/security#Multikey"],
|
|
4023
|
+
"https://w3id.org/security#controller": [{
|
|
4024
|
+
"@id": "did:key:z6MkOwner",
|
|
4025
|
+
}],
|
|
4026
|
+
}],
|
|
4027
|
+
});
|
|
4028
|
+
|
|
4029
|
+
// deno-lint-ignore require-await
|
|
4030
|
+
const documentLoader = async (url: string) => {
|
|
4031
|
+
throw new Error(`Unexpected fetch: ${url}`);
|
|
4032
|
+
};
|
|
4033
|
+
|
|
4034
|
+
const methods = [];
|
|
4035
|
+
for await (const method of person.getAssertionMethods({ documentLoader })) {
|
|
4036
|
+
methods.push(method);
|
|
4037
|
+
}
|
|
4038
|
+
|
|
4039
|
+
deepStrictEqual(methods.length, 1);
|
|
4040
|
+
assertInstanceOf(methods[0], Multikey);
|
|
4041
|
+
deepStrictEqual(
|
|
4042
|
+
methods[0].id,
|
|
4043
|
+
new URL("did:key:z6MkOwner#z6MkOwner"),
|
|
4044
|
+
);
|
|
4045
|
+
},
|
|
4046
|
+
);
|
|
4047
|
+
|
|
4048
|
+
test(
|
|
4049
|
+
"FEP-fe34: DID verification methods from another cryptographic origin are untrusted",
|
|
4050
|
+
async () => {
|
|
4051
|
+
const person = await Person.fromJsonLd({
|
|
4052
|
+
"@id": "ap://did:key:z6MkOwner/actor",
|
|
4053
|
+
"@type": ["https://www.w3.org/ns/activitystreams#Person"],
|
|
4054
|
+
"https://w3id.org/security#assertionMethod": [{
|
|
4055
|
+
"@id": "did:key:z6MkOther#z6MkOther",
|
|
4056
|
+
"@type": ["https://w3id.org/security#Multikey"],
|
|
4057
|
+
"https://w3id.org/security#controller": [{
|
|
4058
|
+
"@id": "did:key:z6MkOther",
|
|
4059
|
+
}],
|
|
4060
|
+
}],
|
|
4061
|
+
});
|
|
4062
|
+
|
|
4063
|
+
let fetches = 0;
|
|
4064
|
+
const methods = [];
|
|
4065
|
+
for await (
|
|
4066
|
+
const method of person.getAssertionMethods({
|
|
4067
|
+
suppressError: true,
|
|
4068
|
+
// deno-lint-ignore require-await
|
|
4069
|
+
documentLoader: async (url) => {
|
|
4070
|
+
fetches++;
|
|
4071
|
+
throw new Error(`Unexpected fetch: ${url}`);
|
|
4072
|
+
},
|
|
4073
|
+
})
|
|
4074
|
+
) {
|
|
4075
|
+
methods.push(method);
|
|
4076
|
+
}
|
|
4077
|
+
deepStrictEqual(methods, []);
|
|
4078
|
+
deepStrictEqual(fetches, 1);
|
|
4079
|
+
},
|
|
4080
|
+
);
|
|
4081
|
+
|
|
3954
4082
|
test("FEP-fe34: Array properties respect cross-origin policy", async () => {
|
|
3955
4083
|
// deno-lint-ignore require-await
|
|
3956
4084
|
const crossOriginDocumentLoader = async (url: string) => {
|
package/tsdown.config.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { glob, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
2
|
import { dirname, join, sep } from "node:path";
|
|
3
3
|
import { defineConfig } from "tsdown";
|
|
4
|
+
import {
|
|
5
|
+
temporalPolyfillCjsBanner,
|
|
6
|
+
temporalPolyfillCjsDeps,
|
|
7
|
+
temporalPolyfillEsmBanner,
|
|
8
|
+
temporalPolyfillImportPlugin,
|
|
9
|
+
temporalPolyfillIntro,
|
|
10
|
+
} from "../../scripts/tsdown/temporal.mts";
|
|
4
11
|
|
|
5
12
|
async function copyFileSafely(
|
|
6
13
|
source: string,
|
|
@@ -16,17 +23,21 @@ export default [
|
|
|
16
23
|
"./src/mod.ts",
|
|
17
24
|
],
|
|
18
25
|
dts: { compilerOptions: { isolatedDeclarations: true, declaration: true } },
|
|
19
|
-
format:
|
|
26
|
+
format: {
|
|
27
|
+
esm: {
|
|
28
|
+
banner: temporalPolyfillEsmBanner(),
|
|
29
|
+
},
|
|
30
|
+
cjs: {
|
|
31
|
+
deps: temporalPolyfillCjsDeps({
|
|
32
|
+
neverBundle: [/^node:/],
|
|
33
|
+
}),
|
|
34
|
+
plugins: [temporalPolyfillImportPlugin],
|
|
35
|
+
banner: temporalPolyfillCjsBanner(),
|
|
36
|
+
},
|
|
37
|
+
},
|
|
20
38
|
platform: "neutral",
|
|
21
|
-
deps: {
|
|
22
|
-
|
|
23
|
-
const js = format === "cjs"
|
|
24
|
-
? `const { Temporal } = require("@js-temporal/polyfill");`
|
|
25
|
-
: `import { Temporal } from "@js-temporal/polyfill";`;
|
|
26
|
-
return {
|
|
27
|
-
js,
|
|
28
|
-
dts: `/// <reference lib="esnext.temporal" />`,
|
|
29
|
-
};
|
|
39
|
+
deps: {
|
|
40
|
+
neverBundle: [/^node:/],
|
|
30
41
|
},
|
|
31
42
|
}),
|
|
32
43
|
defineConfig({
|
|
@@ -49,8 +60,7 @@ export default [
|
|
|
49
60
|
},
|
|
50
61
|
},
|
|
51
62
|
outputOptions: {
|
|
52
|
-
intro:
|
|
53
|
-
import { Temporal } from "@js-temporal/polyfill";
|
|
63
|
+
intro: `${temporalPolyfillIntro}
|
|
54
64
|
globalThis.addEventListener = () => {};
|
|
55
65
|
`,
|
|
56
66
|
},
|