@fedify/fedify 2.0.28-dev.1979 → 2.0.28-dev.1982
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/dist/{assert_instance_of-C4Ri6VuN.mjs → assert-BhQd8QHP.mjs} +19 -1
- package/dist/{builder-CAAXTNeK.mjs → builder-DG5rS_Zv.mjs} +3 -3
- package/dist/{client-HLLGHiTU.mjs → client-glFHmpn4.mjs} +78 -4
- package/dist/compat/mod.d.cts +1 -1
- package/dist/compat/mod.d.ts +1 -1
- package/dist/compat/transformers.test.mjs +3 -4
- package/dist/{context-DJ5oMnte.d.ts → context-B21klrfz.d.ts} +2 -2
- package/dist/{context-zLLwVY_G.d.cts → context-M_MxoQgC.d.cts} +2 -2
- package/dist/{deno-D3gZ1DsB.mjs → deno-eqaJj6JL.mjs} +1 -1
- package/dist/{docloader-DIk3fEXZ.mjs → docloader-BXUqhXNd.mjs} +2 -2
- package/dist/federation/builder.test.mjs +2 -2
- package/dist/federation/collection.test.mjs +1 -1
- package/dist/federation/handler.test.mjs +125 -8
- package/dist/federation/idempotency.test.mjs +4 -4
- package/dist/federation/inbox.test.mjs +1 -1
- package/dist/federation/keycache.test.mjs +44 -10
- package/dist/federation/kv.test.mjs +1 -1
- package/dist/federation/middleware.test.mjs +66 -10
- package/dist/federation/mod.cjs +1 -1
- package/dist/federation/mod.d.cts +2 -2
- package/dist/federation/mod.d.ts +2 -2
- package/dist/federation/mod.js +1 -1
- package/dist/federation/mq.test.mjs +1 -1
- package/dist/federation/negotiation.test.mjs +2 -2
- package/dist/federation/retry.test.mjs +1 -1
- package/dist/federation/router.test.mjs +1 -1
- package/dist/federation/send.test.mjs +5 -6
- package/dist/federation/temporal.test.mjs +2 -2
- package/dist/federation/webfinger.test.mjs +2 -2
- package/dist/{http-BLqv_3ue.cjs → http-BgOlx4n1.cjs} +163 -3
- package/dist/{http-C4D0ajx0.d.ts → http-Bs6thuCC.d.cts} +2 -2
- package/dist/{http-Bn3-am6C.mjs → http-Bu3NxfGb.mjs} +2 -2
- package/dist/{http-RYvZHWMc.d.cts → http-CDupyfOc.d.ts} +2 -2
- package/dist/{http-Cx5dD1D_.js → http-CwcG2wDs.js} +152 -4
- package/dist/{inbox-DC8NbYIj.mjs → inbox-Hou2ZAjv.mjs} +1 -1
- package/dist/{key-BQUnVMwc.mjs → key-bSphUqhB.mjs} +152 -4
- package/dist/{keycache-DaQ3ndaJ.mjs → keycache-D239uGT9.mjs} +12 -4
- package/dist/{kv-cache-CCEitzFw.cjs → kv-cache-B8GaLaNy.cjs} +1 -1
- package/dist/{kv-cache-DI4m-4xy.js → kv-cache-lbswb9pg.js} +1 -1
- package/dist/{ld-CVbwQ8Fz.mjs → ld-D0uFMLmq.mjs} +2 -2
- package/dist/{middleware-C7Afa66S.cjs → middleware-BKpP3cnZ.cjs} +1 -1
- package/dist/{middleware-D3a1JsO1.mjs → middleware-BuVM1K-J.mjs} +27 -19
- package/dist/{middleware-BqkNe0c6.mjs → middleware-DXPBIbm6.mjs} +1 -1
- package/dist/{middleware-Fcpx7tz6.cjs → middleware-D_7wZH27.cjs} +26 -10
- package/dist/{middleware-2Qp34eTb.js → middleware-DomxFoLd.js} +25 -9
- package/dist/{mod-B2zB0rDg.d.cts → mod-Bt0eg_Lv.d.cts} +1 -1
- package/dist/{mod-Du-UVCX_.d.ts → mod-D24V_w-K.d.ts} +1 -1
- package/dist/{mod-BtvLkCgb.d.cts → mod-Dp5dfH-i.d.cts} +1 -1
- package/dist/{mod-CiQNNAQL.d.ts → mod-E7YM72JZ.d.ts} +1 -1
- package/dist/{mod-CB6pc2pw.d.cts → mod-IOWUF5Yr.d.cts} +1 -1
- package/dist/{mod-CD1youdZ.d.ts → mod-XucBiDNd.d.ts} +1 -1
- package/dist/mod.cjs +5 -5
- package/dist/mod.d.cts +6 -6
- package/dist/mod.d.ts +6 -6
- package/dist/mod.js +5 -5
- package/dist/nodeinfo/client.test.mjs +28 -1
- package/dist/nodeinfo/handler.test.mjs +2 -2
- package/dist/nodeinfo/mod.cjs +1 -1
- package/dist/nodeinfo/mod.js +1 -1
- package/dist/nodeinfo/types.test.mjs +1 -1
- package/dist/otel/exporter.test.mjs +1 -1
- package/dist/{owner-CnngXDNJ.d.ts → owner-B3hNQCG8.d.cts} +12 -1
- package/dist/owner-Bzg6w7eA.mjs +175 -0
- package/dist/{owner-DEvZuyOE.d.cts → owner-CiBPcC6F.d.ts} +12 -1
- package/dist/{proof-DZWmoF6Z.mjs → proof-CgLA51pO.mjs} +2 -2
- package/dist/{proof-EY-uItWr.js → proof-DsJHFTkx.js} +112 -58
- package/dist/{proof-liMyPvsb.cjs → proof-xCEe8t7U.cjs} +112 -58
- package/dist/{send-IMhmEkun.mjs → send-D89-FDX-.mjs} +2 -2
- package/dist/sig/http.test.mjs +4 -4
- package/dist/sig/key.test.mjs +169 -2
- package/dist/sig/ld.test.mjs +56 -4
- package/dist/sig/mod.cjs +2 -2
- package/dist/sig/mod.d.cts +3 -3
- package/dist/sig/mod.d.ts +3 -3
- package/dist/sig/mod.js +2 -2
- package/dist/sig/owner.test.mjs +105 -6
- package/dist/sig/proof.test.mjs +72 -3
- package/dist/{temporal-BovRWwqL.mjs → temporal-DPJlkHh3.mjs} +1 -1
- package/dist/{types-m8u9L86X.cjs → types-DWs-qZaY.cjs} +94 -2
- package/dist/{types-DTONwNqL.js → types-sXITXcWa.js} +78 -4
- package/dist/utils/docloader.test.mjs +28 -4
- package/dist/utils/kv-cache.test.mjs +1 -1
- 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/package.json +5 -5
- package/dist/assert-ddO5KLpe.mjs +0 -23
- package/dist/owner-D9CjMxWb.mjs +0 -120
- /package/dist/{collection-ChgDTHLz.mjs → collection-DI6YzYpd.mjs} +0 -0
- /package/dist/{keys-CtZLJq76.mjs → keys-CCxRJhWa.mjs} +0 -0
- /package/dist/{kv-cache-DqEKe5E0.mjs → kv-cache-BrjuJue8.mjs} +0 -0
- /package/dist/{kv-BrZHNugx.mjs → kv-mI4Ix2EU.mjs} +0 -0
- /package/dist/{negotiation-BehA2uul.mjs → negotiation-TWEs6Cl8.mjs} +0 -0
- /package/dist/{retry-B_E3V_Dx.mjs → retry-Ddbq3AcK.mjs} +0 -0
- /package/dist/{types-C37hquWI.mjs → types-MNlFfxjv.mjs} +0 -0
package/dist/sig/key.test.mjs
CHANGED
|
@@ -4,9 +4,10 @@ globalThis.addEventListener = () => {};
|
|
|
4
4
|
import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
|
|
5
5
|
import "../std__assert-Duiq_YC9.mjs";
|
|
6
6
|
import { t as assertRejects } from "../assert_rejects-B-qJtC9Z.mjs";
|
|
7
|
+
import { t as assert } from "../assert-BhQd8QHP.mjs";
|
|
7
8
|
import { t as assertThrows } from "../assert_throws-4NwKEy2q.mjs";
|
|
8
|
-
import { a as
|
|
9
|
-
import { c as rsaPublicKey3, i as rsaPrivateKey2, o as rsaPublicKey1, s as rsaPublicKey2, t as ed25519Multikey } from "../keys-
|
|
9
|
+
import { a as importJwk, i as generateCryptoKeyPair, o as validateCryptoKey, r as fetchKey, t as exportJwk } from "../key-bSphUqhB.mjs";
|
|
10
|
+
import { c as rsaPublicKey3, i as rsaPrivateKey2, o as rsaPublicKey1, s as rsaPublicKey2, t as ed25519Multikey } from "../keys-CCxRJhWa.mjs";
|
|
10
11
|
import { mockDocumentLoader, test } from "@fedify/fixture";
|
|
11
12
|
import { CryptographicKey, Multikey } from "@fedify/vocab";
|
|
12
13
|
//#region src/sig/key.test.ts
|
|
@@ -331,5 +332,171 @@ test("fetchKey() returns null for a malformed actor publicKey", async () => {
|
|
|
331
332
|
cached: true
|
|
332
333
|
});
|
|
333
334
|
});
|
|
335
|
+
test("fetchKey() rejects a key whose owner does not link back", async () => {
|
|
336
|
+
const keyId = "https://attacker.example/key";
|
|
337
|
+
const keyDocument = await rsaPublicKey1.toJsonLd({ contextLoader: mockDocumentLoader });
|
|
338
|
+
const serveKeyOwnedBy = (owner) => ({
|
|
339
|
+
documentLoader(resource) {
|
|
340
|
+
if (resource === keyId) return Promise.resolve({
|
|
341
|
+
contextUrl: null,
|
|
342
|
+
documentUrl: resource,
|
|
343
|
+
document: {
|
|
344
|
+
...keyDocument,
|
|
345
|
+
id: keyId,
|
|
346
|
+
owner
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
return mockDocumentLoader(resource);
|
|
350
|
+
},
|
|
351
|
+
contextLoader: mockDocumentLoader
|
|
352
|
+
});
|
|
353
|
+
assertEquals(await fetchKey(keyId, CryptographicKey, serveKeyOwnedBy("https://example.com/person")), {
|
|
354
|
+
key: null,
|
|
355
|
+
cached: false
|
|
356
|
+
});
|
|
357
|
+
assertEquals(await fetchKey(keyId, CryptographicKey, serveKeyOwnedBy("https://impersonated.invalid/users/victim")), {
|
|
358
|
+
key: null,
|
|
359
|
+
cached: false
|
|
360
|
+
});
|
|
361
|
+
});
|
|
362
|
+
test("fetchKey() rejects a Multikey whose controller does not link back", async () => {
|
|
363
|
+
const keyId = "https://attacker.example/multikey";
|
|
364
|
+
const keyDocument = await ed25519Multikey.toJsonLd({ contextLoader: mockDocumentLoader });
|
|
365
|
+
assertEquals(await fetchKey(keyId, Multikey, {
|
|
366
|
+
documentLoader(resource) {
|
|
367
|
+
if (resource === keyId) return Promise.resolve({
|
|
368
|
+
contextUrl: null,
|
|
369
|
+
documentUrl: resource,
|
|
370
|
+
document: {
|
|
371
|
+
...keyDocument,
|
|
372
|
+
id: keyId,
|
|
373
|
+
controller: "https://example.com/person2"
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
return mockDocumentLoader(resource);
|
|
377
|
+
},
|
|
378
|
+
contextLoader: mockDocumentLoader
|
|
379
|
+
}), {
|
|
380
|
+
key: null,
|
|
381
|
+
cached: false
|
|
382
|
+
});
|
|
383
|
+
});
|
|
384
|
+
test("fetchKey() rejects an actor document from another origin", async () => {
|
|
385
|
+
const keyId = "https://attacker.example/key";
|
|
386
|
+
const impersonated = "https://example.com/person";
|
|
387
|
+
const { publicKeyPem } = await rsaPublicKey1.toJsonLd({ contextLoader: mockDocumentLoader });
|
|
388
|
+
assertEquals(await fetchKey(keyId, CryptographicKey, {
|
|
389
|
+
documentLoader(resource) {
|
|
390
|
+
if (resource === keyId) return Promise.resolve({
|
|
391
|
+
contextUrl: null,
|
|
392
|
+
documentUrl: resource,
|
|
393
|
+
document: {
|
|
394
|
+
"@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1"],
|
|
395
|
+
id: impersonated,
|
|
396
|
+
type: "Person",
|
|
397
|
+
publicKey: [{
|
|
398
|
+
id: `${impersonated}#main-key`,
|
|
399
|
+
type: "CryptographicKey",
|
|
400
|
+
owner: impersonated,
|
|
401
|
+
publicKeyPem
|
|
402
|
+
}]
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
return mockDocumentLoader(resource);
|
|
406
|
+
},
|
|
407
|
+
contextLoader: mockDocumentLoader
|
|
408
|
+
}), {
|
|
409
|
+
key: null,
|
|
410
|
+
cached: false
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
test("fetchKey() rejects a key that disowns the actor document holding it", async () => {
|
|
414
|
+
const actorId = "https://attacker.example/actor";
|
|
415
|
+
const keyId = `${actorId}#main-key`;
|
|
416
|
+
const { publicKeyPem } = await rsaPublicKey1.toJsonLd({ contextLoader: mockDocumentLoader });
|
|
417
|
+
assertEquals(await fetchKey(keyId, CryptographicKey, {
|
|
418
|
+
documentLoader(resource) {
|
|
419
|
+
if (resource === keyId || resource === actorId) return Promise.resolve({
|
|
420
|
+
contextUrl: null,
|
|
421
|
+
documentUrl: resource,
|
|
422
|
+
document: {
|
|
423
|
+
"@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1"],
|
|
424
|
+
id: actorId,
|
|
425
|
+
type: "Person",
|
|
426
|
+
publicKey: [{
|
|
427
|
+
id: keyId,
|
|
428
|
+
type: "CryptographicKey",
|
|
429
|
+
owner: "https://example.com/person",
|
|
430
|
+
publicKeyPem
|
|
431
|
+
}]
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
return mockDocumentLoader(resource);
|
|
435
|
+
},
|
|
436
|
+
contextLoader: mockDocumentLoader
|
|
437
|
+
}), {
|
|
438
|
+
key: null,
|
|
439
|
+
cached: false
|
|
440
|
+
});
|
|
441
|
+
});
|
|
442
|
+
test("fetchKey() records the owner its actor document establishes", async () => {
|
|
443
|
+
const { key } = await fetchKey("https://example.com/users/handle#main-key", CryptographicKey, {
|
|
444
|
+
documentLoader: mockDocumentLoader,
|
|
445
|
+
contextLoader: mockDocumentLoader
|
|
446
|
+
});
|
|
447
|
+
assertEquals(key?.ownerId, new URL("https://example.com/users/handle"));
|
|
448
|
+
});
|
|
449
|
+
test("fetchKey() accepts a key document that leaves its id implicit", async () => {
|
|
450
|
+
const keyId = "https://example.com/key";
|
|
451
|
+
const keyDocument = await rsaPublicKey1.toJsonLd({ contextLoader: mockDocumentLoader });
|
|
452
|
+
delete keyDocument.id;
|
|
453
|
+
const { key } = await fetchKey(keyId, CryptographicKey, {
|
|
454
|
+
documentLoader(resource) {
|
|
455
|
+
if (resource === keyId) return Promise.resolve({
|
|
456
|
+
contextUrl: null,
|
|
457
|
+
documentUrl: resource,
|
|
458
|
+
document: keyDocument
|
|
459
|
+
});
|
|
460
|
+
return mockDocumentLoader(resource);
|
|
461
|
+
},
|
|
462
|
+
contextLoader: mockDocumentLoader
|
|
463
|
+
});
|
|
464
|
+
assertEquals(key?.id, new URL(keyId));
|
|
465
|
+
assertEquals(key?.ownerId, new URL("https://example.com/person"));
|
|
466
|
+
assert(key?.publicKey != null);
|
|
467
|
+
});
|
|
468
|
+
test("fetchKey() records the controller an actor document establishes", async () => {
|
|
469
|
+
const actorId = "https://example.com/multikey-actor";
|
|
470
|
+
const keyId = `${actorId}#assertion`;
|
|
471
|
+
const { publicKeyMultibase } = await ed25519Multikey.toJsonLd({ contextLoader: mockDocumentLoader });
|
|
472
|
+
const { key } = await fetchKey(keyId, Multikey, {
|
|
473
|
+
documentLoader(resource) {
|
|
474
|
+
if (resource === actorId || resource === keyId) return Promise.resolve({
|
|
475
|
+
contextUrl: null,
|
|
476
|
+
documentUrl: actorId,
|
|
477
|
+
document: {
|
|
478
|
+
"@context": [
|
|
479
|
+
"https://www.w3.org/ns/activitystreams",
|
|
480
|
+
"https://w3id.org/security/v1",
|
|
481
|
+
"https://w3id.org/security/multikey/v1",
|
|
482
|
+
"https://w3id.org/security/data-integrity/v1",
|
|
483
|
+
"https://www.w3.org/ns/did/v1"
|
|
484
|
+
],
|
|
485
|
+
id: actorId,
|
|
486
|
+
type: "Person",
|
|
487
|
+
assertionMethod: [{
|
|
488
|
+
id: keyId,
|
|
489
|
+
type: "Multikey",
|
|
490
|
+
publicKeyMultibase
|
|
491
|
+
}]
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
return mockDocumentLoader(resource);
|
|
495
|
+
},
|
|
496
|
+
contextLoader: mockDocumentLoader
|
|
497
|
+
});
|
|
498
|
+
assertEquals(key?.controllerId, new URL(actorId));
|
|
499
|
+
assert(key?.publicKey != null);
|
|
500
|
+
});
|
|
334
501
|
//#endregion
|
|
335
502
|
export {};
|
package/dist/sig/ld.test.mjs
CHANGED
|
@@ -3,11 +3,11 @@ import "urlpattern-polyfill";
|
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
4
|
import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
|
|
5
5
|
import { n as assertFalse, t as assertRejects } from "../assert_rejects-B-qJtC9Z.mjs";
|
|
6
|
+
import { n as assertInstanceOf, t as assert } from "../assert-BhQd8QHP.mjs";
|
|
6
7
|
import { t as assertThrows } from "../assert_throws-4NwKEy2q.mjs";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { a as
|
|
10
|
-
import { a as compactJsonLd, d as isInvalidUrlTypeError, f as signJsonLd, h as verifySignature, i as attachSignature, m as verifyJsonLd, n as UnsafeJsonLdError, o as createSignature, s as detachSignature } from "../ld-CVbwQ8Fz.mjs";
|
|
8
|
+
import { i as generateCryptoKeyPair } from "../key-bSphUqhB.mjs";
|
|
9
|
+
import { a as rsaPrivateKey3, c as rsaPublicKey3, i as rsaPrivateKey2, n as ed25519PrivateKey, s as rsaPublicKey2, t as ed25519Multikey } from "../keys-CCxRJhWa.mjs";
|
|
10
|
+
import { a as compactJsonLd, d as isInvalidUrlTypeError, f as signJsonLd, h as verifySignature, i as attachSignature, m as verifyJsonLd, n as UnsafeJsonLdError, o as createSignature, s as detachSignature } from "../ld-D0uFMLmq.mjs";
|
|
11
11
|
import { mockDocumentLoader, test } from "@fedify/fixture";
|
|
12
12
|
import { CryptographicKey } from "@fedify/vocab";
|
|
13
13
|
import { encodeBase64 } from "byte-encodings/base64";
|
|
@@ -763,5 +763,57 @@ test("compactJsonLd() rejects inputs that compact into @graph wrappers", async (
|
|
|
763
763
|
content: "two"
|
|
764
764
|
}], mockDocumentLoader), UnsafeJsonLdError, "Unsupported JSON-LD keyword: @graph.");
|
|
765
765
|
});
|
|
766
|
+
test("verifyJsonLd() rejects a key that claims a forged owner", async () => {
|
|
767
|
+
const keyId = new URL("https://attacker.example/key");
|
|
768
|
+
const impersonated = "https://example.com/person";
|
|
769
|
+
const { publicKeyPem } = await rsaPublicKey2.toJsonLd({ contextLoader: mockDocumentLoader });
|
|
770
|
+
const signed = await signJsonLd({
|
|
771
|
+
"@context": "https://www.w3.org/ns/activitystreams",
|
|
772
|
+
id: "https://attacker.example/activities/1",
|
|
773
|
+
type: "Create",
|
|
774
|
+
actor: impersonated
|
|
775
|
+
}, rsaPrivateKey2, keyId, { contextLoader: mockDocumentLoader });
|
|
776
|
+
const serveKeyOwnedBy = (owner) => (resource) => {
|
|
777
|
+
if (resource === keyId.href) return Promise.resolve({
|
|
778
|
+
contextUrl: null,
|
|
779
|
+
documentUrl: resource,
|
|
780
|
+
document: {
|
|
781
|
+
"@context": "https://w3id.org/security/v1",
|
|
782
|
+
id: keyId.href,
|
|
783
|
+
type: "CryptographicKey",
|
|
784
|
+
owner,
|
|
785
|
+
publicKeyPem
|
|
786
|
+
}
|
|
787
|
+
});
|
|
788
|
+
if (resource === "https://attacker.example/actor") return Promise.resolve({
|
|
789
|
+
contextUrl: null,
|
|
790
|
+
documentUrl: resource,
|
|
791
|
+
document: {
|
|
792
|
+
"@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1"],
|
|
793
|
+
id: resource,
|
|
794
|
+
type: "Person",
|
|
795
|
+
publicKey: [keyId.href]
|
|
796
|
+
}
|
|
797
|
+
});
|
|
798
|
+
return mockDocumentLoader(resource);
|
|
799
|
+
};
|
|
800
|
+
assertEquals(await verifySignature(signed, {
|
|
801
|
+
documentLoader: serveKeyOwnedBy(impersonated),
|
|
802
|
+
contextLoader: mockDocumentLoader
|
|
803
|
+
}), null);
|
|
804
|
+
assertFalse(await verifyJsonLd(signed, {
|
|
805
|
+
documentLoader: serveKeyOwnedBy(impersonated),
|
|
806
|
+
contextLoader: mockDocumentLoader
|
|
807
|
+
}));
|
|
808
|
+
const honestLoader = serveKeyOwnedBy("https://attacker.example/actor");
|
|
809
|
+
assertInstanceOf(await verifySignature(signed, {
|
|
810
|
+
documentLoader: honestLoader,
|
|
811
|
+
contextLoader: mockDocumentLoader
|
|
812
|
+
}), CryptographicKey);
|
|
813
|
+
assertFalse(await verifyJsonLd(signed, {
|
|
814
|
+
documentLoader: honestLoader,
|
|
815
|
+
contextLoader: mockDocumentLoader
|
|
816
|
+
}));
|
|
817
|
+
});
|
|
766
818
|
//#endregion
|
|
767
819
|
export {};
|
package/dist/sig/mod.cjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
const { Temporal } = require("@js-temporal/polyfill");
|
|
2
2
|
const { URLPattern } = require("urlpattern-polyfill");
|
|
3
3
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
4
|
-
const require_http = require("../http-
|
|
5
|
-
const require_proof = require("../proof-
|
|
4
|
+
const require_http = require("../http-BgOlx4n1.cjs");
|
|
5
|
+
const require_proof = require("../proof-xCEe8t7U.cjs");
|
|
6
6
|
exports.attachSignature = require_proof.attachSignature;
|
|
7
7
|
exports.createProof = require_proof.createProof;
|
|
8
8
|
exports.createSignature = require_proof.createSignature;
|
package/dist/sig/mod.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/// <reference lib="esnext.temporal" />
|
|
2
|
-
import { a as signRequest, c as FetchKeyResult, d as fetchKey, f as generateCryptoKeyPair, i as VerifyRequestOptions, l as KeyCache, n as HttpMessageSignaturesSpecDeterminer, o as verifyRequest, p as importJwk, r as SignRequestOptions, s as FetchKeyOptions, t as HttpMessageSignaturesSpec, u as exportJwk } from "../http-
|
|
3
|
-
import { i as getKeyOwner, n as GetKeyOwnerOptions, r as doesActorOwnKey, t as DoesActorOwnKeyOptions } from "../owner-
|
|
4
|
-
import { _ as verifyJsonLd, a as createProof, c as verifyProof, d as VerifyJsonLdOptions, f as VerifySignatureOptions, g as signJsonLd, h as detachSignature, i as VerifyProofOptions, l as CreateSignatureOptions, m as createSignature, n as SignObjectOptions, o as signObject, p as attachSignature, r as VerifyObjectOptions, s as verifyObject, t as CreateProofOptions, u as SignJsonLdOptions, v as verifySignature } from "../mod-
|
|
2
|
+
import { a as signRequest, c as FetchKeyResult, d as fetchKey, f as generateCryptoKeyPair, i as VerifyRequestOptions, l as KeyCache, n as HttpMessageSignaturesSpecDeterminer, o as verifyRequest, p as importJwk, r as SignRequestOptions, s as FetchKeyOptions, t as HttpMessageSignaturesSpec, u as exportJwk } from "../http-Bs6thuCC.cjs";
|
|
3
|
+
import { i as getKeyOwner, n as GetKeyOwnerOptions, r as doesActorOwnKey, t as DoesActorOwnKeyOptions } from "../owner-B3hNQCG8.cjs";
|
|
4
|
+
import { _ as verifyJsonLd, a as createProof, c as verifyProof, d as VerifyJsonLdOptions, f as VerifySignatureOptions, g as signJsonLd, h as detachSignature, i as VerifyProofOptions, l as CreateSignatureOptions, m as createSignature, n as SignObjectOptions, o as signObject, p as attachSignature, r as VerifyObjectOptions, s as verifyObject, t as CreateProofOptions, u as SignJsonLdOptions, v as verifySignature } from "../mod-IOWUF5Yr.cjs";
|
|
5
5
|
export { CreateProofOptions, CreateSignatureOptions, DoesActorOwnKeyOptions, FetchKeyOptions, FetchKeyResult, GetKeyOwnerOptions, HttpMessageSignaturesSpec, HttpMessageSignaturesSpecDeterminer, KeyCache, SignJsonLdOptions, SignObjectOptions, SignRequestOptions, VerifyJsonLdOptions, VerifyObjectOptions, VerifyProofOptions, VerifyRequestOptions, VerifySignatureOptions, attachSignature, createProof, createSignature, detachSignature, doesActorOwnKey, exportJwk, fetchKey, generateCryptoKeyPair, getKeyOwner, importJwk, signJsonLd, signObject, signRequest, verifyJsonLd, verifyObject, verifyProof, verifyRequest, verifySignature };
|
package/dist/sig/mod.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/// <reference lib="esnext.temporal" />
|
|
2
|
-
import { a as signRequest, c as FetchKeyResult, d as fetchKey, f as generateCryptoKeyPair, i as VerifyRequestOptions, l as KeyCache, n as HttpMessageSignaturesSpecDeterminer, o as verifyRequest, p as importJwk, r as SignRequestOptions, s as FetchKeyOptions, t as HttpMessageSignaturesSpec, u as exportJwk } from "../http-
|
|
3
|
-
import { i as getKeyOwner, n as GetKeyOwnerOptions, r as doesActorOwnKey, t as DoesActorOwnKeyOptions } from "../owner-
|
|
4
|
-
import { _ as verifyJsonLd, a as createProof, c as verifyProof, d as VerifyJsonLdOptions, f as VerifySignatureOptions, g as signJsonLd, h as detachSignature, i as VerifyProofOptions, l as CreateSignatureOptions, m as createSignature, n as SignObjectOptions, o as signObject, p as attachSignature, r as VerifyObjectOptions, s as verifyObject, t as CreateProofOptions, u as SignJsonLdOptions, v as verifySignature } from "../mod-
|
|
2
|
+
import { a as signRequest, c as FetchKeyResult, d as fetchKey, f as generateCryptoKeyPair, i as VerifyRequestOptions, l as KeyCache, n as HttpMessageSignaturesSpecDeterminer, o as verifyRequest, p as importJwk, r as SignRequestOptions, s as FetchKeyOptions, t as HttpMessageSignaturesSpec, u as exportJwk } from "../http-CDupyfOc.js";
|
|
3
|
+
import { i as getKeyOwner, n as GetKeyOwnerOptions, r as doesActorOwnKey, t as DoesActorOwnKeyOptions } from "../owner-CiBPcC6F.js";
|
|
4
|
+
import { _ as verifyJsonLd, a as createProof, c as verifyProof, d as VerifyJsonLdOptions, f as VerifySignatureOptions, g as signJsonLd, h as detachSignature, i as VerifyProofOptions, l as CreateSignatureOptions, m as createSignature, n as SignObjectOptions, o as signObject, p as attachSignature, r as VerifyObjectOptions, s as verifyObject, t as CreateProofOptions, u as SignJsonLdOptions, v as verifySignature } from "../mod-E7YM72JZ.js";
|
|
5
5
|
export { CreateProofOptions, CreateSignatureOptions, DoesActorOwnKeyOptions, FetchKeyOptions, FetchKeyResult, GetKeyOwnerOptions, HttpMessageSignaturesSpec, HttpMessageSignaturesSpecDeterminer, KeyCache, SignJsonLdOptions, SignObjectOptions, SignRequestOptions, VerifyJsonLdOptions, VerifyObjectOptions, VerifyProofOptions, VerifyRequestOptions, VerifySignatureOptions, attachSignature, createProof, createSignature, detachSignature, doesActorOwnKey, exportJwk, fetchKey, generateCryptoKeyPair, getKeyOwner, importJwk, signJsonLd, signObject, signRequest, verifyJsonLd, verifyObject, verifyProof, verifyRequest, verifySignature };
|
package/dist/sig/mod.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Temporal } from "@js-temporal/polyfill";
|
|
2
2
|
import { URLPattern } from "urlpattern-polyfill";
|
|
3
|
-
import {
|
|
4
|
-
import { _ as signJsonLd, a as doesActorOwnKey, b as verifySignature, d as createSignature, f as detachSignature, i as verifyProof, l as attachSignature, n as signObject, o as getKeyOwner, r as verifyObject, t as createProof, y as verifyJsonLd } from "../proof-
|
|
3
|
+
import { c as importJwk, i as exportJwk, n as signRequest, o as fetchKey, r as verifyRequest, s as generateCryptoKeyPair } from "../http-CwcG2wDs.js";
|
|
4
|
+
import { _ as signJsonLd, a as doesActorOwnKey, b as verifySignature, d as createSignature, f as detachSignature, i as verifyProof, l as attachSignature, n as signObject, o as getKeyOwner, r as verifyObject, t as createProof, y as verifyJsonLd } from "../proof-DsJHFTkx.js";
|
|
5
5
|
export { attachSignature, createProof, createSignature, detachSignature, doesActorOwnKey, exportJwk, fetchKey, generateCryptoKeyPair, getKeyOwner, importJwk, signJsonLd, signObject, signRequest, verifyJsonLd, verifyObject, verifyProof, verifyRequest, verifySignature };
|
package/dist/sig/owner.test.mjs
CHANGED
|
@@ -4,9 +4,9 @@ globalThis.addEventListener = () => {};
|
|
|
4
4
|
import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
|
|
5
5
|
import "../std__assert-Duiq_YC9.mjs";
|
|
6
6
|
import { n as assertFalse } from "../assert_rejects-B-qJtC9Z.mjs";
|
|
7
|
-
import { t as assert } from "../assert-
|
|
8
|
-
import { o as rsaPublicKey1, s as rsaPublicKey2 } from "../keys-
|
|
9
|
-
import { n as getKeyOwner, t as doesActorOwnKey } from "../owner-
|
|
7
|
+
import { t as assert } from "../assert-BhQd8QHP.mjs";
|
|
8
|
+
import { o as rsaPublicKey1, s as rsaPublicKey2 } from "../keys-CCxRJhWa.mjs";
|
|
9
|
+
import { n as getKeyOwner, t as doesActorOwnKey } from "../owner-Bzg6w7eA.mjs";
|
|
10
10
|
import { createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
|
|
11
11
|
import { Create, CryptographicKey, lookupObject } from "@fedify/vocab";
|
|
12
12
|
//#region src/sig/owner.test.ts
|
|
@@ -40,7 +40,7 @@ test("doesActorOwnKey() records OpenTelemetry span", async () => {
|
|
|
40
40
|
id: new URL("https://example.com/activity"),
|
|
41
41
|
actor: new URL("https://example.com/person")
|
|
42
42
|
}), new CryptographicKey({
|
|
43
|
-
id: new URL("https://example.com/
|
|
43
|
+
id: new URL("https://example.com/key"),
|
|
44
44
|
owner: new URL("https://example.com/person")
|
|
45
45
|
}), {
|
|
46
46
|
documentLoader: mockDocumentLoader,
|
|
@@ -50,9 +50,108 @@ test("doesActorOwnKey() records OpenTelemetry span", async () => {
|
|
|
50
50
|
assertEquals(spans.length, 1);
|
|
51
51
|
const span = spans[0];
|
|
52
52
|
assertEquals(span.attributes["activitypub.actor.id"], "https://example.com/person");
|
|
53
|
-
assertEquals(span.attributes["activitypub.key.id"], "https://example.com/
|
|
53
|
+
assertEquals(span.attributes["activitypub.key.id"], "https://example.com/key");
|
|
54
54
|
assertEquals(span.attributes["activitypub.key_ownership.verified"], true);
|
|
55
|
-
assertEquals(span.attributes["activitypub.key_ownership.method"], "
|
|
55
|
+
assertEquals(span.attributes["activitypub.key_ownership.method"], "key_owner");
|
|
56
|
+
});
|
|
57
|
+
test("doesActorOwnKey() rejects a key that only claims to be owned", async () => {
|
|
58
|
+
const options = {
|
|
59
|
+
documentLoader: mockDocumentLoader,
|
|
60
|
+
contextLoader: mockDocumentLoader
|
|
61
|
+
};
|
|
62
|
+
assertFalse(await doesActorOwnKey(new Create({ actor: new URL("https://example.com/person") }), new CryptographicKey({
|
|
63
|
+
id: new URL("https://attacker.example/key"),
|
|
64
|
+
owner: new URL("https://example.com/person")
|
|
65
|
+
}), options));
|
|
66
|
+
const unresolvable = new URL("https://impersonated.invalid/users/victim");
|
|
67
|
+
assertFalse(await doesActorOwnKey(new Create({ actor: unresolvable }), new CryptographicKey({
|
|
68
|
+
id: new URL("https://attacker.example/key"),
|
|
69
|
+
owner: unresolvable
|
|
70
|
+
}), options));
|
|
71
|
+
});
|
|
72
|
+
test("doesActorOwnKey() ignores an actor embedded in the activity", async () => {
|
|
73
|
+
const { publicKeyPem } = await rsaPublicKey1.toJsonLd({ contextLoader: mockDocumentLoader });
|
|
74
|
+
const activity = await Create.fromJsonLd({
|
|
75
|
+
"@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1"],
|
|
76
|
+
id: "https://example.com/activity",
|
|
77
|
+
type: "Create",
|
|
78
|
+
actor: {
|
|
79
|
+
id: "https://example.com/person",
|
|
80
|
+
type: "Person",
|
|
81
|
+
publicKey: {
|
|
82
|
+
id: "https://attacker.example/key",
|
|
83
|
+
type: "CryptographicKey",
|
|
84
|
+
owner: "https://example.com/person",
|
|
85
|
+
publicKeyPem
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}, {
|
|
89
|
+
documentLoader: mockDocumentLoader,
|
|
90
|
+
contextLoader: mockDocumentLoader
|
|
91
|
+
});
|
|
92
|
+
assertEquals(activity.actorId, new URL("https://example.com/person"));
|
|
93
|
+
assertFalse(await doesActorOwnKey(activity, new CryptographicKey({ id: new URL("https://attacker.example/key") }), {
|
|
94
|
+
documentLoader: mockDocumentLoader,
|
|
95
|
+
contextLoader: mockDocumentLoader
|
|
96
|
+
}));
|
|
97
|
+
});
|
|
98
|
+
test("getKeyOwner() rejects an actor document from another origin", async () => {
|
|
99
|
+
const keyId = "https://attacker.example/key";
|
|
100
|
+
const { publicKeyPem } = await rsaPublicKey1.toJsonLd({ contextLoader: mockDocumentLoader });
|
|
101
|
+
const options = {
|
|
102
|
+
documentLoader(resource) {
|
|
103
|
+
if (resource === keyId) return Promise.resolve({
|
|
104
|
+
contextUrl: null,
|
|
105
|
+
documentUrl: resource,
|
|
106
|
+
document: {
|
|
107
|
+
"@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1"],
|
|
108
|
+
id: "https://example.com/person",
|
|
109
|
+
type: "Person",
|
|
110
|
+
publicKey: [{
|
|
111
|
+
id: keyId,
|
|
112
|
+
type: "CryptographicKey",
|
|
113
|
+
publicKeyPem
|
|
114
|
+
}]
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
return mockDocumentLoader(resource);
|
|
118
|
+
},
|
|
119
|
+
contextLoader: mockDocumentLoader
|
|
120
|
+
};
|
|
121
|
+
assertEquals(await getKeyOwner(new URL(keyId), options), null);
|
|
122
|
+
});
|
|
123
|
+
test("doesActorOwnKey() matches a key that leaves its id implicit", async () => {
|
|
124
|
+
const actorId = "https://example.com/implicit-key-actor";
|
|
125
|
+
const { publicKeyPem } = await rsaPublicKey1.toJsonLd({ contextLoader: mockDocumentLoader });
|
|
126
|
+
const options = {
|
|
127
|
+
documentLoader(resource) {
|
|
128
|
+
if (resource === actorId) return Promise.resolve({
|
|
129
|
+
contextUrl: null,
|
|
130
|
+
documentUrl: resource,
|
|
131
|
+
document: {
|
|
132
|
+
"@context": ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1"],
|
|
133
|
+
id: actorId,
|
|
134
|
+
type: "Person",
|
|
135
|
+
publicKey: [{
|
|
136
|
+
type: "CryptographicKey",
|
|
137
|
+
owner: actorId,
|
|
138
|
+
publicKeyPem
|
|
139
|
+
}]
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
return mockDocumentLoader(resource);
|
|
143
|
+
},
|
|
144
|
+
contextLoader: mockDocumentLoader
|
|
145
|
+
};
|
|
146
|
+
const activity = new Create({ actor: new URL(actorId) });
|
|
147
|
+
assert(await doesActorOwnKey(activity, new CryptographicKey({
|
|
148
|
+
owner: new URL(actorId),
|
|
149
|
+
publicKey: rsaPublicKey1.publicKey
|
|
150
|
+
}), options));
|
|
151
|
+
assertFalse(await doesActorOwnKey(activity, new CryptographicKey({
|
|
152
|
+
owner: new URL(actorId),
|
|
153
|
+
publicKey: rsaPublicKey2.publicKey
|
|
154
|
+
}), options));
|
|
56
155
|
});
|
|
57
156
|
//#endregion
|
|
58
157
|
export {};
|
package/dist/sig/proof.test.mjs
CHANGED
|
@@ -4,9 +4,9 @@ globalThis.addEventListener = () => {};
|
|
|
4
4
|
import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
|
|
5
5
|
import "../std__assert-Duiq_YC9.mjs";
|
|
6
6
|
import { t as assertRejects } from "../assert_rejects-B-qJtC9Z.mjs";
|
|
7
|
-
import {
|
|
8
|
-
import { i as rsaPrivateKey2, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2, t as ed25519Multikey } from "../keys-
|
|
9
|
-
import { i as verifyProof, n as signObject, r as verifyObject, t as createProof } from "../proof-
|
|
7
|
+
import { n as assertInstanceOf } from "../assert-BhQd8QHP.mjs";
|
|
8
|
+
import { i as rsaPrivateKey2, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2, t as ed25519Multikey } from "../keys-CCxRJhWa.mjs";
|
|
9
|
+
import { i as verifyProof, n as signObject, r as verifyObject, t as createProof } from "../proof-CgLA51pO.mjs";
|
|
10
10
|
import { mockDocumentLoader, test } from "@fedify/fixture";
|
|
11
11
|
import { Create, DataIntegrityProof, Multikey, Note, Place } from "@fedify/vocab";
|
|
12
12
|
import { decodeMultibase, importMultibaseKey } from "@fedify/vocab-runtime";
|
|
@@ -253,5 +253,74 @@ test("verifyObject()", async () => {
|
|
|
253
253
|
assertInstanceOf(note, Note);
|
|
254
254
|
assertEquals(note.content, "Hello world");
|
|
255
255
|
});
|
|
256
|
+
test("verifyObject() rejects a key that claims a forged controller", async () => {
|
|
257
|
+
const keyId = new URL("https://attacker.example/multikey");
|
|
258
|
+
const impersonated = "https://example.com/person2";
|
|
259
|
+
const attackerActor = "https://attacker.example/actor";
|
|
260
|
+
const multikeyDocument = await ed25519Multikey.toJsonLd({ contextLoader: mockDocumentLoader });
|
|
261
|
+
const options = {
|
|
262
|
+
format: "compact",
|
|
263
|
+
contextLoader: mockDocumentLoader,
|
|
264
|
+
documentLoader: mockDocumentLoader,
|
|
265
|
+
context: ["https://www.w3.org/ns/activitystreams", "https://w3id.org/security/data-integrity/v1"]
|
|
266
|
+
};
|
|
267
|
+
const signed = await signObject(new Create({
|
|
268
|
+
id: new URL("https://attacker.example/activities/1"),
|
|
269
|
+
actor: new URL(impersonated),
|
|
270
|
+
object: new Note({
|
|
271
|
+
id: new URL("https://attacker.example/notes/1"),
|
|
272
|
+
attribution: new URL(impersonated),
|
|
273
|
+
content: "Hello world"
|
|
274
|
+
})
|
|
275
|
+
}), ed25519PrivateKey, keyId, options);
|
|
276
|
+
const jsonLd = await signed.toJsonLd(options);
|
|
277
|
+
const serveKeyControlledBy = (controller) => (resource) => {
|
|
278
|
+
if (resource === keyId.href) return Promise.resolve({
|
|
279
|
+
contextUrl: null,
|
|
280
|
+
documentUrl: resource,
|
|
281
|
+
document: {
|
|
282
|
+
...multikeyDocument,
|
|
283
|
+
id: keyId.href,
|
|
284
|
+
controller
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
if (resource === attackerActor) return Promise.resolve({
|
|
288
|
+
contextUrl: null,
|
|
289
|
+
documentUrl: resource,
|
|
290
|
+
document: {
|
|
291
|
+
"@context": [
|
|
292
|
+
"https://www.w3.org/ns/activitystreams",
|
|
293
|
+
"https://w3id.org/security/v1",
|
|
294
|
+
"https://w3id.org/security/multikey/v1",
|
|
295
|
+
"https://w3id.org/security/data-integrity/v1",
|
|
296
|
+
"https://www.w3.org/ns/did/v1"
|
|
297
|
+
],
|
|
298
|
+
id: resource,
|
|
299
|
+
type: "Person",
|
|
300
|
+
assertionMethod: [keyId.href]
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
return mockDocumentLoader(resource);
|
|
304
|
+
};
|
|
305
|
+
assertEquals(await verifyObject(Create, jsonLd, {
|
|
306
|
+
documentLoader: serveKeyControlledBy(impersonated),
|
|
307
|
+
contextLoader: mockDocumentLoader
|
|
308
|
+
}), null);
|
|
309
|
+
const honestLoader = serveKeyControlledBy(attackerActor);
|
|
310
|
+
let proof = null;
|
|
311
|
+
for await (const p of signed.getProofs(options)) {
|
|
312
|
+
proof = p;
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
assertInstanceOf(proof, DataIntegrityProof);
|
|
316
|
+
assertInstanceOf(await verifyProof(jsonLd, proof, {
|
|
317
|
+
documentLoader: honestLoader,
|
|
318
|
+
contextLoader: mockDocumentLoader
|
|
319
|
+
}), Multikey);
|
|
320
|
+
assertEquals(await verifyObject(Create, jsonLd, {
|
|
321
|
+
documentLoader: honestLoader,
|
|
322
|
+
contextLoader: mockDocumentLoader
|
|
323
|
+
}), null);
|
|
324
|
+
});
|
|
256
325
|
//#endregion
|
|
257
326
|
export {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Temporal } from "@js-temporal/polyfill";
|
|
2
2
|
import "urlpattern-polyfill";
|
|
3
3
|
globalThis.addEventListener = () => {};
|
|
4
|
-
import { c as getNormalizationContextLoader } from "./ld-
|
|
4
|
+
import { c as getNormalizationContextLoader } from "./ld-D0uFMLmq.mjs";
|
|
5
5
|
import jsonld from "@fedify/vocab-runtime/jsonld";
|
|
6
6
|
//#region src/federation/temporal.ts
|
|
7
7
|
function isPlainObject(value) {
|
|
@@ -3,6 +3,80 @@ const { URLPattern } = require("urlpattern-polyfill");
|
|
|
3
3
|
require("./chunk-DDcVe30Y.cjs");
|
|
4
4
|
let _logtape_logtape = require("@logtape/logtape");
|
|
5
5
|
let _fedify_vocab_runtime = require("@fedify/vocab-runtime");
|
|
6
|
+
//#region src/utils/body.ts
|
|
7
|
+
/** Decoded JSON body limit: 16 MiB. @internal */
|
|
8
|
+
const MAX_BODY_SIZE = 16 * 1024 * 1024;
|
|
9
|
+
/** A body exceeded the byte limit. @internal */
|
|
10
|
+
var BodyTooLargeError = class extends _fedify_vocab_runtime.FetchError {
|
|
11
|
+
/** Creates an error for a body exceeding the given limit. */
|
|
12
|
+
constructor(url, maxBytes) {
|
|
13
|
+
super(url, `Body exceeds the limit of ${maxBytes} bytes`);
|
|
14
|
+
this.name = "BodyTooLargeError";
|
|
15
|
+
}
|
|
16
|
+
};
|
|
17
|
+
/** Validates a finite, positive byte limit. @internal */
|
|
18
|
+
function validateBodySizeLimit(maxBytes) {
|
|
19
|
+
if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) throw new RangeError("The body size limit must be a positive safe integer.");
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Reads UTF-8 text while limiting bytes received from the body stream.
|
|
23
|
+
* Fetch responses are already decompressed, so the stream count is authoritative.
|
|
24
|
+
* @param message The response or request to read.
|
|
25
|
+
* @param maxBytes The maximum number of decoded bytes to read.
|
|
26
|
+
* @param url The URL to include in errors and logs.
|
|
27
|
+
* @returns The decoded body text.
|
|
28
|
+
* @throws {BodyTooLargeError} If the body exceeds the limit.
|
|
29
|
+
* @internal
|
|
30
|
+
*/
|
|
31
|
+
async function readBoundedText(message, maxBytes, url) {
|
|
32
|
+
validateBodySizeLimit(maxBytes);
|
|
33
|
+
const reader = message.body?.getReader();
|
|
34
|
+
const tooLarge = () => {
|
|
35
|
+
(0, _logtape_logtape.getLogger)([
|
|
36
|
+
"fedify",
|
|
37
|
+
"runtime",
|
|
38
|
+
"body"
|
|
39
|
+
]).warn("Body from {url} exceeds the limit of {maxBytes} bytes.", {
|
|
40
|
+
url: url.toString(),
|
|
41
|
+
maxBytes
|
|
42
|
+
});
|
|
43
|
+
throw new BodyTooLargeError(url, maxBytes);
|
|
44
|
+
};
|
|
45
|
+
try {
|
|
46
|
+
const length = message.headers.get("Content-Length");
|
|
47
|
+
const encoding = message.headers.get("Content-Encoding");
|
|
48
|
+
if ((encoding == null || encoding.toLowerCase() === "identity") && length != null && /^\d+$/.test(length) && Number(length) > maxBytes) tooLarge();
|
|
49
|
+
if (reader == null) return "";
|
|
50
|
+
let size = 0;
|
|
51
|
+
const textReader = new ReadableStream({ async pull(controller) {
|
|
52
|
+
const { done, value } = await reader.read();
|
|
53
|
+
if (done) {
|
|
54
|
+
controller.close();
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
size += value.byteLength;
|
|
58
|
+
if (size > maxBytes) tooLarge();
|
|
59
|
+
controller.enqueue(value);
|
|
60
|
+
} }, { highWaterMark: 0 }).pipeThrough(new TextDecoderStream()).getReader();
|
|
61
|
+
try {
|
|
62
|
+
const chunks = [];
|
|
63
|
+
while (true) {
|
|
64
|
+
const { done, value } = await textReader.read();
|
|
65
|
+
if (done) break;
|
|
66
|
+
chunks.push(value);
|
|
67
|
+
}
|
|
68
|
+
return chunks.join("");
|
|
69
|
+
} finally {
|
|
70
|
+
textReader.releaseLock();
|
|
71
|
+
}
|
|
72
|
+
} catch (error) {
|
|
73
|
+
if (reader != null) reader.cancel(error).catch(() => {});
|
|
74
|
+
throw error;
|
|
75
|
+
} finally {
|
|
76
|
+
reader?.releaseLock();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
//#endregion
|
|
6
80
|
//#region src/nodeinfo/client.ts
|
|
7
81
|
const logger = (0, _logtape_logtape.getLogger)([
|
|
8
82
|
"fedify",
|
|
@@ -83,7 +157,7 @@ async function getNodeInfo(url, options = {}) {
|
|
|
83
157
|
});
|
|
84
158
|
return;
|
|
85
159
|
}
|
|
86
|
-
const wellKnownRd = await wellKnownResponse
|
|
160
|
+
const wellKnownRd = JSON.parse(await readBoundedText(wellKnownResponse, MAX_BODY_SIZE, wellKnownUrl));
|
|
87
161
|
const link = wellKnownRd?.links?.find((link) => link != null && "rel" in link && (link.rel === "http://nodeinfo.diaspora.software/ns/schema/2.0" || link.rel === "http://nodeinfo.diaspora.software/ns/schema/2.1") && "href" in link && link.href != null);
|
|
88
162
|
if (link == null || link.href == null) {
|
|
89
163
|
logger.error("Failed to find a NodeInfo document link from {url}: {resourceDescriptor}", {
|
|
@@ -107,7 +181,7 @@ async function getNodeInfo(url, options = {}) {
|
|
|
107
181
|
});
|
|
108
182
|
return;
|
|
109
183
|
}
|
|
110
|
-
const data = await response
|
|
184
|
+
const data = JSON.parse(await readBoundedText(response, MAX_BODY_SIZE, nodeInfoUrl));
|
|
111
185
|
if (options.parse === "none") return data;
|
|
112
186
|
return parseNodeInfo(data, { tryBestEffort: options.parse === "best-effort" }) ?? void 0;
|
|
113
187
|
} catch (error) {
|
|
@@ -353,6 +427,18 @@ function nodeInfoToJson(nodeInfo) {
|
|
|
353
427
|
};
|
|
354
428
|
}
|
|
355
429
|
//#endregion
|
|
430
|
+
Object.defineProperty(exports, "BodyTooLargeError", {
|
|
431
|
+
enumerable: true,
|
|
432
|
+
get: function() {
|
|
433
|
+
return BodyTooLargeError;
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
Object.defineProperty(exports, "MAX_BODY_SIZE", {
|
|
437
|
+
enumerable: true,
|
|
438
|
+
get: function() {
|
|
439
|
+
return MAX_BODY_SIZE;
|
|
440
|
+
}
|
|
441
|
+
});
|
|
356
442
|
Object.defineProperty(exports, "getNodeInfo", {
|
|
357
443
|
enumerable: true,
|
|
358
444
|
get: function() {
|
|
@@ -371,3 +457,9 @@ Object.defineProperty(exports, "parseNodeInfo", {
|
|
|
371
457
|
return parseNodeInfo;
|
|
372
458
|
}
|
|
373
459
|
});
|
|
460
|
+
Object.defineProperty(exports, "readBoundedText", {
|
|
461
|
+
enumerable: true,
|
|
462
|
+
get: function() {
|
|
463
|
+
return readBoundedText;
|
|
464
|
+
}
|
|
465
|
+
});
|