@fedify/fedify 2.4.0-dev.2073 → 2.4.0-dev.2084
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/{builder-CVHpPAjK.mjs → builder-BGlpUocK.mjs} +2 -2
- package/dist/compat/transformers.test.mjs +1 -1
- package/dist/{compound-proof-B5Skp-vE.mjs → compound-proof-B6Aj2kGY.mjs} +1 -1
- package/dist/{deno-LYTbBhYV.mjs → deno-Dn-py9Zy.mjs} +1 -1
- package/dist/{docloader-C0SWQylk.mjs → docloader-BUq5bYJr.mjs} +2 -2
- package/dist/federation/builder.test.mjs +1 -1
- package/dist/federation/compound-fanout.test.mjs +3 -3
- package/dist/federation/handler-compound-proof.test.mjs +137 -10
- package/dist/federation/handler.test.mjs +5 -5
- package/dist/federation/hashlink-media.test.mjs +2 -2
- package/dist/federation/idempotency.test.mjs +2 -2
- package/dist/federation/metrics.test.mjs +1 -1
- package/dist/federation/middleware.test.mjs +7 -7
- package/dist/federation/mod.cjs +1 -1
- package/dist/federation/mod.js +1 -1
- package/dist/federation/outgoing-proof.test.mjs +5 -5
- package/dist/federation/portable-gateway-keys.test.mjs +3 -3
- package/dist/federation/portable-inbox.test.mjs +401 -6
- package/dist/federation/portable.test.mjs +3 -3
- package/dist/federation/send.test.mjs +3 -3
- package/dist/federation/tasks/enqueue.test.mjs +1 -1
- package/dist/federation/tasks/tasks.test.mjs +2 -2
- package/dist/federation/temporal.test.mjs +1 -1
- package/dist/federation/webfinger.test.mjs +1 -1
- package/dist/{federation-B1aeTKKN.cjs → federation-BLGx8JUH.cjs} +126 -22
- package/dist/{http-vfTpzGEq.js → http-4PCCvhsU.js} +1 -1
- package/dist/{http-C7qHlv-b.mjs → http-B0VRNipE.mjs} +4 -4
- package/dist/{http-DGk8XI38.cjs → http-B5q1CHtL.cjs} +1 -1
- package/dist/{key-BYXnTGro.mjs → key-BJrp524f.mjs} +2 -2
- package/dist/{kv-cache-DCJuwdnB.js → kv-cache-1KUmAPH7.js} +1 -1
- package/dist/{kv-cache-DEH5jyND.mjs → kv-cache-C4C3LyYW.mjs} +1 -1
- package/dist/{kv-cache-DvqkKNIj.cjs → kv-cache-Dhhhrryv.cjs} +1 -1
- package/dist/{ld-DT3e2hOv.mjs → ld-BmFz5NLA.mjs} +3 -3
- package/dist/{metrics-DvlvYpq_.mjs → metrics-Beod6tzO.mjs} +1 -1
- package/dist/{middleware-BJcez_E3.mjs → middleware-B7CNsyqt.mjs} +1 -1
- package/dist/{middleware-CpyCNOW_.mjs → middleware-BKeQYPm0.mjs} +136 -32
- package/dist/{middleware-CCbyUF4c.js → middleware-D_9pNYe9.js} +126 -22
- package/dist/mod.cjs +5 -5
- package/dist/mod.js +5 -5
- package/dist/nodeinfo/handler.test.mjs +1 -1
- package/dist/{owner-DCIH7Iq0.cjs → owner-BQFZtFV4.cjs} +1 -1
- package/dist/{owner-CDsVeZI7.mjs → owner-CsKVgMzk.mjs} +3 -3
- package/dist/{owner-DfGz0cXm.js → owner-_pbbgLJK.js} +1 -1
- package/dist/{portable-key-DCyKGCQd.mjs → portable-key-B4R-CiIV.mjs} +2 -2
- package/dist/{proof-CxsVG6vM.mjs → proof-ozG4HfcQ.mjs} +4 -4
- package/dist/{send-BZJ7NsVV.mjs → send-ClPt8nYu.mjs} +3 -3
- package/dist/sig/compound-proof-discovery.test.mjs +1 -1
- package/dist/sig/compound-proof-producer.test.mjs +1 -1
- package/dist/sig/compound-proof-verification.test.mjs +2 -2
- package/dist/sig/compound-proof.test.mjs +2 -2
- package/dist/sig/http.test.mjs +2 -2
- package/dist/sig/key.test.mjs +1 -1
- package/dist/sig/ld.test.mjs +2 -2
- package/dist/sig/mod.cjs +3 -3
- package/dist/sig/mod.js +3 -3
- package/dist/sig/owner.test.mjs +1 -1
- package/dist/sig/portable-collection.test.mjs +2 -2
- package/dist/sig/portable-key.test.mjs +3 -3
- package/dist/sig/proof.test.mjs +3 -3
- package/dist/{sig-Cs9_ecdh.js → sig-DhdomAN0.js} +2 -2
- package/dist/{sig-C6TnADze.cjs → sig-k3C8sF7g.cjs} +2 -2
- package/dist/{temporal-CLzSfIOV.mjs → temporal-Ba0EAZAI.mjs} +1 -1
- package/dist/testing/kv.test.mjs +1 -1
- package/dist/utils/docloader.test.mjs +2 -2
- package/dist/utils/kv-cache.test.mjs +1 -1
- package/dist/utils/mod.cjs +1 -1
- package/dist/utils/mod.js +1 -1
- package/package.json +7 -7
|
@@ -8,11 +8,11 @@ import { t as assertFalse } from "../assert_false-CPdRnc3E.mjs";
|
|
|
8
8
|
import { t as assertThrows } from "../assert_throws-BYITOvPO.mjs";
|
|
9
9
|
import "../std__assert-DvMDRPes.mjs";
|
|
10
10
|
import { t as assert } from "../assert-D1Eafmd_.mjs";
|
|
11
|
-
import { p as signJsonLd } from "../ld-
|
|
12
|
-
import { r as signObject } from "../proof-
|
|
13
|
-
import { s as signRequest } from "../http-
|
|
11
|
+
import { p as signJsonLd } from "../ld-BmFz5NLA.mjs";
|
|
12
|
+
import { r as signObject } from "../proof-ozG4HfcQ.mjs";
|
|
13
|
+
import { s as signRequest, u as verifyRequestDetailed } from "../http-B0VRNipE.mjs";
|
|
14
14
|
import { a as rsaPrivateKey3, c as rsaPublicKey3, i as rsaPrivateKey2, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2 } from "../keys-DIJNHW-Y.mjs";
|
|
15
|
-
import { c as forwardPortableInboxActivity, l as resolvePortableInboxForwardingOptions, o as createFederation } from "../middleware-
|
|
15
|
+
import { c as forwardPortableInboxActivity, l as resolvePortableInboxForwardingOptions, o as createFederation } from "../middleware-BKeQYPm0.mjs";
|
|
16
16
|
import { Create, Follow, Note, Person, Tombstone } from "@fedify/vocab";
|
|
17
17
|
import { mockDocumentLoader, test } from "@fedify/fixture";
|
|
18
18
|
import { exportDidKey, formatIri, parseIri } from "@fedify/vocab-runtime";
|
|
@@ -56,7 +56,7 @@ function setup({ gateways = [
|
|
|
56
56
|
LOCAL,
|
|
57
57
|
GATEWAY2,
|
|
58
58
|
GATEWAY3
|
|
59
|
-
], kv = new MemoryKvStore(), queue, options = {}, actor, listenerError = false, keyPairs = true, onSharedInboxKey } = {}) {
|
|
59
|
+
], kv = new MemoryKvStore(), queue, options = {}, actor, listenerError = false, keyPairs = true, aliceKeyPairs = [], mapPortableActorId, onSharedInboxKey } = {}) {
|
|
60
60
|
const federation = createFederation({
|
|
61
61
|
kv,
|
|
62
62
|
...options.allowPrivateAddress ? {} : {
|
|
@@ -83,7 +83,8 @@ function setup({ gateways = [
|
|
|
83
83
|
if (keyPairs) actorCallbacks.setKeyPairsDispatcher((_ctx, identifier) => identifier === "ordinary" ? [{
|
|
84
84
|
privateKey: rsaPrivateKey2,
|
|
85
85
|
publicKey: rsaPublicKey2.publicKey
|
|
86
|
-
}] : []);
|
|
86
|
+
}] : identifier === "alice" ? aliceKeyPairs : []);
|
|
87
|
+
if (mapPortableActorId != null) actorCallbacks.mapPortableActorId((_ctx, identifier) => mapPortableActorId(identifier));
|
|
87
88
|
const inboxListeners = federation.setInboxListeners("/users/{identifier}/inbox", "/inbox");
|
|
88
89
|
if (onSharedInboxKey != null) inboxListeners.setSharedKeyDispatcher(() => {
|
|
89
90
|
onSharedInboxKey();
|
|
@@ -494,6 +495,7 @@ test("forwardPortableInboxActivity() does not wait for slow gateways", async ()
|
|
|
494
495
|
const activityId = parseIri(`ap+ef61://${did}/follows/slow`);
|
|
495
496
|
const parameters = {
|
|
496
497
|
recipient: {
|
|
498
|
+
actorId: parseIri(`ap+ef61://${did}/users/alice`),
|
|
497
499
|
inboxId: parseIri(`ap+ef61://${did}/users/alice/inbox`),
|
|
498
500
|
canonicalInboxId: `ap://${did}/users/alice/inbox`,
|
|
499
501
|
gateways: [new URL(LOCAL), new URL(GATEWAY2)]
|
|
@@ -520,6 +522,399 @@ test("forwardPortableInboxActivity() does not wait for slow gateways", async ()
|
|
|
520
522
|
esm_default.hardReset();
|
|
521
523
|
}
|
|
522
524
|
});
|
|
525
|
+
const aliceId = parseIri(`ap+ef61://${did}/users/alice`);
|
|
526
|
+
const rsaGatewayKeyPair = await crypto.subtle.generateKey({
|
|
527
|
+
name: "RSASSA-PKCS1-v1_5",
|
|
528
|
+
modulusLength: 2048,
|
|
529
|
+
publicExponent: new Uint8Array([
|
|
530
|
+
1,
|
|
531
|
+
0,
|
|
532
|
+
1
|
|
533
|
+
]),
|
|
534
|
+
hash: "SHA-256"
|
|
535
|
+
}, true, ["sign", "verify"]);
|
|
536
|
+
function compatibleAliceId(origin = LOCAL) {
|
|
537
|
+
return `${origin}/.well-known/apgateway/${did}/users/alice`;
|
|
538
|
+
}
|
|
539
|
+
function mapAlice(identifier) {
|
|
540
|
+
return identifier === "alice" ? aliceId : null;
|
|
541
|
+
}
|
|
542
|
+
function signatureKeyId(headers) {
|
|
543
|
+
return headers.get("Signature-Input")?.match(/keyid="([^"]+)"/)?.[1] ?? headers.get("Signature")?.match(/keyId="([^"]+)"/)?.[1];
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Makes a document loader that serves Alice's actor document as the gateway
|
|
547
|
+
* at {@link LOCAL} does: signed by her DID, and embedding the gateway keys in
|
|
548
|
+
* its `assertionMethod`.
|
|
549
|
+
*/
|
|
550
|
+
async function createGatewayLoader(keyPairs) {
|
|
551
|
+
const { federation } = setup({
|
|
552
|
+
aliceKeyPairs: keyPairs,
|
|
553
|
+
mapPortableActorId: mapAlice
|
|
554
|
+
});
|
|
555
|
+
const keys = await federation.createContext(new URL(LOCAL)).getActorKeyPairs("alice");
|
|
556
|
+
const document = await (await signObject(new Person({
|
|
557
|
+
id: aliceId,
|
|
558
|
+
inbox: parseIri(`${aliceId.href}/inbox`),
|
|
559
|
+
gateways: [
|
|
560
|
+
new URL(LOCAL),
|
|
561
|
+
new URL(GATEWAY2),
|
|
562
|
+
new URL(GATEWAY3)
|
|
563
|
+
],
|
|
564
|
+
assertionMethods: keys.map((k) => k.multikey)
|
|
565
|
+
}), ed25519PrivateKey, keyId, { contextLoader: mockDocumentLoader })).toJsonLd({ contextLoader: mockDocumentLoader });
|
|
566
|
+
return (url) => {
|
|
567
|
+
if (url.replace(/#.*$/, "") !== compatibleAliceId()) return mockDocumentLoader(url);
|
|
568
|
+
return Promise.resolve({
|
|
569
|
+
contextUrl: null,
|
|
570
|
+
documentUrl: url,
|
|
571
|
+
document: structuredClone(document)
|
|
572
|
+
});
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
function toRequest({ url, headers, body }) {
|
|
576
|
+
return new Request(url, {
|
|
577
|
+
method: "POST",
|
|
578
|
+
headers,
|
|
579
|
+
body
|
|
580
|
+
});
|
|
581
|
+
}
|
|
582
|
+
async function assertSignedBy(request, expectedKeyId, documentLoader) {
|
|
583
|
+
assertEquals(signatureKeyId(request.headers), expectedKeyId);
|
|
584
|
+
const result = await verifyRequestDetailed(toRequest(request), {
|
|
585
|
+
documentLoader,
|
|
586
|
+
contextLoader: mockDocumentLoader
|
|
587
|
+
});
|
|
588
|
+
assert(result.verified, JSON.stringify(result));
|
|
589
|
+
assertEquals(result.key.id?.href, expectedKeyId);
|
|
590
|
+
}
|
|
591
|
+
/** Records the requests made to the gateways other than {@link LOCAL}. */
|
|
592
|
+
function captureGatewayRequests(respond = () => new Response(null, { status: 202 })) {
|
|
593
|
+
const requests = [];
|
|
594
|
+
esm_default.spyGlobal();
|
|
595
|
+
for (const gateway of [GATEWAY2, GATEWAY3]) esm_default.post(`begin:${gateway}/`, async (callLog) => {
|
|
596
|
+
const request = callLog.request;
|
|
597
|
+
const captured = {
|
|
598
|
+
url: request.url,
|
|
599
|
+
headers: new Headers(request.headers),
|
|
600
|
+
body: await request.text()
|
|
601
|
+
};
|
|
602
|
+
requests.push(captured);
|
|
603
|
+
return respond(captured);
|
|
604
|
+
});
|
|
605
|
+
return requests;
|
|
606
|
+
}
|
|
607
|
+
test("Federation.fetch() signs forwarded portable inbox deliveries with gateway keys", async (t) => {
|
|
608
|
+
const keyOrders = [[
|
|
609
|
+
"RSA key first",
|
|
610
|
+
[rsaGatewayKeyPair, otherKeyPair],
|
|
611
|
+
"#main-key"
|
|
612
|
+
], [
|
|
613
|
+
"Ed25519 key first",
|
|
614
|
+
[otherKeyPair, rsaGatewayKeyPair],
|
|
615
|
+
"#key-2"
|
|
616
|
+
]];
|
|
617
|
+
for (const [name, aliceKeyPairs, fragment] of keyOrders) await t.step(name, async () => {
|
|
618
|
+
const requests = captureGatewayRequests();
|
|
619
|
+
try {
|
|
620
|
+
const kv = new MemoryKvStore();
|
|
621
|
+
const { federation, received } = setup({
|
|
622
|
+
kv,
|
|
623
|
+
aliceKeyPairs,
|
|
624
|
+
mapPortableActorId: mapAlice,
|
|
625
|
+
options: { allowPrivateAddress: true }
|
|
626
|
+
});
|
|
627
|
+
const json = await signedFollow();
|
|
628
|
+
assertEquals((await federation.fetch(post(inboxUrl(), json), { contextData: void 0 })).status, 202);
|
|
629
|
+
assertEquals(received.length, 1);
|
|
630
|
+
assertEquals(requests.map((r) => r.url).sort(), [inboxUrl(did, "/users/alice/inbox", GATEWAY3), inboxUrl(did, "/users/alice/inbox", GATEWAY2)]);
|
|
631
|
+
const loader = await createGatewayLoader(aliceKeyPairs);
|
|
632
|
+
for (const request of requests) {
|
|
633
|
+
await assertSignedBy(request, compatibleAliceId() + fragment, loader);
|
|
634
|
+
assertEquals(JSON.parse(request.body), json);
|
|
635
|
+
}
|
|
636
|
+
assertEquals(await kv.get([
|
|
637
|
+
"_fedify",
|
|
638
|
+
"httpMessageSignaturesSpec",
|
|
639
|
+
GATEWAY2
|
|
640
|
+
]), "rfc9421");
|
|
641
|
+
} finally {
|
|
642
|
+
esm_default.hardReset();
|
|
643
|
+
}
|
|
644
|
+
});
|
|
645
|
+
});
|
|
646
|
+
test("Federation.fetch() signs queued forwarded portable inbox deliveries with gateway keys", async () => {
|
|
647
|
+
const queue = new RecordingQueue();
|
|
648
|
+
const aliceKeyPairs = [otherKeyPair, rsaGatewayKeyPair];
|
|
649
|
+
const { federation } = setup({
|
|
650
|
+
queue,
|
|
651
|
+
aliceKeyPairs,
|
|
652
|
+
mapPortableActorId: mapAlice,
|
|
653
|
+
options: { allowPrivateAddress: true }
|
|
654
|
+
});
|
|
655
|
+
assertEquals((await federation.fetch(post(inboxUrl(), await signedFollow()), { contextData: void 0 })).status, 202);
|
|
656
|
+
assertEquals(queue.outbox.length, 2);
|
|
657
|
+
const expectedKeyId = `${compatibleAliceId()}#key-2`;
|
|
658
|
+
for (const message of queue.outbox) {
|
|
659
|
+
assertEquals(message.keys.map((k) => k.keyId), [expectedKeyId]);
|
|
660
|
+
assertEquals(message.keys[0].privateKey.kty, "RSA");
|
|
661
|
+
}
|
|
662
|
+
const requests = captureGatewayRequests();
|
|
663
|
+
try {
|
|
664
|
+
for (const message of queue.outbox) await federation.processQueuedTask(void 0, message);
|
|
665
|
+
} finally {
|
|
666
|
+
esm_default.hardReset();
|
|
667
|
+
}
|
|
668
|
+
assertEquals(requests.length, 2);
|
|
669
|
+
const loader = await createGatewayLoader(aliceKeyPairs);
|
|
670
|
+
for (const request of requests) await assertSignedBy(request, expectedKeyId, loader);
|
|
671
|
+
});
|
|
672
|
+
test("Federation.fetch() forwards portable inbox deliveries unsigned without gateway keys", async (t) => {
|
|
673
|
+
const otherActorId = parseIri(`ap+ef61://${otherDid}/users/alice`);
|
|
674
|
+
const cases = [
|
|
675
|
+
["without a mapper", { aliceKeyPairs: [rsaGatewayKeyPair] }],
|
|
676
|
+
["the mapper returns null", {
|
|
677
|
+
aliceKeyPairs: [rsaGatewayKeyPair],
|
|
678
|
+
mapPortableActorId: () => null
|
|
679
|
+
}],
|
|
680
|
+
["the mapper returns another actor", {
|
|
681
|
+
aliceKeyPairs: [rsaGatewayKeyPair],
|
|
682
|
+
mapPortableActorId: () => otherActorId
|
|
683
|
+
}],
|
|
684
|
+
["the mapper returns another actor of the same DID", {
|
|
685
|
+
aliceKeyPairs: [rsaGatewayKeyPair],
|
|
686
|
+
mapPortableActorId: () => parseIri(`ap+ef61://${did}/users/bob`)
|
|
687
|
+
}],
|
|
688
|
+
["without a key pairs dispatcher", {
|
|
689
|
+
keyPairs: false,
|
|
690
|
+
mapPortableActorId: mapAlice
|
|
691
|
+
}],
|
|
692
|
+
["without key pairs", { mapPortableActorId: mapAlice }],
|
|
693
|
+
["without an RSA key", {
|
|
694
|
+
aliceKeyPairs: [otherKeyPair],
|
|
695
|
+
mapPortableActorId: mapAlice
|
|
696
|
+
}]
|
|
697
|
+
];
|
|
698
|
+
for (const [name, options] of cases) await t.step(name, async () => {
|
|
699
|
+
const queue = new RecordingQueue();
|
|
700
|
+
const { federation, received } = setup({
|
|
701
|
+
...options,
|
|
702
|
+
queue
|
|
703
|
+
});
|
|
704
|
+
assertEquals((await federation.fetch(post(inboxUrl(), await signedFollow()), { contextData: void 0 })).status, 202);
|
|
705
|
+
assertEquals(received.length, 1);
|
|
706
|
+
assertEquals(queue.outbox.length, 2);
|
|
707
|
+
for (const message of queue.outbox) assertEquals(message.keys, []);
|
|
708
|
+
});
|
|
709
|
+
});
|
|
710
|
+
test("Federation.fetch() signs forwarded portable inbox deliveries if the mapper returns an equivalent ID", async (t) => {
|
|
711
|
+
const ids = [
|
|
712
|
+
["ap: URI", parseIri(`ap://${did}/users/alice`)],
|
|
713
|
+
["percent-encoded DID", parseIri(`ap+ef61://${did.replaceAll(":", "%3A")}/users/alice`)],
|
|
714
|
+
["compatible identifier", new URL(compatibleAliceId(GATEWAY2))]
|
|
715
|
+
];
|
|
716
|
+
for (const [name, id] of ids) await t.step(name, async () => {
|
|
717
|
+
const queue = new RecordingQueue();
|
|
718
|
+
const { federation } = setup({
|
|
719
|
+
queue,
|
|
720
|
+
aliceKeyPairs: [rsaGatewayKeyPair],
|
|
721
|
+
mapPortableActorId: (identifier) => identifier === "alice" ? id : null
|
|
722
|
+
});
|
|
723
|
+
assertEquals((await federation.fetch(post(inboxUrl(), await signedFollow()), { contextData: void 0 })).status, 202);
|
|
724
|
+
assertEquals(queue.outbox.length, 2);
|
|
725
|
+
for (const message of queue.outbox) assertEquals(message.keys.map((k) => k.keyId), [`${compatibleAliceId()}#main-key`]);
|
|
726
|
+
});
|
|
727
|
+
});
|
|
728
|
+
test("Federation.fetch() signs forwarded portable inbox deliveries with the gateway keys of the canonical origin", async () => {
|
|
729
|
+
const queue = new RecordingQueue();
|
|
730
|
+
const { federation } = setup({
|
|
731
|
+
queue,
|
|
732
|
+
gateways: [
|
|
733
|
+
GATEWAY2,
|
|
734
|
+
LOCAL,
|
|
735
|
+
GATEWAY3
|
|
736
|
+
],
|
|
737
|
+
aliceKeyPairs: [rsaGatewayKeyPair],
|
|
738
|
+
mapPortableActorId: mapAlice,
|
|
739
|
+
options: { origin: GATEWAY2 }
|
|
740
|
+
});
|
|
741
|
+
assertEquals((await federation.fetch(post(inboxUrl(), await signedFollow()), { contextData: void 0 })).status, 202);
|
|
742
|
+
assertEquals(queue.outbox.length, 1);
|
|
743
|
+
assertEquals(queue.outbox[0].keys.map((k) => k.keyId), [`${compatibleAliceId(GATEWAY2)}#main-key`]);
|
|
744
|
+
});
|
|
745
|
+
test("Federation.fetch() double-knocks forwarded portable inbox deliveries", async (t) => {
|
|
746
|
+
await t.step("firstKnock", async () => {
|
|
747
|
+
const requests = captureGatewayRequests();
|
|
748
|
+
try {
|
|
749
|
+
const { federation } = setup({
|
|
750
|
+
aliceKeyPairs: [rsaGatewayKeyPair],
|
|
751
|
+
mapPortableActorId: mapAlice,
|
|
752
|
+
options: {
|
|
753
|
+
allowPrivateAddress: true,
|
|
754
|
+
firstKnock: "draft-cavage-http-signatures-12"
|
|
755
|
+
}
|
|
756
|
+
});
|
|
757
|
+
assertEquals((await federation.fetch(post(inboxUrl(), await signedFollow()), { contextData: void 0 })).status, 202);
|
|
758
|
+
assertEquals(requests.length, 2);
|
|
759
|
+
for (const request of requests) {
|
|
760
|
+
assert(request.headers.has("Signature"));
|
|
761
|
+
assertFalse(request.headers.has("Signature-Input"));
|
|
762
|
+
}
|
|
763
|
+
} finally {
|
|
764
|
+
esm_default.hardReset();
|
|
765
|
+
}
|
|
766
|
+
});
|
|
767
|
+
await t.step("falls back to the other spec", async () => {
|
|
768
|
+
const requests = captureGatewayRequests((request) => new Response(null, { status: request.headers.has("Signature-Input") ? 401 : 202 }));
|
|
769
|
+
try {
|
|
770
|
+
const kv = new MemoryKvStore();
|
|
771
|
+
const { federation } = setup({
|
|
772
|
+
kv,
|
|
773
|
+
aliceKeyPairs: [rsaGatewayKeyPair],
|
|
774
|
+
mapPortableActorId: mapAlice,
|
|
775
|
+
options: { allowPrivateAddress: true }
|
|
776
|
+
});
|
|
777
|
+
assertEquals((await federation.fetch(post(inboxUrl(), await signedFollow()), { contextData: void 0 })).status, 202);
|
|
778
|
+
assertEquals(requests.length, 4);
|
|
779
|
+
const loader = await createGatewayLoader([rsaGatewayKeyPair]);
|
|
780
|
+
for (const request of requests) {
|
|
781
|
+
if (request.headers.has("Signature-Input")) continue;
|
|
782
|
+
await assertSignedBy(request, `${compatibleAliceId()}#main-key`, loader);
|
|
783
|
+
}
|
|
784
|
+
assertEquals(await kv.get([
|
|
785
|
+
"_fedify",
|
|
786
|
+
"httpMessageSignaturesSpec",
|
|
787
|
+
GATEWAY2
|
|
788
|
+
]), "draft-cavage-http-signatures-12");
|
|
789
|
+
} finally {
|
|
790
|
+
esm_default.hardReset();
|
|
791
|
+
}
|
|
792
|
+
});
|
|
793
|
+
});
|
|
794
|
+
test("A Fedify gateway accepts signed forwarded portable inbox deliveries", async () => {
|
|
795
|
+
const requests = captureGatewayRequests();
|
|
796
|
+
let forwarded;
|
|
797
|
+
try {
|
|
798
|
+
const { federation } = setup({
|
|
799
|
+
aliceKeyPairs: [rsaGatewayKeyPair],
|
|
800
|
+
mapPortableActorId: mapAlice,
|
|
801
|
+
options: { allowPrivateAddress: true }
|
|
802
|
+
});
|
|
803
|
+
assertEquals((await federation.fetch(post(inboxUrl(), await signedFollow()), { contextData: void 0 })).status, 202);
|
|
804
|
+
forwarded = requests.find((r) => r.url.startsWith(`${GATEWAY2}/`));
|
|
805
|
+
} finally {
|
|
806
|
+
esm_default.hardReset();
|
|
807
|
+
}
|
|
808
|
+
await assertSignedBy(forwarded, `${compatibleAliceId()}#main-key`, await createGatewayLoader([rsaGatewayKeyPair]));
|
|
809
|
+
const queue = new RecordingQueue();
|
|
810
|
+
const { federation, received } = setup({
|
|
811
|
+
queue,
|
|
812
|
+
options: { origin: GATEWAY2 }
|
|
813
|
+
});
|
|
814
|
+
assertEquals((await federation.fetch(toRequest(forwarded), { contextData: void 0 })).status, 202);
|
|
815
|
+
assertEquals(received.length, 1);
|
|
816
|
+
assertEquals(received[0].recipient, "alice");
|
|
817
|
+
assertEquals(queue.outbox.map((m) => m.inbox).sort(), [inboxUrl(did, "/users/alice/inbox", LOCAL), inboxUrl(did, "/users/alice/inbox", GATEWAY3)]);
|
|
818
|
+
});
|
|
819
|
+
function forwardingParameters(overrides = {}) {
|
|
820
|
+
const activityId = parseIri(`ap+ef61://${did}/follows/${++activityCounter}`);
|
|
821
|
+
return {
|
|
822
|
+
recipient: {
|
|
823
|
+
actorId: aliceId,
|
|
824
|
+
inboxId: parseIri(`ap+ef61://${did}/users/alice/inbox`),
|
|
825
|
+
canonicalInboxId: `ap://${did}/users/alice/inbox`,
|
|
826
|
+
gateways: [new URL(LOCAL), new URL(GATEWAY2)]
|
|
827
|
+
},
|
|
828
|
+
activity: { id: formatIri(activityId) },
|
|
829
|
+
activityId,
|
|
830
|
+
activityType: "https://www.w3.org/ns/activitystreams#Follow",
|
|
831
|
+
excludedOrigins: [LOCAL],
|
|
832
|
+
baseUrl: LOCAL,
|
|
833
|
+
kv: new MemoryKvStore(),
|
|
834
|
+
kvPrefix: ["_fedify", "portableInboxForwarding"],
|
|
835
|
+
allowPrivateAddress: true,
|
|
836
|
+
...overrides
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
const gatewayKey = {
|
|
840
|
+
keyId: new URL(`${compatibleAliceId()}#main-key`),
|
|
841
|
+
privateKey: rsaGatewayKeyPair.privateKey
|
|
842
|
+
};
|
|
843
|
+
test("forwardPortableInboxActivity() gets keys only when it forwards", async () => {
|
|
844
|
+
let calls = 0;
|
|
845
|
+
const parameters = forwardingParameters({
|
|
846
|
+
outboxQueue: new RecordingQueue(),
|
|
847
|
+
getKeys: () => {
|
|
848
|
+
calls++;
|
|
849
|
+
return Promise.resolve([gatewayKey]);
|
|
850
|
+
}
|
|
851
|
+
});
|
|
852
|
+
assertEquals((await forwardPortableInboxActivity(parameters)).length, 1);
|
|
853
|
+
assertEquals(calls, 1);
|
|
854
|
+
assertEquals(await forwardPortableInboxActivity(parameters), []);
|
|
855
|
+
assertEquals(calls, 1);
|
|
856
|
+
});
|
|
857
|
+
test("forwardPortableInboxActivity() forwards unsigned if it cannot get keys", async (t) => {
|
|
858
|
+
const nonExtractable = await crypto.subtle.generateKey({
|
|
859
|
+
name: "RSASSA-PKCS1-v1_5",
|
|
860
|
+
modulusLength: 2048,
|
|
861
|
+
publicExponent: new Uint8Array([
|
|
862
|
+
1,
|
|
863
|
+
0,
|
|
864
|
+
1
|
|
865
|
+
]),
|
|
866
|
+
hash: "SHA-256"
|
|
867
|
+
}, false, ["sign", "verify"]);
|
|
868
|
+
const getKeysCases = [["getKeys() rejects", () => Promise.reject(/* @__PURE__ */ new Error("Unavailable."))], ["the key is not extractable", () => Promise.resolve([{
|
|
869
|
+
keyId: gatewayKey.keyId,
|
|
870
|
+
privateKey: nonExtractable.privateKey
|
|
871
|
+
}])]];
|
|
872
|
+
for (const [name, getKeys] of getKeysCases) {
|
|
873
|
+
await t.step(`${name} (queued)`, async () => {
|
|
874
|
+
const queue = new RecordingQueue();
|
|
875
|
+
assertEquals((await forwardPortableInboxActivity(forwardingParameters({
|
|
876
|
+
outboxQueue: queue,
|
|
877
|
+
getKeys
|
|
878
|
+
}))).length, 1);
|
|
879
|
+
assertEquals(queue.outbox.length, 1);
|
|
880
|
+
assertEquals(queue.outbox[0].keys, []);
|
|
881
|
+
});
|
|
882
|
+
await t.step(`${name} (immediate)`, async () => {
|
|
883
|
+
const requests = captureGatewayRequests();
|
|
884
|
+
try {
|
|
885
|
+
assertEquals((await forwardPortableInboxActivity(forwardingParameters({ getKeys }))).length, 1);
|
|
886
|
+
} finally {
|
|
887
|
+
esm_default.hardReset();
|
|
888
|
+
}
|
|
889
|
+
assertEquals(requests.length, 1);
|
|
890
|
+
assertEquals(signatureKeyId(requests[0].headers), void 0);
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
});
|
|
894
|
+
test("forwardPortableInboxActivity() does not wait for slow keys", async () => {
|
|
895
|
+
let release = () => {};
|
|
896
|
+
const pending = new Promise((resolve) => release = resolve);
|
|
897
|
+
const requests = captureGatewayRequests();
|
|
898
|
+
try {
|
|
899
|
+
const started = performance.now();
|
|
900
|
+
const forwarded = await forwardPortableInboxActivity(forwardingParameters({
|
|
901
|
+
options: resolvePortableInboxForwardingOptions({ deadline: { milliseconds: 10 } }),
|
|
902
|
+
getKeys: async () => {
|
|
903
|
+
await pending;
|
|
904
|
+
throw new Error("Unavailable.");
|
|
905
|
+
}
|
|
906
|
+
}));
|
|
907
|
+
assert(performance.now() - started < 5e3);
|
|
908
|
+
assertEquals(forwarded.length, 1);
|
|
909
|
+
assertEquals(requests.length, 0);
|
|
910
|
+
release();
|
|
911
|
+
for (let i = 0; i < 100 && requests.length < 1; i++) await new Promise((resolve) => setTimeout(resolve, 10));
|
|
912
|
+
assertEquals(requests.length, 1);
|
|
913
|
+
assertEquals(signatureKeyId(requests[0].headers), void 0);
|
|
914
|
+
} finally {
|
|
915
|
+
esm_default.hardReset();
|
|
916
|
+
}
|
|
917
|
+
});
|
|
523
918
|
test("Context.getPortableInboxUri()", async (t) => {
|
|
524
919
|
const { federation } = setup();
|
|
525
920
|
await t.step("Context", () => {
|
|
@@ -7,10 +7,10 @@ import { t as assertInstanceOf } from "../assert_instance_of-mrXWWNY-.mjs";
|
|
|
7
7
|
import { t as assertThrows } from "../assert_throws-BYITOvPO.mjs";
|
|
8
8
|
import "../std__assert-DvMDRPes.mjs";
|
|
9
9
|
import { t as assertStrictEquals } from "../assert_strict_equals-CYvBmkpC.mjs";
|
|
10
|
-
import { o as verifyPortableObjectProof, r as signObject } from "../proof-
|
|
11
|
-
import { s as signRequest } from "../http-
|
|
10
|
+
import { o as verifyPortableObjectProof, r as signObject } from "../proof-ozG4HfcQ.mjs";
|
|
11
|
+
import { s as signRequest } from "../http-B0VRNipE.mjs";
|
|
12
12
|
import { a as rsaPrivateKey3, c as rsaPublicKey3, i as rsaPrivateKey2, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2 } from "../keys-DIJNHW-Y.mjs";
|
|
13
|
-
import { o as createFederation, v as PORTABLE_OBJECT_CONTENT_TYPE } from "../middleware-
|
|
13
|
+
import { o as createFederation, v as PORTABLE_OBJECT_CONTENT_TYPE } from "../middleware-BKeQYPm0.mjs";
|
|
14
14
|
import { Collection, Create, Note, Object as Object$1, Person, Tombstone, lookupObject } from "@fedify/vocab";
|
|
15
15
|
import { mockDocumentLoader, test } from "@fedify/fixture";
|
|
16
16
|
import { exportDidKey, formatIri, parseIri } from "@fedify/vocab-runtime";
|
|
@@ -10,10 +10,10 @@ import { t as assertNotEquals } from "../assert_not_equals-CpPK2t89.mjs";
|
|
|
10
10
|
import "../std__assert-DvMDRPes.mjs";
|
|
11
11
|
import { t as assertRejects } from "../assert_rejects-CTptdYck.mjs";
|
|
12
12
|
import { t as assert } from "../assert-D1Eafmd_.mjs";
|
|
13
|
-
import { l as verifyRequest } from "../http-
|
|
13
|
+
import { l as verifyRequest } from "../http-B0VRNipE.mjs";
|
|
14
14
|
import { i as rsaPrivateKey2, n as ed25519PrivateKey, s as rsaPublicKey2, t as ed25519Multikey } from "../keys-DIJNHW-Y.mjs";
|
|
15
|
-
import { t as doesActorOwnKey } from "../owner-
|
|
16
|
-
import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "../send-
|
|
15
|
+
import { t as doesActorOwnKey } from "../owner-CsKVgMzk.mjs";
|
|
16
|
+
import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "../send-ClPt8nYu.mjs";
|
|
17
17
|
import { Activity, Application, Endpoints, Group, Person, Service } from "@fedify/vocab";
|
|
18
18
|
import { createTestMeterProvider, createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
|
|
19
19
|
import { FetchError, UrlError } from "@fedify/vocab-runtime";
|
|
@@ -4,7 +4,7 @@ globalThis.addEventListener = () => {};
|
|
|
4
4
|
import { t as MemoryKvStore } from "../../kv-DCHkaJ9-.mjs";
|
|
5
5
|
import { a as numberSchema, i as makeSchema, n as baseOptions, o as stringSchema, t as MockQueue } from "../../tasks-dbCShlCb.mjs";
|
|
6
6
|
import "../../testing-CO_cEvQc.mjs";
|
|
7
|
-
import { o as createFederation } from "../../middleware-
|
|
7
|
+
import { o as createFederation } from "../../middleware-BKeQYPm0.mjs";
|
|
8
8
|
import { n as ParallelMessageQueue } from "../../mq-Dm1Wmtm-.mjs";
|
|
9
9
|
import { configure, reset } from "@logtape/logtape";
|
|
10
10
|
import { delay } from "es-toolkit";
|
|
@@ -4,8 +4,8 @@ globalThis.addEventListener = () => {};
|
|
|
4
4
|
import { t as MemoryKvStore } from "../../kv-DCHkaJ9-.mjs";
|
|
5
5
|
import { a as numberSchema, i as makeSchema, n as baseOptions, o as stringSchema, t as MockQueue } from "../../tasks-dbCShlCb.mjs";
|
|
6
6
|
import "../../testing-CO_cEvQc.mjs";
|
|
7
|
-
import { o as createFederation } from "../../middleware-
|
|
8
|
-
import { r as createFederationBuilder } from "../../builder-
|
|
7
|
+
import { o as createFederation } from "../../middleware-BKeQYPm0.mjs";
|
|
8
|
+
import { r as createFederationBuilder } from "../../builder-BGlpUocK.mjs";
|
|
9
9
|
import { t as TaskCodec } from "../../codec-C6rTrpIp.mjs";
|
|
10
10
|
import { t as InProcessMessageQueue } from "../../mq-Dm1Wmtm-.mjs";
|
|
11
11
|
import { W3CTraceContextPropagator } from "@opentelemetry/core";
|
|
@@ -4,7 +4,7 @@ globalThis.addEventListener = () => {};
|
|
|
4
4
|
import { t as assertFalse } from "../assert_false-CPdRnc3E.mjs";
|
|
5
5
|
import "../std__assert-DvMDRPes.mjs";
|
|
6
6
|
import { t as assert } from "../assert-D1Eafmd_.mjs";
|
|
7
|
-
import { t as hasMalformedKnownTemporalLiteral } from "../temporal-
|
|
7
|
+
import { t as hasMalformedKnownTemporalLiteral } from "../temporal-Ba0EAZAI.mjs";
|
|
8
8
|
import { test } from "@fedify/fixture";
|
|
9
9
|
//#region src/federation/temporal.test.ts
|
|
10
10
|
test("hasMalformedKnownTemporalLiteral() detects expanded proof timestamps", async () => {
|
|
@@ -6,7 +6,7 @@ import { t as MemoryKvStore } from "../kv-DCHkaJ9-.mjs";
|
|
|
6
6
|
import { t as assertEquals } from "../assert_equals-Z6IcEbWU.mjs";
|
|
7
7
|
import { t as assertNotEquals } from "../assert_not_equals-CpPK2t89.mjs";
|
|
8
8
|
import "../std__assert-DvMDRPes.mjs";
|
|
9
|
-
import { o as createFederation, s as handleWebFinger } from "../middleware-
|
|
9
|
+
import { o as createFederation, s as handleWebFinger } from "../middleware-BKeQYPm0.mjs";
|
|
10
10
|
import { Image, Link, Person, Tombstone } from "@fedify/vocab";
|
|
11
11
|
import { createTestMeterProvider, test } from "@fedify/fixture";
|
|
12
12
|
import { parseIri } from "@fedify/vocab-runtime";
|