@fedify/fedify 2.5.0-dev.2448 → 2.5.0-dev.2492
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/dist/{builder-DbC8bn6k.mjs → builder-DphK9v7f.mjs} +2 -2
- package/dist/{client-D-XP_Ccb.d.cts → client-3InDZbba.d.cts} +0 -1
- package/dist/{client-D-XP_Ccb.d.ts → client-3InDZbba.d.ts} +0 -1
- package/dist/{client-CEnNz8O7.mjs → client-5SIFzW3q.mjs} +113 -64
- package/dist/compat/mod.d.cts +1 -1
- package/dist/compat/mod.d.ts +1 -1
- package/dist/compat/transformers.test.mjs +1 -1
- package/dist/{compound-proof-heeXW2_A.mjs → compound-proof-cYtvlTgF.mjs} +2 -2
- package/dist/{context-GlHrzdSP.d.ts → context-DQJE9YfH.d.ts} +29 -3
- package/dist/{context-BazwQrHn.d.cts → context-DSuL3YWx.d.cts} +29 -3
- package/dist/{deno-Dpewt_jJ.mjs → deno-iOAA7iRN.mjs} +1 -1
- package/dist/docloader-DCZkiTeF.mjs +136 -0
- package/dist/{exporter-DThFfe7P.mjs → exporter-DVG68ozH.mjs} +36 -30
- package/dist/federation/builder.test.mjs +1 -1
- package/dist/federation/compound-fanout.test.mjs +6 -3
- package/dist/federation/handler-compound-proof.test.mjs +3 -3
- 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/inbox-actorless.test.mjs +5 -5
- package/dist/federation/inbox-report.test.mjs +5 -5
- package/dist/federation/inbox-view.test.mjs +1 -1
- package/dist/federation/inbox.test.mjs +1 -1
- package/dist/federation/metrics.test.mjs +1 -1
- package/dist/federation/middleware.test.mjs +468 -33
- package/dist/federation/mod.cjs +1 -1
- package/dist/federation/mod.d.cts +3 -3
- package/dist/federation/mod.d.ts +3 -3
- package/dist/federation/mod.js +1 -1
- package/dist/federation/mq.test.mjs +1 -1
- package/dist/federation/outgoing-proof.test.mjs +9 -6
- package/dist/federation/portable-actor.test.mjs +3 -3
- package/dist/federation/portable-chain.test.mjs +3 -3
- package/dist/federation/portable-collection.test.mjs +4 -4
- package/dist/federation/portable-delivery.test.mjs +2 -2
- package/dist/federation/portable-gateway-keys.test.mjs +11 -5
- package/dist/federation/portable-inbox.test.mjs +4 -4
- package/dist/federation/portable-parse-uri.test.mjs +1 -1
- package/dist/federation/portable-route-activity.test.mjs +2 -2
- package/dist/federation/portable.test.mjs +3 -3
- package/dist/federation/send.test.mjs +502 -93
- 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-D8YexkP1.cjs → federation-D0AZw_aw.cjs} +246 -108
- package/dist/{http-BZkyQIkb.mjs → http-BoJCMnjY.mjs} +204 -106
- package/dist/{http-BBr-VLil.js → http-ChXUU_3c.js} +178 -109
- package/dist/{http-CAl7Num5.cjs → http-kv1LZw-r.cjs} +179 -110
- package/dist/{inbox-wDuMKaFW.mjs → inbox-BjihTKLo.mjs} +2 -2
- package/dist/{inbox-view-NE9wXdLo.mjs → inbox-view-D-skypg-.mjs} +1 -1
- package/dist/{key-nYGY4QB9.d.cts → key-DbLNT-vA.d.ts} +1 -2
- package/dist/{key-DhsBxxZN.d.ts → key-DlHT4R6k.d.cts} +1 -2
- package/dist/{kv-cache-CTQSfkmp.mjs → kv-cache-Cmsyke4d.mjs} +1 -1
- package/dist/{kv-cache-U7uz5nx4.js → kv-cache-DQcte7ES.js} +73 -28
- package/dist/{kv-cache-CjgCOz3i.cjs → kv-cache-Ddh5sMxH.cjs} +72 -27
- package/dist/{ld-Cel1RRZ5.d.cts → ld-CWVpo_NM.d.cts} +1 -1
- package/dist/{ld-CQFuiah9.mjs → ld-CdvcOEXQ.mjs} +2 -2
- package/dist/{ld-CcuZ4NZa.d.ts → ld-Inagww4R.d.ts} +1 -1
- package/dist/{metrics-B-QxRSSR.mjs → metrics-CpxvUrBz.mjs} +1 -1
- package/dist/{middleware-P-AZEQMn.js → middleware-BHb5toqA.js} +247 -109
- package/dist/{middleware-BdlNa8BD.mjs → middleware-BKs2DLUF.mjs} +1 -1
- package/dist/{middleware-CuVa0UuS.mjs → middleware-CoBLrTKX.mjs} +89 -49
- package/dist/{mod-DvWOv2Ho.d.cts → mod-6TQ4UQZb.d.cts} +1 -1
- package/dist/{mod-BHQE-7WQ.d.ts → mod-B4kBw1-8.d.ts} +1 -1
- package/dist/{mod-DkhfZC1u.d.ts → mod-B7j30IQf.d.ts} +2 -9
- package/dist/{mod-B2783_3v.d.cts → mod-C1nT74IG.d.cts} +1 -1
- package/dist/{mod-DjZfJj9R.d.ts → mod-CIeBgsOC.d.ts} +1 -1
- package/dist/{mod-B5-wdzmJ.d.cts → mod-YvA4Rk8J.d.cts} +2 -9
- package/dist/mod.cjs +5 -5
- package/dist/mod.d.cts +7 -7
- package/dist/mod.d.ts +7 -7
- package/dist/mod.js +5 -5
- package/dist/nodeinfo/client.test.mjs +337 -19
- package/dist/nodeinfo/handler.test.mjs +1 -1
- package/dist/nodeinfo/mod.cjs +1 -1
- package/dist/nodeinfo/mod.d.cts +1 -1
- package/dist/nodeinfo/mod.d.ts +1 -1
- package/dist/nodeinfo/mod.js +1 -1
- package/dist/otel/exporter.test.mjs +257 -1
- package/dist/otel/mod.cjs +36 -30
- package/dist/otel/mod.d.cts +13 -0
- package/dist/otel/mod.d.ts +13 -0
- package/dist/otel/mod.js +36 -30
- package/dist/{owner-ccKkQOAf.mjs → owner-DTnVpUKA.mjs} +3 -3
- package/dist/{portable-collection-CndnpXfo.mjs → portable-collection-CJeYTL61.mjs} +2 -2
- package/dist/{portable-collection-TC-kvkVI.cjs → portable-collection-DciwBIia.cjs} +1 -1
- package/dist/{portable-collection-DUlQ3s2M.js → portable-collection-TmtpzD4E.js} +1 -1
- package/dist/{portable-key-D5tFOoV-.mjs → portable-key-DcYok4ki.mjs} +1 -1
- package/dist/{proof-DPoF06nz.mjs → proof-GH7Poos_.mjs} +3 -3
- package/dist/{send-CF44O1Df.mjs → send-BgGJh6pv.mjs} +174 -76
- 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 +553 -2
- package/dist/sig/key.test.mjs +1 -1
- package/dist/sig/ld.test.mjs +1 -1
- package/dist/sig/mitra-receive-interop.test.mjs +4 -4
- package/dist/sig/mitra-send-interop.test.mjs +3 -3
- 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 +1 -1
- package/dist/sig/portable-collection.test.mjs +1 -1
- package/dist/sig/portable-interop.test.mjs +3 -3
- package/dist/sig/portable-key.test.mjs +5 -5
- package/dist/sig/proof.test.mjs +4 -4
- package/dist/sig/tootik-request-interop.test.mjs +4 -4
- package/dist/sig/tootik-send-interop.test.mjs +3 -3
- package/dist/sig/verification.test.mjs +3 -3
- package/dist/{temporal-XlZ4Wb94.mjs → temporal-gJINzcL4.mjs} +1 -1
- package/dist/testing/kv.test.mjs +1 -1
- package/dist/testing/mod.d.mts +26 -0
- package/dist/{types-_S2_MeLF.js → types-DUjZb4i6.js} +112 -63
- package/dist/{types-poIao40f.cjs → types-hKCCjii0.cjs} +111 -62
- package/dist/utils/docloader.test.mjs +344 -36
- 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 +8 -8
- package/dist/docloader-CpsDyqVN.mjs +0 -91
|
@@ -16,14 +16,14 @@ import "../std__assert-DvMDRPes.mjs";
|
|
|
16
16
|
import { t as assertRejects } from "../assert_rejects-CTptdYck.mjs";
|
|
17
17
|
import { t as assertStrictEquals } from "../assert_strict_equals-CYvBmkpC.mjs";
|
|
18
18
|
import { t as assert } from "../assert-D1Eafmd_.mjs";
|
|
19
|
-
import { g as recordInboxActivity } from "../metrics-
|
|
20
|
-
import { _ as verifyJsonLd, a as compactJsonLd, m as signJsonLd, s as detachSignature } from "../ld-
|
|
21
|
-
import { l as verifyObject, s as signObject } from "../proof-
|
|
22
|
-
import {
|
|
19
|
+
import { g as recordInboxActivity } from "../metrics-CpxvUrBz.mjs";
|
|
20
|
+
import { _ as verifyJsonLd, a as compactJsonLd, m as signJsonLd, s as detachSignature } from "../ld-CdvcOEXQ.mjs";
|
|
21
|
+
import { l as verifyObject, s as signObject } from "../proof-GH7Poos_.mjs";
|
|
22
|
+
import { p as verifyRequest, u as signRequest } from "../http-BoJCMnjY.mjs";
|
|
23
23
|
import { a as rsaPrivateKey3, c as rsaPublicKey3, i as rsaPrivateKey2, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2, t as ed25519Multikey } from "../keys-DIJNHW-Y.mjs";
|
|
24
|
-
import {
|
|
25
|
-
import { T as handleBenchmarkTrigger, i as KvSpecDeterminer, n as FederationImpl, o as createFederation, r as InboxContextImpl, t as ContextImpl } from "../middleware-
|
|
26
|
-
import { t as doesActorOwnKey } from "../owner-
|
|
24
|
+
import { n as getAuthenticatedDocumentLoader } from "../docloader-DCZkiTeF.mjs";
|
|
25
|
+
import { T as handleBenchmarkTrigger, i as KvSpecDeterminer, n as FederationImpl, o as createFederation, r as InboxContextImpl, t as ContextImpl } from "../middleware-CoBLrTKX.mjs";
|
|
26
|
+
import { t as doesActorOwnKey } from "../owner-DTnVpUKA.mjs";
|
|
27
27
|
import { t as CircuitBreaker } from "../circuit-breaker-XJ53md4S.mjs";
|
|
28
28
|
import { t as TaskCodec } from "../codec-C6rTrpIp.mjs";
|
|
29
29
|
import { t as markCircuitBreakerLegacySweepDone } from "../circuit-breaker-test-utils-DW6YC_Pe.mjs";
|
|
@@ -34,11 +34,11 @@ import { Create, CryptographicKey, Note, Offer, Person, getTypeId, lookupObject
|
|
|
34
34
|
import { SpanStatusCode, metrics } from "@opentelemetry/api";
|
|
35
35
|
import { FetchError, UrlError, getDocumentLoader } from "@fedify/vocab-runtime";
|
|
36
36
|
import { createTestMeterProvider, createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
|
|
37
|
-
import dns from "node:dns/promises";
|
|
38
37
|
import { deepStrictEqual, ok, rejects, strictEqual, throws } from "node:assert/strict";
|
|
39
38
|
import serialize from "json-canon";
|
|
40
39
|
import { DataPointType, MeterProvider as MeterProvider$1, MetricReader } from "@opentelemetry/sdk-metrics";
|
|
41
40
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
41
|
+
import dns from "node:dns/promises";
|
|
42
42
|
//#region ../fixture/src/fixtures/example.com/create.json
|
|
43
43
|
var create_default = {
|
|
44
44
|
"@context": "https://www.w3.org/ns/activitystreams",
|
|
@@ -102,7 +102,7 @@ var person2_default = {
|
|
|
102
102
|
};
|
|
103
103
|
//#endregion
|
|
104
104
|
//#region src/federation/middleware.test.ts
|
|
105
|
-
const documentLoader = getDocumentLoader();
|
|
105
|
+
const documentLoader = getDocumentLoader({ allowPrivateAddress: true });
|
|
106
106
|
let logtapeLock = Promise.resolve();
|
|
107
107
|
async function withLogtapeLock(fn) {
|
|
108
108
|
const run = logtapeLock.then(fn, fn);
|
|
@@ -657,7 +657,8 @@ test({
|
|
|
657
657
|
const federation = createFederation({
|
|
658
658
|
kv,
|
|
659
659
|
documentLoaderFactory: () => rejectingLoader,
|
|
660
|
-
contextLoaderFactory: () => mockDocumentLoader
|
|
660
|
+
contextLoaderFactory: () => mockDocumentLoader,
|
|
661
|
+
authenticatedDocumentLoaderFactory: (identity) => getAuthenticatedDocumentLoader(identity, { allowPrivateAddress: true })
|
|
661
662
|
});
|
|
662
663
|
let ctx = federation.createContext(new URL("https://example.com:1234/"), 123);
|
|
663
664
|
assertEquals(ctx.data, 123);
|
|
@@ -768,7 +769,7 @@ test({
|
|
|
768
769
|
documentUrl: "https://example.com/auth-check",
|
|
769
770
|
document: true
|
|
770
771
|
});
|
|
771
|
-
assertEquals(await ctx.lookupObject("https://example.com/object"), null);
|
|
772
|
+
assertEquals(await withDnsLookup(() => Promise.reject(/* @__PURE__ */ new Error("Resolver unavailable")), () => ctx.lookupObject("https://example.com/object")), null);
|
|
772
773
|
await assertRejects(() => ctx.sendActivity({ identifier: "handle" }, [], new vocab.Create({})), TypeError, "The activity to send must have at least one actor property.");
|
|
773
774
|
await ctx.sendActivity({ identifier: "handle" }, [], new vocab.Create({ actor: new URL("https://example.com/users/handle") }));
|
|
774
775
|
esm_default.get("https://example.com/object", () => new Response(JSON.stringify({
|
|
@@ -1017,7 +1018,7 @@ test({
|
|
|
1017
1018
|
assertStrictEquals(clone.contextLoader, ctx.contextLoader);
|
|
1018
1019
|
assertStrictEquals(clone.federation, ctx.federation);
|
|
1019
1020
|
});
|
|
1020
|
-
esm_default.get("
|
|
1021
|
+
esm_default.get("path:/.well-known/nodeinfo", (cl) => {
|
|
1021
1022
|
const headers = cl.options.headers ?? {};
|
|
1022
1023
|
assertEquals(new Headers(headers).get("User-Agent"), "CustomUserAgent/1.2.3");
|
|
1023
1024
|
return new Response(JSON.stringify({ links: [{
|
|
@@ -1025,7 +1026,7 @@ test({
|
|
|
1025
1026
|
href: "https://example.com/nodeinfo/2.1"
|
|
1026
1027
|
}] }));
|
|
1027
1028
|
});
|
|
1028
|
-
esm_default.get("
|
|
1029
|
+
esm_default.get("path:/nodeinfo/2.1", (cl) => {
|
|
1029
1030
|
const headers = cl.options.headers ?? {};
|
|
1030
1031
|
assertEquals(new Headers(headers).get("User-Agent"), "CustomUserAgent/1.2.3");
|
|
1031
1032
|
return new Response(JSON.stringify({
|
|
@@ -1046,7 +1047,11 @@ test({
|
|
|
1046
1047
|
kv,
|
|
1047
1048
|
userAgent: "CustomUserAgent/1.2.3"
|
|
1048
1049
|
}).createContext(new URL("https://example.com/"), 123);
|
|
1049
|
-
|
|
1050
|
+
const lookupExample = () => Promise.resolve([{
|
|
1051
|
+
address: "93.184.215.14",
|
|
1052
|
+
family: 4
|
|
1053
|
+
}]);
|
|
1054
|
+
assertEquals(await withDnsLookup(lookupExample, () => ctx.lookupNodeInfo("https://example.com/")), {
|
|
1050
1055
|
software: {
|
|
1051
1056
|
name: "foo",
|
|
1052
1057
|
version: "1.2.3"
|
|
@@ -1058,7 +1063,7 @@ test({
|
|
|
1058
1063
|
localComments: 456
|
|
1059
1064
|
}
|
|
1060
1065
|
});
|
|
1061
|
-
assertEquals(await ctx.lookupNodeInfo("https://example.com/", { parse: "none" }), {
|
|
1066
|
+
assertEquals(await withDnsLookup(lookupExample, () => ctx.lookupNodeInfo("https://example.com/", { parse: "none" })), {
|
|
1062
1067
|
software: {
|
|
1063
1068
|
name: "foo",
|
|
1064
1069
|
version: "1.2.3"
|
|
@@ -1258,6 +1263,408 @@ test({
|
|
|
1258
1263
|
esm_default.hardReset();
|
|
1259
1264
|
}
|
|
1260
1265
|
});
|
|
1266
|
+
test("Federation.fetch() accepts unencoded path colons", async () => {
|
|
1267
|
+
const identifier = "https://pod.example/alice";
|
|
1268
|
+
const federation = createFederation({
|
|
1269
|
+
kv: new MemoryKvStore(),
|
|
1270
|
+
contextLoaderFactory: () => mockDocumentLoader
|
|
1271
|
+
});
|
|
1272
|
+
const dispatched = [];
|
|
1273
|
+
federation.setActorDispatcher("/users/{identifier}", (ctx, id) => {
|
|
1274
|
+
dispatched.push(id);
|
|
1275
|
+
assertEquals(ctx.url.pathname, "/users/https:%2F%2Fpod.example%2Falice");
|
|
1276
|
+
assertEquals(ctx.url.search, "?page=1");
|
|
1277
|
+
assertEquals(ctx.request.url, ctx.url.href);
|
|
1278
|
+
return new vocab.Person({ id: ctx.getActorUri(id) });
|
|
1279
|
+
});
|
|
1280
|
+
const request = new Request("https://local.example/users/https:%2F%2Fpod.example%2Falice?page=1", { headers: { Accept: "application/activity+json" } });
|
|
1281
|
+
const response = await federation.fetch(request, { contextData: void 0 });
|
|
1282
|
+
assertEquals(response.status, 200);
|
|
1283
|
+
assertEquals(dispatched, [identifier]);
|
|
1284
|
+
const canonical = "https://local.example/users/https%3A%2F%2Fpod.example%2Falice";
|
|
1285
|
+
const body = await response.json();
|
|
1286
|
+
assert(body != null && typeof body === "object" && "id" in body);
|
|
1287
|
+
assertEquals(body.id, canonical);
|
|
1288
|
+
const ctx = federation.createContext(request, void 0);
|
|
1289
|
+
assertEquals(ctx.getActorUri(identifier).href, canonical);
|
|
1290
|
+
assertEquals(ctx.parseUri(new URL(canonical)), {
|
|
1291
|
+
type: "actor",
|
|
1292
|
+
identifier
|
|
1293
|
+
});
|
|
1294
|
+
assertEquals(ctx.parseUri(new URL(request.url)), null);
|
|
1295
|
+
assertEquals(ctx.parseUri(new URL(canonical.replace("local.example", "other.example"))), null);
|
|
1296
|
+
});
|
|
1297
|
+
test("Federation.fetch() verifies signatures over unencoded path colons", async (t) => {
|
|
1298
|
+
const identifier = "https://pod.example/alice";
|
|
1299
|
+
const canonical = "https://local.example/users/https%3A%2F%2Fpod.example%2Falice/inbox";
|
|
1300
|
+
const encoded = "https://local.example/users/https%3A%2F%2Fpod.example%2Falice/inbox?delivery=1";
|
|
1301
|
+
const normalized = encoded.replace("%3A", ":");
|
|
1302
|
+
for (const spec of ["draft-cavage-http-signatures-12", "rfc9421"]) for (const target of [
|
|
1303
|
+
"encoded",
|
|
1304
|
+
"normalized",
|
|
1305
|
+
"changed after signing"
|
|
1306
|
+
]) await t.step(`${spec}: ${target}`, async () => {
|
|
1307
|
+
const recipients = [];
|
|
1308
|
+
const federation = createFederation({
|
|
1309
|
+
kv: new MemoryKvStore(),
|
|
1310
|
+
documentLoaderFactory: () => mockDocumentLoader,
|
|
1311
|
+
contextLoaderFactory: () => mockDocumentLoader
|
|
1312
|
+
});
|
|
1313
|
+
federation.setActorDispatcher("/users/{identifier}", (ctx, id) => new vocab.Person({ id: ctx.getActorUri(id) })).setKeyPairsDispatcher(() => []);
|
|
1314
|
+
federation.setInboxListeners("/users/{identifier}/inbox").on(vocab.Create, (ctx) => {
|
|
1315
|
+
recipients.push(ctx.recipient);
|
|
1316
|
+
});
|
|
1317
|
+
const signed = await signRequest(new Request(target === "normalized" ? normalized : encoded, {
|
|
1318
|
+
method: "POST",
|
|
1319
|
+
headers: { "Content-Type": "application/activity+json" },
|
|
1320
|
+
body: JSON.stringify(create_default)
|
|
1321
|
+
}), rsaPrivateKey2, rsaPublicKey2.id, { spec });
|
|
1322
|
+
const request = target === "changed after signing" ? new Request(normalized, signed) : signed;
|
|
1323
|
+
const response = await federation.fetch(request, { contextData: void 0 });
|
|
1324
|
+
assertEquals(request.url, target === "encoded" ? encoded : normalized);
|
|
1325
|
+
const valid = target !== "changed after signing";
|
|
1326
|
+
assertEquals(response.status, valid ? 202 : 401);
|
|
1327
|
+
assertEquals(recipients, valid ? [identifier] : []);
|
|
1328
|
+
assertEquals(federation.createContext(new URL(canonical), void 0).getInboxUri(identifier).href, canonical);
|
|
1329
|
+
});
|
|
1330
|
+
});
|
|
1331
|
+
test("RequestContext signature checks", async (t) => {
|
|
1332
|
+
const [tracerProvider, exporter] = createTestTracerProvider();
|
|
1333
|
+
const keyId = rsaPublicKey3.id.href;
|
|
1334
|
+
const ownerId = rsaPublicKey3.ownerId.href;
|
|
1335
|
+
const request = await signRequest(new Request("https://example.com/"), rsaPrivateKey3, rsaPublicKey3.id);
|
|
1336
|
+
function setup(options = {}) {
|
|
1337
|
+
const loads = {
|
|
1338
|
+
key: 0,
|
|
1339
|
+
owner: 0
|
|
1340
|
+
};
|
|
1341
|
+
const loader = async (url, loaderOptions) => {
|
|
1342
|
+
if (url === keyId) loads.key++;
|
|
1343
|
+
else if (url === ownerId) loads.owner++;
|
|
1344
|
+
await options.gate;
|
|
1345
|
+
return await (options.load ?? mockDocumentLoader)(url, loaderOptions);
|
|
1346
|
+
};
|
|
1347
|
+
const federation = createFederation({
|
|
1348
|
+
kv: new MemoryKvStore(),
|
|
1349
|
+
documentLoaderFactory: () => loader,
|
|
1350
|
+
contextLoaderFactory: () => mockDocumentLoader,
|
|
1351
|
+
tracerProvider: options.tracerProvider ?? tracerProvider
|
|
1352
|
+
});
|
|
1353
|
+
return {
|
|
1354
|
+
ctx: federation.createContext(options.request ?? new Request(request), void 0),
|
|
1355
|
+
loads,
|
|
1356
|
+
loader,
|
|
1357
|
+
federation
|
|
1358
|
+
};
|
|
1359
|
+
}
|
|
1360
|
+
try {
|
|
1361
|
+
for (const mode of [
|
|
1362
|
+
"keys",
|
|
1363
|
+
"owners",
|
|
1364
|
+
"warm owners",
|
|
1365
|
+
"mixed"
|
|
1366
|
+
]) await t.step(`coalesces ${mode}`, async () => {
|
|
1367
|
+
exporter.clear();
|
|
1368
|
+
let release;
|
|
1369
|
+
const { ctx, loads, loader } = setup({ gate: new Promise((resolve) => release = resolve) });
|
|
1370
|
+
if (mode === "warm owners") {
|
|
1371
|
+
release();
|
|
1372
|
+
assert(await ctx.getSignedKey());
|
|
1373
|
+
}
|
|
1374
|
+
const results = Promise.all(Array.from({ length: 8 }, (_, i) => {
|
|
1375
|
+
const options = i % 2 === 0 ? {} : {
|
|
1376
|
+
documentLoader: loader,
|
|
1377
|
+
contextLoader: ctx.contextLoader,
|
|
1378
|
+
tracerProvider
|
|
1379
|
+
};
|
|
1380
|
+
return mode === "keys" || mode === "mixed" && i % 2 === 0 ? ctx.getSignedKey(options) : ctx.getSignedKeyOwner(options);
|
|
1381
|
+
}));
|
|
1382
|
+
release();
|
|
1383
|
+
for (const result of await results) assert(result != null);
|
|
1384
|
+
assertEquals(loads, {
|
|
1385
|
+
key: 1,
|
|
1386
|
+
owner: mode === "keys" ? 0 : 1
|
|
1387
|
+
});
|
|
1388
|
+
assertEquals(exporter.getSpans("http_signatures.verify").length, 1);
|
|
1389
|
+
assert(await ctx.getSignedKey());
|
|
1390
|
+
if (mode !== "keys") assert(await ctx.getSignedKeyOwner());
|
|
1391
|
+
assertEquals(loads, {
|
|
1392
|
+
key: 1,
|
|
1393
|
+
owner: mode === "keys" ? 0 : 1
|
|
1394
|
+
});
|
|
1395
|
+
});
|
|
1396
|
+
await t.step("memoizes null keys", async () => {
|
|
1397
|
+
exporter.clear();
|
|
1398
|
+
const { ctx, loads } = setup({ load: () => Promise.reject(/* @__PURE__ */ new Error("unavailable")) });
|
|
1399
|
+
for (let i = 0; i < 3; i++) {
|
|
1400
|
+
assertEquals(await ctx.getSignedKey(), null);
|
|
1401
|
+
assertEquals(await ctx.getSignedKeyOwner(), null);
|
|
1402
|
+
}
|
|
1403
|
+
assertEquals(loads, {
|
|
1404
|
+
key: 1,
|
|
1405
|
+
owner: 0
|
|
1406
|
+
});
|
|
1407
|
+
assertEquals(exporter.getSpans("http_signatures.verify").length, 1);
|
|
1408
|
+
assert(await ctx.getSignedKey({ documentLoader: mockDocumentLoader }));
|
|
1409
|
+
assert(await ctx.getSignedKeyOwner({ documentLoader: mockDocumentLoader }));
|
|
1410
|
+
});
|
|
1411
|
+
await t.step("coalesces key-owner FetchError results", async () => {
|
|
1412
|
+
const { ctx, loads } = setup({ load: async (url, options) => {
|
|
1413
|
+
if (url === ownerId) throw new FetchError(new URL(url), "HTTP 401: Unauthorized");
|
|
1414
|
+
return await mockDocumentLoader(url, options);
|
|
1415
|
+
} });
|
|
1416
|
+
assert(await ctx.getSignedKey());
|
|
1417
|
+
assertEquals(await Promise.all(Array.from({ length: 8 }, () => ctx.getSignedKeyOwner())), Array(8).fill(null));
|
|
1418
|
+
assertEquals(await ctx.getSignedKeyOwner(), null);
|
|
1419
|
+
assertEquals(loads, {
|
|
1420
|
+
key: 1,
|
|
1421
|
+
owner: 1
|
|
1422
|
+
});
|
|
1423
|
+
assert(await ctx.getSignedKeyOwner({ documentLoader: mockDocumentLoader }));
|
|
1424
|
+
});
|
|
1425
|
+
await t.step("memoizes unsigned requests", async () => {
|
|
1426
|
+
exporter.clear();
|
|
1427
|
+
const { ctx, loads } = setup({ request: new Request("https://example.com/") });
|
|
1428
|
+
for (let i = 0; i < 3; i++) {
|
|
1429
|
+
assertEquals(await ctx.getSignedKey(), null);
|
|
1430
|
+
assertEquals(await ctx.getSignedKeyOwner(), null);
|
|
1431
|
+
}
|
|
1432
|
+
assertEquals(loads, {
|
|
1433
|
+
key: 0,
|
|
1434
|
+
owner: 0
|
|
1435
|
+
});
|
|
1436
|
+
assertEquals(exporter.getSpans("http_signatures.verify").length, 1);
|
|
1437
|
+
});
|
|
1438
|
+
await t.step("memoizes null owners without trusting a key's owner claim", async () => {
|
|
1439
|
+
const { ctx, loads } = setup({ load: async (url, options) => url === ownerId ? {
|
|
1440
|
+
documentUrl: url,
|
|
1441
|
+
contextUrl: null,
|
|
1442
|
+
document: {
|
|
1443
|
+
...person2_default,
|
|
1444
|
+
publicKey: []
|
|
1445
|
+
}
|
|
1446
|
+
} : await mockDocumentLoader(url, options) });
|
|
1447
|
+
assert(await ctx.getSignedKey());
|
|
1448
|
+
assertEquals(await Promise.all(Array.from({ length: 8 }, () => ctx.getSignedKeyOwner())), Array(8).fill(null));
|
|
1449
|
+
for (let i = 0; i < 3; i++) assertEquals(await ctx.getSignedKeyOwner(), null);
|
|
1450
|
+
assertEquals(loads, {
|
|
1451
|
+
key: 1,
|
|
1452
|
+
owner: 1
|
|
1453
|
+
});
|
|
1454
|
+
assert(await ctx.getSignedKeyOwner({ documentLoader: mockDocumentLoader }));
|
|
1455
|
+
assert(await ctx.getSignedKeyOwner());
|
|
1456
|
+
});
|
|
1457
|
+
await t.step("isolates concurrent document loaders before success", async () => {
|
|
1458
|
+
const { ctx, loads } = setup();
|
|
1459
|
+
let otherLoads = 0;
|
|
1460
|
+
const otherLoader = async (url, options) => {
|
|
1461
|
+
otherLoads++;
|
|
1462
|
+
if (url === ownerId) return {
|
|
1463
|
+
documentUrl: url,
|
|
1464
|
+
contextUrl: null,
|
|
1465
|
+
document: {
|
|
1466
|
+
...person2_default,
|
|
1467
|
+
name: "Other view"
|
|
1468
|
+
}
|
|
1469
|
+
};
|
|
1470
|
+
return await mockDocumentLoader(url, options);
|
|
1471
|
+
};
|
|
1472
|
+
const [first, second] = await Promise.all([ctx.getSignedKeyOwner(), ctx.getSignedKeyOwner({ documentLoader: otherLoader })]);
|
|
1473
|
+
assertEquals(first?.name, "Jane Doe");
|
|
1474
|
+
assertEquals(second?.name, "Other view");
|
|
1475
|
+
const cached = await ctx.getSignedKeyOwner();
|
|
1476
|
+
assert(cached === first || cached === second);
|
|
1477
|
+
assertStrictEquals(await ctx.getSignedKeyOwner({ documentLoader: otherLoader }), cached);
|
|
1478
|
+
assertEquals(loads, {
|
|
1479
|
+
key: 1,
|
|
1480
|
+
owner: 1
|
|
1481
|
+
});
|
|
1482
|
+
assertEquals(otherLoads, 2);
|
|
1483
|
+
});
|
|
1484
|
+
await t.step("preserves in-flight null while another loader succeeds", async () => {
|
|
1485
|
+
exporter.clear();
|
|
1486
|
+
let release;
|
|
1487
|
+
const gate = new Promise((resolve) => release = resolve);
|
|
1488
|
+
const { ctx, loads } = setup({ load: async () => {
|
|
1489
|
+
await gate;
|
|
1490
|
+
throw new TypeError("fetch failed");
|
|
1491
|
+
} });
|
|
1492
|
+
const failed = Promise.all([ctx.getSignedKey(), ctx.getSignedKeyOwner()]);
|
|
1493
|
+
const owner = await ctx.getSignedKeyOwner({ documentLoader: mockDocumentLoader });
|
|
1494
|
+
assert(owner);
|
|
1495
|
+
release();
|
|
1496
|
+
assertEquals(await failed, [null, null]);
|
|
1497
|
+
assertStrictEquals(await ctx.getSignedKeyOwner(), owner);
|
|
1498
|
+
assert(await ctx.getSignedKey());
|
|
1499
|
+
assertEquals(loads, {
|
|
1500
|
+
key: 1,
|
|
1501
|
+
owner: 0
|
|
1502
|
+
});
|
|
1503
|
+
assertEquals(exporter.getSpans("http_signatures.verify").length, 2);
|
|
1504
|
+
});
|
|
1505
|
+
await t.step("isolates context loaders", async () => {
|
|
1506
|
+
exporter.clear();
|
|
1507
|
+
const { ctx, loads } = setup();
|
|
1508
|
+
const otherContextLoader = (url, options) => mockDocumentLoader(url, options);
|
|
1509
|
+
await Promise.all([ctx.getSignedKeyOwner(), ctx.getSignedKeyOwner({ contextLoader: otherContextLoader })]);
|
|
1510
|
+
assertEquals(loads, {
|
|
1511
|
+
key: 2,
|
|
1512
|
+
owner: 2
|
|
1513
|
+
});
|
|
1514
|
+
assertEquals(exporter.getSpans("http_signatures.verify").length, 2);
|
|
1515
|
+
});
|
|
1516
|
+
await t.step("shares work across tracer providers", async () => {
|
|
1517
|
+
exporter.clear();
|
|
1518
|
+
const [otherProvider, otherExporter] = createTestTracerProvider();
|
|
1519
|
+
try {
|
|
1520
|
+
const { ctx, loads } = setup();
|
|
1521
|
+
await Promise.all([
|
|
1522
|
+
ctx.getSignedKeyOwner(),
|
|
1523
|
+
ctx.getSignedKeyOwner({ tracerProvider: otherProvider }),
|
|
1524
|
+
ctx.getSignedKey(),
|
|
1525
|
+
ctx.getSignedKey({ tracerProvider: otherProvider })
|
|
1526
|
+
]);
|
|
1527
|
+
assert(await ctx.getSignedKeyOwner({ tracerProvider: otherProvider }));
|
|
1528
|
+
assertEquals(loads, {
|
|
1529
|
+
key: 1,
|
|
1530
|
+
owner: 1
|
|
1531
|
+
});
|
|
1532
|
+
assertEquals(exporter.getSpans("http_signatures.verify").length, 1);
|
|
1533
|
+
assertEquals(otherExporter.getSpans("http_signatures.verify").length, 0);
|
|
1534
|
+
} finally {
|
|
1535
|
+
await otherProvider.shutdown();
|
|
1536
|
+
}
|
|
1537
|
+
});
|
|
1538
|
+
await t.step("reuses success with a fresh authorization loader", async () => {
|
|
1539
|
+
exporter.clear();
|
|
1540
|
+
const { ctx, loads } = setup();
|
|
1541
|
+
let freshLoads = 0;
|
|
1542
|
+
const freshLoader = (url, options) => {
|
|
1543
|
+
freshLoads++;
|
|
1544
|
+
return mockDocumentLoader(url, options);
|
|
1545
|
+
};
|
|
1546
|
+
const key = await ctx.getSignedKey({ documentLoader: freshLoader });
|
|
1547
|
+
assert(key);
|
|
1548
|
+
assertStrictEquals(await ctx.getSignedKey(), key);
|
|
1549
|
+
const owner = await ctx.getSignedKeyOwner();
|
|
1550
|
+
assert(owner);
|
|
1551
|
+
assertStrictEquals(await ctx.getSignedKeyOwner({ documentLoader: freshLoader }), owner);
|
|
1552
|
+
assertEquals(loads, {
|
|
1553
|
+
key: 0,
|
|
1554
|
+
owner: 1
|
|
1555
|
+
});
|
|
1556
|
+
assertEquals(freshLoads, 1);
|
|
1557
|
+
assertEquals(exporter.getSpans("http_signatures.verify").length, 1);
|
|
1558
|
+
});
|
|
1559
|
+
await t.step("retains a transient owner lookup failure as null", async () => {
|
|
1560
|
+
let fail = true;
|
|
1561
|
+
const { ctx, loads } = setup({ load: async (url, options) => {
|
|
1562
|
+
if (url === ownerId && fail) throw new TypeError("fetch failed");
|
|
1563
|
+
return await mockDocumentLoader(url, options);
|
|
1564
|
+
} });
|
|
1565
|
+
assert(await ctx.getSignedKey());
|
|
1566
|
+
assertEquals(await ctx.getSignedKeyOwner(), null);
|
|
1567
|
+
fail = false;
|
|
1568
|
+
assertEquals(await ctx.getSignedKeyOwner(), null);
|
|
1569
|
+
assertEquals(loads, {
|
|
1570
|
+
key: 1,
|
|
1571
|
+
owner: 1
|
|
1572
|
+
});
|
|
1573
|
+
assert(await ctx.getSignedKeyOwner({ documentLoader: mockDocumentLoader }));
|
|
1574
|
+
});
|
|
1575
|
+
await t.step("rechecks an owner after another loader verifies the key", async () => {
|
|
1576
|
+
let fail = true;
|
|
1577
|
+
const { ctx, loads } = setup({ load: async (url, options) => {
|
|
1578
|
+
if (url === keyId && fail) throw new TypeError("fetch failed");
|
|
1579
|
+
return await mockDocumentLoader(url, options);
|
|
1580
|
+
} });
|
|
1581
|
+
assertEquals(await ctx.getSignedKeyOwner(), null);
|
|
1582
|
+
fail = false;
|
|
1583
|
+
assertEquals(await ctx.getSignedKeyOwner(), null);
|
|
1584
|
+
assertEquals(loads, {
|
|
1585
|
+
key: 1,
|
|
1586
|
+
owner: 0
|
|
1587
|
+
});
|
|
1588
|
+
assert(await ctx.getSignedKey({ documentLoader: mockDocumentLoader }));
|
|
1589
|
+
assertEquals((await ctx.getSignedKeyOwner())?.id?.href, ownerId);
|
|
1590
|
+
assertEquals(loads, {
|
|
1591
|
+
key: 1,
|
|
1592
|
+
owner: 1
|
|
1593
|
+
});
|
|
1594
|
+
});
|
|
1595
|
+
await t.step("retries unexpected key rejections from a rejection handler", async () => {
|
|
1596
|
+
exporter.clear();
|
|
1597
|
+
const failure = /* @__PURE__ */ new Error("transient context loader failure");
|
|
1598
|
+
let rejectContext = true;
|
|
1599
|
+
const contextLoader = async (url, options) => {
|
|
1600
|
+
if (rejectContext) throw failure;
|
|
1601
|
+
return await mockDocumentLoader(url, options);
|
|
1602
|
+
};
|
|
1603
|
+
const { ctx, loads } = setup();
|
|
1604
|
+
const options = { contextLoader };
|
|
1605
|
+
const failedKeys = Promise.allSettled([
|
|
1606
|
+
ctx.getSignedKey(options),
|
|
1607
|
+
ctx.getSignedKeyOwner(options),
|
|
1608
|
+
ctx.getSignedKey(options)
|
|
1609
|
+
]);
|
|
1610
|
+
const recovery = ctx.getSignedKey(options).catch(() => {
|
|
1611
|
+
rejectContext = false;
|
|
1612
|
+
return ctx.getSignedKey(options);
|
|
1613
|
+
});
|
|
1614
|
+
for (const result of await failedKeys) assertEquals(result.status, "rejected");
|
|
1615
|
+
assert(await recovery);
|
|
1616
|
+
assertEquals(loads, {
|
|
1617
|
+
key: 2,
|
|
1618
|
+
owner: 0
|
|
1619
|
+
});
|
|
1620
|
+
assertEquals(exporter.getSpans("http_signatures.verify").length, 2);
|
|
1621
|
+
});
|
|
1622
|
+
await t.step("retries owner rejections while retaining the verified key", async () => {
|
|
1623
|
+
exporter.clear();
|
|
1624
|
+
let rejectContext = false;
|
|
1625
|
+
const contextLoader = async (url, options) => {
|
|
1626
|
+
if (rejectContext) throw new Error("transient owner context failure");
|
|
1627
|
+
return await mockDocumentLoader(url, options);
|
|
1628
|
+
};
|
|
1629
|
+
const { ctx, loads } = setup();
|
|
1630
|
+
const options = { contextLoader };
|
|
1631
|
+
assert(await ctx.getSignedKey(options));
|
|
1632
|
+
assertEquals(loads, {
|
|
1633
|
+
key: 1,
|
|
1634
|
+
owner: 0
|
|
1635
|
+
});
|
|
1636
|
+
rejectContext = true;
|
|
1637
|
+
const failures = await Promise.allSettled([ctx.getSignedKeyOwner(options), ctx.getSignedKeyOwner(options)]);
|
|
1638
|
+
for (const result of failures) assertEquals(result.status, "rejected");
|
|
1639
|
+
assertEquals(loads, {
|
|
1640
|
+
key: 1,
|
|
1641
|
+
owner: 1
|
|
1642
|
+
});
|
|
1643
|
+
rejectContext = false;
|
|
1644
|
+
assert(await ctx.getSignedKeyOwner(options));
|
|
1645
|
+
assertEquals(loads, {
|
|
1646
|
+
key: 1,
|
|
1647
|
+
owner: 2
|
|
1648
|
+
});
|
|
1649
|
+
assertEquals(exporter.getSpans("http_signatures.verify").length, 1);
|
|
1650
|
+
});
|
|
1651
|
+
await t.step("keeps clones and separate requests independent", async () => {
|
|
1652
|
+
exporter.clear();
|
|
1653
|
+
const { ctx, loads, federation } = setup();
|
|
1654
|
+
assert(await ctx.getSignedKeyOwner());
|
|
1655
|
+
const clone = ctx.clone(void 0);
|
|
1656
|
+
const separate = federation.createContext(new Request(request), void 0);
|
|
1657
|
+
await Promise.all([clone.getSignedKeyOwner(), separate.getSignedKeyOwner()]);
|
|
1658
|
+
assertEquals(loads, {
|
|
1659
|
+
key: 1,
|
|
1660
|
+
owner: 3
|
|
1661
|
+
});
|
|
1662
|
+
assertEquals(exporter.getSpans("http_signatures.verify").length, 3);
|
|
1663
|
+
});
|
|
1664
|
+
} finally {
|
|
1665
|
+
await tracerProvider.shutdown();
|
|
1666
|
+
}
|
|
1667
|
+
});
|
|
1261
1668
|
test("Federation.fetch() [rfc9421] multiple POST signatures", async (t) => {
|
|
1262
1669
|
for (const [name, validSecond, maxHttpSignatures] of [
|
|
1263
1670
|
[
|
|
@@ -1360,7 +1767,7 @@ test("Federation.fetch()", async (t) => {
|
|
|
1360
1767
|
kv,
|
|
1361
1768
|
documentLoaderFactory: () => mockDocumentLoader,
|
|
1362
1769
|
authenticatedDocumentLoaderFactory(identity) {
|
|
1363
|
-
const docLoader = getAuthenticatedDocumentLoader(identity);
|
|
1770
|
+
const docLoader = getAuthenticatedDocumentLoader(identity, { allowPrivateAddress: true });
|
|
1364
1771
|
return (url) => {
|
|
1365
1772
|
if (new URL(url).host === "example.com") return docLoader(url);
|
|
1366
1773
|
return mockDocumentLoader(url);
|
|
@@ -1811,7 +2218,7 @@ test("Federation.setInboxListeners()", async (t) => {
|
|
|
1811
2218
|
kv,
|
|
1812
2219
|
documentLoaderFactory: () => mockDocumentLoader,
|
|
1813
2220
|
authenticatedDocumentLoaderFactory(identity) {
|
|
1814
|
-
const docLoader = getAuthenticatedDocumentLoader(identity);
|
|
2221
|
+
const docLoader = getAuthenticatedDocumentLoader(identity, { allowPrivateAddress: true });
|
|
1815
2222
|
return (url) => {
|
|
1816
2223
|
const urlObj = new URL(url);
|
|
1817
2224
|
authenticatedRequests.push([url, identity.keyId.href]);
|
|
@@ -2134,7 +2541,7 @@ test("Federation.setInboxListeners()", async (t) => {
|
|
|
2134
2541
|
kv,
|
|
2135
2542
|
documentLoaderFactory: () => mockDocumentLoader,
|
|
2136
2543
|
authenticatedDocumentLoaderFactory(identity) {
|
|
2137
|
-
const docLoader = getAuthenticatedDocumentLoader(identity);
|
|
2544
|
+
const docLoader = getAuthenticatedDocumentLoader(identity, { allowPrivateAddress: true });
|
|
2138
2545
|
return (url) => {
|
|
2139
2546
|
if (new URL(url).host === "example.com") return docLoader(url);
|
|
2140
2547
|
return mockDocumentLoader(url);
|
|
@@ -2347,10 +2754,11 @@ test("Federation.setOutboxListeners()", async (t) => {
|
|
|
2347
2754
|
sinks: ["buffer"]
|
|
2348
2755
|
}]
|
|
2349
2756
|
});
|
|
2350
|
-
const federation =
|
|
2757
|
+
const federation = new FederationImpl({
|
|
2351
2758
|
kv,
|
|
2352
2759
|
documentLoaderFactory: () => mockDocumentLoader
|
|
2353
2760
|
});
|
|
2761
|
+
federation.allowPrivateAddress = true;
|
|
2354
2762
|
federation.setActorDispatcher("/users/{identifier}", (_ctx, identifier) => identifier === "john" ? new vocab.Person({}) : null).setKeyPairsDispatcher(() => [{
|
|
2355
2763
|
privateKey: rsaPrivateKey2,
|
|
2356
2764
|
publicKey: rsaPublicKey2.publicKey
|
|
@@ -2453,10 +2861,11 @@ test("Federation.setOutboxListeners()", async (t) => {
|
|
|
2453
2861
|
sinks: ["buffer"]
|
|
2454
2862
|
}]
|
|
2455
2863
|
});
|
|
2456
|
-
const federation =
|
|
2864
|
+
const federation = new FederationImpl({
|
|
2457
2865
|
kv,
|
|
2458
2866
|
documentLoaderFactory: () => mockDocumentLoader
|
|
2459
2867
|
});
|
|
2868
|
+
federation.allowPrivateAddress = true;
|
|
2460
2869
|
federation.setActorDispatcher("/{identifier}", (_ctx, identifier) => identifier === "person2" ? new vocab.Person({}) : null).setKeyPairsDispatcher(() => [{
|
|
2461
2870
|
privateKey: rsaPrivateKey2,
|
|
2462
2871
|
publicKey: rsaPublicKey2.publicKey
|
|
@@ -3136,7 +3545,7 @@ test("FederationImpl.sendActivity()", async (t) => {
|
|
|
3136
3545
|
});
|
|
3137
3546
|
const federation = new FederationImpl({
|
|
3138
3547
|
kv: new MemoryKvStore(),
|
|
3139
|
-
|
|
3548
|
+
allowPrivateAddress: true
|
|
3140
3549
|
});
|
|
3141
3550
|
const context = federation.createContext(new URL("https://example.com/"));
|
|
3142
3551
|
await t.step("success", async () => {
|
|
@@ -6863,7 +7272,7 @@ test("ContextImpl.sendActivity()", async (t) => {
|
|
|
6863
7272
|
const kv = new MemoryKvStore();
|
|
6864
7273
|
const federation = new FederationImpl({
|
|
6865
7274
|
kv,
|
|
6866
|
-
|
|
7275
|
+
allowPrivateAddress: true
|
|
6867
7276
|
});
|
|
6868
7277
|
federation.setActorDispatcher("/{identifier}", async (ctx, identifier) => {
|
|
6869
7278
|
if (identifier !== "1") return null;
|
|
@@ -6986,11 +7395,13 @@ test("ContextImpl.sendActivity()", async (t) => {
|
|
|
6986
7395
|
});
|
|
6987
7396
|
await t.step("records recipient span attributes correctly", async () => {
|
|
6988
7397
|
const [tracerProvider, exporter] = createTestTracerProvider();
|
|
6989
|
-
const
|
|
7398
|
+
const federation3 = new FederationImpl({
|
|
6990
7399
|
kv,
|
|
6991
7400
|
contextLoaderFactory: () => mockDocumentLoader,
|
|
6992
7401
|
tracerProvider
|
|
6993
|
-
})
|
|
7402
|
+
});
|
|
7403
|
+
federation3.allowPrivateAddress = true;
|
|
7404
|
+
const ctx = federation3.createContext(new URL("https://example.com/"), void 0);
|
|
6994
7405
|
const activity = new vocab.Create({
|
|
6995
7406
|
id: new URL("https://example.com/activity/telemetry"),
|
|
6996
7407
|
actor: new URL("https://example.com/person"),
|
|
@@ -7024,7 +7435,7 @@ test("ContextImpl.sendActivity()", async (t) => {
|
|
|
7024
7435
|
};
|
|
7025
7436
|
const federation2 = new FederationImpl({
|
|
7026
7437
|
kv,
|
|
7027
|
-
|
|
7438
|
+
allowPrivateAddress: true,
|
|
7028
7439
|
queue
|
|
7029
7440
|
});
|
|
7030
7441
|
federation2.setActorDispatcher("/{identifier}", async (ctx, identifier) => {
|
|
@@ -7195,7 +7606,7 @@ test("ContextImpl.sendActivity()", async (t) => {
|
|
|
7195
7606
|
const asyncEnqueued = [];
|
|
7196
7607
|
const fed = new FederationImpl({
|
|
7197
7608
|
kv,
|
|
7198
|
-
|
|
7609
|
+
allowPrivateAddress: true,
|
|
7199
7610
|
queue: {
|
|
7200
7611
|
enqueue(message) {
|
|
7201
7612
|
return new Promise((resolve) => {
|
|
@@ -7422,10 +7833,10 @@ test({
|
|
|
7422
7833
|
const signedInvite = await signObject(new vocab.Invite({ actor: new URL("https://example.com/person2") }), ed25519PrivateKey, ed25519Multikey.id);
|
|
7423
7834
|
assert(await ctx.routeActivity("id", signedInvite));
|
|
7424
7835
|
assertEquals(activities, [[null, signedOffer], ["id", signedInvite]]);
|
|
7425
|
-
assertFalse(await ctx.routeActivity(null, new vocab.Create({
|
|
7836
|
+
assertFalse(await withDnsLookup(() => Promise.reject(/* @__PURE__ */ new Error("Resolver unavailable")), () => ctx.routeActivity(null, new vocab.Create({
|
|
7426
7837
|
id: new URL("https://example.com/not-found"),
|
|
7427
7838
|
actor: new URL("https://example.com/person")
|
|
7428
|
-
})));
|
|
7839
|
+
}))));
|
|
7429
7840
|
assertEquals(activities, [[null, signedOffer], ["id", signedInvite]]);
|
|
7430
7841
|
assertFalse(await ctx.routeActivity(null, new vocab.Create({
|
|
7431
7842
|
id: new URL("https://example.com/person"),
|
|
@@ -7932,7 +8343,7 @@ test("InboxContextImpl.forwardActivity()", async (t) => {
|
|
|
7932
8343
|
const kv = new MemoryKvStore();
|
|
7933
8344
|
const federation = new FederationImpl({
|
|
7934
8345
|
kv,
|
|
7935
|
-
|
|
8346
|
+
allowPrivateAddress: true
|
|
7936
8347
|
});
|
|
7937
8348
|
await t.step("skip", async () => {
|
|
7938
8349
|
await new InboxContextImpl(null, {
|
|
@@ -8055,6 +8466,7 @@ test("InboxContextImpl.forwardActivity()", async (t) => {
|
|
|
8055
8466
|
contextLoaderFactory: () => mockDocumentLoader,
|
|
8056
8467
|
tracerProvider
|
|
8057
8468
|
});
|
|
8469
|
+
federationWithTracing.allowPrivateAddress = true;
|
|
8058
8470
|
await new InboxContextImpl(null, await signJsonLd({
|
|
8059
8471
|
"@context": "https://www.w3.org/ns/activitystreams",
|
|
8060
8472
|
"type": "Create",
|
|
@@ -8178,12 +8590,14 @@ test("createFederation() applies httpMessageSignaturesSpecTtl to remembered spec
|
|
|
8178
8590
|
return new Response(null, { status: key == null ? 401 : 202 });
|
|
8179
8591
|
});
|
|
8180
8592
|
const kv = new TtlRecordingKvStore();
|
|
8181
|
-
const
|
|
8593
|
+
const federation = new FederationImpl({
|
|
8182
8594
|
kv,
|
|
8183
8595
|
documentLoaderFactory: () => mockDocumentLoader,
|
|
8184
8596
|
contextLoaderFactory: () => mockDocumentLoader,
|
|
8185
8597
|
httpMessageSignaturesSpecTtl: { days: 14 }
|
|
8186
|
-
})
|
|
8598
|
+
});
|
|
8599
|
+
federation.allowPrivateAddress = true;
|
|
8600
|
+
const ctx = federation.createContext(new URL("https://example.com/"), void 0);
|
|
8187
8601
|
const specKey = [
|
|
8188
8602
|
"_fedify",
|
|
8189
8603
|
"httpMessageSignaturesSpec",
|
|
@@ -8230,12 +8644,17 @@ test("createFederation() applies publicKeyTtl to cached public keys", async () =
|
|
|
8230
8644
|
});
|
|
8231
8645
|
const keyId = "https://example.com/person2#key3";
|
|
8232
8646
|
const kv = new TtlRecordingKvStore();
|
|
8233
|
-
const federation =
|
|
8647
|
+
const federation = new FederationImpl({
|
|
8234
8648
|
kv,
|
|
8235
8649
|
documentLoaderFactory: () => mockDocumentLoader,
|
|
8236
8650
|
contextLoaderFactory: () => mockDocumentLoader,
|
|
8651
|
+
authenticatedDocumentLoaderFactory: (identity, options) => getAuthenticatedDocumentLoader(identity, {
|
|
8652
|
+
...options,
|
|
8653
|
+
allowPrivateAddress: true
|
|
8654
|
+
}),
|
|
8237
8655
|
publicKeyTtl: { days: 7 }
|
|
8238
8656
|
});
|
|
8657
|
+
federation.allowPrivateAddress = true;
|
|
8239
8658
|
const inbox = [];
|
|
8240
8659
|
federation.setActorDispatcher("/users/{identifier}", (_, identifier) => identifier === "john" ? new vocab.Person({}) : null).setKeyPairsDispatcher(() => [{
|
|
8241
8660
|
privateKey: rsaPrivateKey2,
|
|
@@ -8441,7 +8860,9 @@ test("ContextImpl.lookupWebFinger()", async (t) => {
|
|
|
8441
8860
|
links: []
|
|
8442
8861
|
};
|
|
8443
8862
|
esm_default.get("https://example.com/.well-known/webfinger?resource=acct%3Aalice%40example.com", { body: expected });
|
|
8444
|
-
|
|
8863
|
+
const federation = new FederationImpl(mockOptions());
|
|
8864
|
+
federation.allowPrivateAddress = true;
|
|
8865
|
+
deepStrictEqual(await federation.createContext(new URL("https://example.com/"), void 0).lookupWebFinger("acct:alice@example.com", { withDiagnostics: true }), expected);
|
|
8445
8866
|
} finally {
|
|
8446
8867
|
esm_default.hardReset();
|
|
8447
8868
|
}
|
|
@@ -8872,5 +9293,19 @@ test("createFederation() validates documentLoaderTimeout", () => {
|
|
|
8872
9293
|
documentLoaderTimeout: Temporal.Duration.from({ seconds: 30 })
|
|
8873
9294
|
});
|
|
8874
9295
|
});
|
|
9296
|
+
/**
|
|
9297
|
+
* Runs the given function while `node:dns/promises`' `lookup()`, which the
|
|
9298
|
+
* default public fetch resolves hostnames with, is replaced, so that no test
|
|
9299
|
+
* depends on the real DNS.
|
|
9300
|
+
*/
|
|
9301
|
+
async function withDnsLookup(lookup, fn) {
|
|
9302
|
+
const originalLookup = dns.lookup;
|
|
9303
|
+
dns.lookup = lookup;
|
|
9304
|
+
try {
|
|
9305
|
+
return await fn();
|
|
9306
|
+
} finally {
|
|
9307
|
+
dns.lookup = originalLookup;
|
|
9308
|
+
}
|
|
9309
|
+
}
|
|
8875
9310
|
//#endregion
|
|
8876
9311
|
export {};
|