@fedify/fedify 2.4.5-dev.2453 → 2.4.5
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-CS2LjGi-.mjs → builder-CUAVOHg6.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-C6_4hMjm.mjs → compound-proof-BNjXclzO.mjs} +2 -2
- package/dist/{context-7NyiqIcg.d.ts → context-CVOOd9pD.d.ts} +28 -2
- package/dist/{context-tsYPJ2Ew.d.cts → context-D0gFIgev.d.cts} +28 -2
- package/dist/{deno-CTWhU1oT.mjs → deno-DcJISdwu.mjs} +1 -1
- package/dist/docloader-CP1UxMLL.mjs +136 -0
- 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 +4 -4
- package/dist/federation/hashlink-media.test.mjs +2 -2
- package/dist/federation/idempotency.test.mjs +2 -2
- package/dist/federation/inbox-report.test.mjs +5 -5
- package/dist/federation/inbox.test.mjs +1 -1
- package/dist/federation/metrics.test.mjs +1 -1
- package/dist/federation/middleware.test.mjs +465 -32
- 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/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 +498 -88
- 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-DWVfQYX_.cjs → federation-Bkfq2YpS.cjs} +240 -102
- package/dist/{http-Dx5s5-pP.cjs → http-DHVH0DqC.cjs} +179 -110
- package/dist/{http-rgt1uxns.mjs → http-DuGmr8J8.mjs} +204 -106
- package/dist/{http-y9FnJFIe.js → http-R0ty0Udi.js} +178 -109
- package/dist/{inbox-4AZHfaO8.mjs → inbox-Bl1Y0s_A.mjs} +2 -2
- package/dist/{key-DYxuyd-s.d.cts → key-CyCcut3h.d.ts} +1 -2
- package/dist/{key-C1nv7_jD.d.ts → key-VEL1QqHF.d.cts} +1 -2
- package/dist/{kv-cache-DmxFPJaR.cjs → kv-cache-BtBSBZi9.cjs} +72 -27
- package/dist/{kv-cache-C3QkJF-x.mjs → kv-cache-DAV2_U44.mjs} +1 -1
- package/dist/{kv-cache-w4yWxqS1.js → kv-cache-FnLtmATH.js} +73 -28
- package/dist/{ld-CT-bAqSC.mjs → ld-Cy5GqbvS.mjs} +2 -2
- package/dist/{metrics-DsFjgE5X.mjs → metrics-CW8LoN7n.mjs} +1 -1
- package/dist/{middleware-DC8Omn-e.mjs → middleware-CdP-lz8Q.mjs} +1 -1
- package/dist/{middleware-Ch9WIaoZ.mjs → middleware-D2fk45uN.mjs} +82 -42
- package/dist/{middleware-BCHbutlW.js → middleware-D4rQARvq.js} +241 -103
- package/dist/{mod-DEZXwV6J.d.ts → mod-B1Qt5cQQ.d.ts} +2 -9
- package/dist/{mod-Xj5P4s4d.d.ts → mod-B5COx5Wd.d.ts} +1 -1
- package/dist/{mod-CavhAgLw.d.cts → mod-BTxJG7ZH.d.cts} +1 -1
- package/dist/{mod-CtpF2BU5.d.ts → mod-CpyjFA9s.d.ts} +1 -1
- package/dist/{mod-BojP2Bw3.d.cts → mod-makPMVMg.d.cts} +1 -1
- package/dist/{mod-Dw-nPxQh.d.cts → mod-ss0wBUH1.d.cts} +2 -9
- 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 +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 +292 -30
- 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-BGYYk-xi.mjs → owner-CbQZqFim.mjs} +3 -3
- package/dist/{portable-collection-DzEc4cB2.js → portable-collection-B5Aryi8I.js} +1 -1
- package/dist/{portable-collection-Cwn7DM0G.cjs → portable-collection-C_Kmvl3q.cjs} +1 -1
- package/dist/{portable-collection-DJSwe8QM.mjs → portable-collection-D2JYDadi.mjs} +2 -2
- package/dist/{portable-key-Dc6N1SEB.mjs → portable-key-Cc-ctNU2.mjs} +1 -1
- package/dist/{proof-DP93iZiH.mjs → proof-UN4CW55z.mjs} +3 -3
- package/dist/{send-c6buRbhI.mjs → send-BzbX2e6v.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 +2 -2
- package/dist/sig/mod.d.ts +2 -2
- 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-DfC1NkLg.mjs → temporal-B7Wvxrjs.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 +7 -7
- package/dist/docloader-CXFE-n2O.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 { a as compactJsonLd, h as verifyJsonLd, p as signJsonLd, s as detachSignature } from "../ld-
|
|
21
|
-
import { a as signObject, s as verifyObject } from "../proof-
|
|
22
|
-
import {
|
|
19
|
+
import { g as recordInboxActivity } from "../metrics-CW8LoN7n.mjs";
|
|
20
|
+
import { a as compactJsonLd, h as verifyJsonLd, p as signJsonLd, s as detachSignature } from "../ld-Cy5GqbvS.mjs";
|
|
21
|
+
import { a as signObject, s as verifyObject } from "../proof-UN4CW55z.mjs";
|
|
22
|
+
import { p as verifyRequest, u as signRequest } from "../http-DuGmr8J8.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-CP1UxMLL.mjs";
|
|
25
|
+
import { T as handleBenchmarkTrigger, i as KvSpecDeterminer, n as FederationImpl, o as createFederation, r as InboxContextImpl, t as ContextImpl } from "../middleware-D2fk45uN.mjs";
|
|
26
|
+
import { t as doesActorOwnKey } from "../owner-CbQZqFim.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,10 +34,10 @@ import { Create, Note, Offer, Person, getTypeId, lookupObject } from "@fedify/vo
|
|
|
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";
|
|
40
|
+
import dns from "node:dns/promises";
|
|
41
41
|
//#region ../fixture/src/fixtures/example.com/create.json
|
|
42
42
|
var create_default = {
|
|
43
43
|
"@context": "https://www.w3.org/ns/activitystreams",
|
|
@@ -101,7 +101,7 @@ var person2_default = {
|
|
|
101
101
|
};
|
|
102
102
|
//#endregion
|
|
103
103
|
//#region src/federation/middleware.test.ts
|
|
104
|
-
const documentLoader = getDocumentLoader();
|
|
104
|
+
const documentLoader = getDocumentLoader({ allowPrivateAddress: true });
|
|
105
105
|
let logtapeLock = Promise.resolve();
|
|
106
106
|
async function withLogtapeLock(fn) {
|
|
107
107
|
const run = logtapeLock.then(fn, fn);
|
|
@@ -656,7 +656,8 @@ test({
|
|
|
656
656
|
const federation = createFederation({
|
|
657
657
|
kv,
|
|
658
658
|
documentLoaderFactory: () => rejectingLoader,
|
|
659
|
-
contextLoaderFactory: () => mockDocumentLoader
|
|
659
|
+
contextLoaderFactory: () => mockDocumentLoader,
|
|
660
|
+
authenticatedDocumentLoaderFactory: (identity) => getAuthenticatedDocumentLoader(identity, { allowPrivateAddress: true })
|
|
660
661
|
});
|
|
661
662
|
let ctx = federation.createContext(new URL("https://example.com:1234/"), 123);
|
|
662
663
|
assertEquals(ctx.data, 123);
|
|
@@ -767,7 +768,7 @@ test({
|
|
|
767
768
|
documentUrl: "https://example.com/auth-check",
|
|
768
769
|
document: true
|
|
769
770
|
});
|
|
770
|
-
assertEquals(await ctx.lookupObject("https://example.com/object"), null);
|
|
771
|
+
assertEquals(await withDnsLookup(() => Promise.reject(/* @__PURE__ */ new Error("Resolver unavailable")), () => ctx.lookupObject("https://example.com/object")), null);
|
|
771
772
|
await assertRejects(() => ctx.sendActivity({ identifier: "handle" }, [], new vocab.Create({})), TypeError, "The activity to send must have at least one actor property.");
|
|
772
773
|
await ctx.sendActivity({ identifier: "handle" }, [], new vocab.Create({ actor: new URL("https://example.com/users/handle") }));
|
|
773
774
|
esm_default.get("https://example.com/object", () => new Response(JSON.stringify({
|
|
@@ -1016,7 +1017,7 @@ test({
|
|
|
1016
1017
|
assertStrictEquals(clone.contextLoader, ctx.contextLoader);
|
|
1017
1018
|
assertStrictEquals(clone.federation, ctx.federation);
|
|
1018
1019
|
});
|
|
1019
|
-
esm_default.get("
|
|
1020
|
+
esm_default.get("path:/.well-known/nodeinfo", (cl) => {
|
|
1020
1021
|
const headers = cl.options.headers ?? {};
|
|
1021
1022
|
assertEquals(new Headers(headers).get("User-Agent"), "CustomUserAgent/1.2.3");
|
|
1022
1023
|
return new Response(JSON.stringify({ links: [{
|
|
@@ -1024,7 +1025,7 @@ test({
|
|
|
1024
1025
|
href: "https://example.com/nodeinfo/2.1"
|
|
1025
1026
|
}] }));
|
|
1026
1027
|
});
|
|
1027
|
-
esm_default.get("
|
|
1028
|
+
esm_default.get("path:/nodeinfo/2.1", (cl) => {
|
|
1028
1029
|
const headers = cl.options.headers ?? {};
|
|
1029
1030
|
assertEquals(new Headers(headers).get("User-Agent"), "CustomUserAgent/1.2.3");
|
|
1030
1031
|
return new Response(JSON.stringify({
|
|
@@ -1045,7 +1046,11 @@ test({
|
|
|
1045
1046
|
kv,
|
|
1046
1047
|
userAgent: "CustomUserAgent/1.2.3"
|
|
1047
1048
|
}).createContext(new URL("https://example.com/"), 123);
|
|
1048
|
-
|
|
1049
|
+
const lookupExample = () => Promise.resolve([{
|
|
1050
|
+
address: "93.184.215.14",
|
|
1051
|
+
family: 4
|
|
1052
|
+
}]);
|
|
1053
|
+
assertEquals(await withDnsLookup(lookupExample, () => ctx.lookupNodeInfo("https://example.com/")), {
|
|
1049
1054
|
software: {
|
|
1050
1055
|
name: "foo",
|
|
1051
1056
|
version: "1.2.3"
|
|
@@ -1057,7 +1062,7 @@ test({
|
|
|
1057
1062
|
localComments: 456
|
|
1058
1063
|
}
|
|
1059
1064
|
});
|
|
1060
|
-
assertEquals(await ctx.lookupNodeInfo("https://example.com/", { parse: "none" }), {
|
|
1065
|
+
assertEquals(await withDnsLookup(lookupExample, () => ctx.lookupNodeInfo("https://example.com/", { parse: "none" })), {
|
|
1061
1066
|
software: {
|
|
1062
1067
|
name: "foo",
|
|
1063
1068
|
version: "1.2.3"
|
|
@@ -1176,6 +1181,408 @@ test({
|
|
|
1176
1181
|
esm_default.hardReset();
|
|
1177
1182
|
}
|
|
1178
1183
|
});
|
|
1184
|
+
test("Federation.fetch() accepts unencoded path colons", async () => {
|
|
1185
|
+
const identifier = "https://pod.example/alice";
|
|
1186
|
+
const federation = createFederation({
|
|
1187
|
+
kv: new MemoryKvStore(),
|
|
1188
|
+
contextLoaderFactory: () => mockDocumentLoader
|
|
1189
|
+
});
|
|
1190
|
+
const dispatched = [];
|
|
1191
|
+
federation.setActorDispatcher("/users/{identifier}", (ctx, id) => {
|
|
1192
|
+
dispatched.push(id);
|
|
1193
|
+
assertEquals(ctx.url.pathname, "/users/https:%2F%2Fpod.example%2Falice");
|
|
1194
|
+
assertEquals(ctx.url.search, "?page=1");
|
|
1195
|
+
assertEquals(ctx.request.url, ctx.url.href);
|
|
1196
|
+
return new vocab.Person({ id: ctx.getActorUri(id) });
|
|
1197
|
+
});
|
|
1198
|
+
const request = new Request("https://local.example/users/https:%2F%2Fpod.example%2Falice?page=1", { headers: { Accept: "application/activity+json" } });
|
|
1199
|
+
const response = await federation.fetch(request, { contextData: void 0 });
|
|
1200
|
+
assertEquals(response.status, 200);
|
|
1201
|
+
assertEquals(dispatched, [identifier]);
|
|
1202
|
+
const canonical = "https://local.example/users/https%3A%2F%2Fpod.example%2Falice";
|
|
1203
|
+
const body = await response.json();
|
|
1204
|
+
assert(body != null && typeof body === "object" && "id" in body);
|
|
1205
|
+
assertEquals(body.id, canonical);
|
|
1206
|
+
const ctx = federation.createContext(request, void 0);
|
|
1207
|
+
assertEquals(ctx.getActorUri(identifier).href, canonical);
|
|
1208
|
+
assertEquals(ctx.parseUri(new URL(canonical)), {
|
|
1209
|
+
type: "actor",
|
|
1210
|
+
identifier
|
|
1211
|
+
});
|
|
1212
|
+
assertEquals(ctx.parseUri(new URL(request.url)), null);
|
|
1213
|
+
assertEquals(ctx.parseUri(new URL(canonical.replace("local.example", "other.example"))), null);
|
|
1214
|
+
});
|
|
1215
|
+
test("Federation.fetch() verifies signatures over unencoded path colons", async (t) => {
|
|
1216
|
+
const identifier = "https://pod.example/alice";
|
|
1217
|
+
const canonical = "https://local.example/users/https%3A%2F%2Fpod.example%2Falice/inbox";
|
|
1218
|
+
const encoded = "https://local.example/users/https%3A%2F%2Fpod.example%2Falice/inbox?delivery=1";
|
|
1219
|
+
const normalized = encoded.replace("%3A", ":");
|
|
1220
|
+
for (const spec of ["draft-cavage-http-signatures-12", "rfc9421"]) for (const target of [
|
|
1221
|
+
"encoded",
|
|
1222
|
+
"normalized",
|
|
1223
|
+
"changed after signing"
|
|
1224
|
+
]) await t.step(`${spec}: ${target}`, async () => {
|
|
1225
|
+
const recipients = [];
|
|
1226
|
+
const federation = createFederation({
|
|
1227
|
+
kv: new MemoryKvStore(),
|
|
1228
|
+
documentLoaderFactory: () => mockDocumentLoader,
|
|
1229
|
+
contextLoaderFactory: () => mockDocumentLoader
|
|
1230
|
+
});
|
|
1231
|
+
federation.setActorDispatcher("/users/{identifier}", (ctx, id) => new vocab.Person({ id: ctx.getActorUri(id) })).setKeyPairsDispatcher(() => []);
|
|
1232
|
+
federation.setInboxListeners("/users/{identifier}/inbox").on(vocab.Create, (ctx) => {
|
|
1233
|
+
recipients.push(ctx.recipient);
|
|
1234
|
+
});
|
|
1235
|
+
const signed = await signRequest(new Request(target === "normalized" ? normalized : encoded, {
|
|
1236
|
+
method: "POST",
|
|
1237
|
+
headers: { "Content-Type": "application/activity+json" },
|
|
1238
|
+
body: JSON.stringify(create_default)
|
|
1239
|
+
}), rsaPrivateKey2, rsaPublicKey2.id, { spec });
|
|
1240
|
+
const request = target === "changed after signing" ? new Request(normalized, signed) : signed;
|
|
1241
|
+
const response = await federation.fetch(request, { contextData: void 0 });
|
|
1242
|
+
assertEquals(request.url, target === "encoded" ? encoded : normalized);
|
|
1243
|
+
const valid = target !== "changed after signing";
|
|
1244
|
+
assertEquals(response.status, valid ? 202 : 401);
|
|
1245
|
+
assertEquals(recipients, valid ? [identifier] : []);
|
|
1246
|
+
assertEquals(federation.createContext(new URL(canonical), void 0).getInboxUri(identifier).href, canonical);
|
|
1247
|
+
});
|
|
1248
|
+
});
|
|
1249
|
+
test("RequestContext signature checks", async (t) => {
|
|
1250
|
+
const [tracerProvider, exporter] = createTestTracerProvider();
|
|
1251
|
+
const keyId = rsaPublicKey3.id.href;
|
|
1252
|
+
const ownerId = rsaPublicKey3.ownerId.href;
|
|
1253
|
+
const request = await signRequest(new Request("https://example.com/"), rsaPrivateKey3, rsaPublicKey3.id);
|
|
1254
|
+
function setup(options = {}) {
|
|
1255
|
+
const loads = {
|
|
1256
|
+
key: 0,
|
|
1257
|
+
owner: 0
|
|
1258
|
+
};
|
|
1259
|
+
const loader = async (url, loaderOptions) => {
|
|
1260
|
+
if (url === keyId) loads.key++;
|
|
1261
|
+
else if (url === ownerId) loads.owner++;
|
|
1262
|
+
await options.gate;
|
|
1263
|
+
return await (options.load ?? mockDocumentLoader)(url, loaderOptions);
|
|
1264
|
+
};
|
|
1265
|
+
const federation = createFederation({
|
|
1266
|
+
kv: new MemoryKvStore(),
|
|
1267
|
+
documentLoaderFactory: () => loader,
|
|
1268
|
+
contextLoaderFactory: () => mockDocumentLoader,
|
|
1269
|
+
tracerProvider: options.tracerProvider ?? tracerProvider
|
|
1270
|
+
});
|
|
1271
|
+
return {
|
|
1272
|
+
ctx: federation.createContext(options.request ?? new Request(request), void 0),
|
|
1273
|
+
loads,
|
|
1274
|
+
loader,
|
|
1275
|
+
federation
|
|
1276
|
+
};
|
|
1277
|
+
}
|
|
1278
|
+
try {
|
|
1279
|
+
for (const mode of [
|
|
1280
|
+
"keys",
|
|
1281
|
+
"owners",
|
|
1282
|
+
"warm owners",
|
|
1283
|
+
"mixed"
|
|
1284
|
+
]) await t.step(`coalesces ${mode}`, async () => {
|
|
1285
|
+
exporter.clear();
|
|
1286
|
+
let release;
|
|
1287
|
+
const { ctx, loads, loader } = setup({ gate: new Promise((resolve) => release = resolve) });
|
|
1288
|
+
if (mode === "warm owners") {
|
|
1289
|
+
release();
|
|
1290
|
+
assert(await ctx.getSignedKey());
|
|
1291
|
+
}
|
|
1292
|
+
const results = Promise.all(Array.from({ length: 8 }, (_, i) => {
|
|
1293
|
+
const options = i % 2 === 0 ? {} : {
|
|
1294
|
+
documentLoader: loader,
|
|
1295
|
+
contextLoader: ctx.contextLoader,
|
|
1296
|
+
tracerProvider
|
|
1297
|
+
};
|
|
1298
|
+
return mode === "keys" || mode === "mixed" && i % 2 === 0 ? ctx.getSignedKey(options) : ctx.getSignedKeyOwner(options);
|
|
1299
|
+
}));
|
|
1300
|
+
release();
|
|
1301
|
+
for (const result of await results) assert(result != null);
|
|
1302
|
+
assertEquals(loads, {
|
|
1303
|
+
key: 1,
|
|
1304
|
+
owner: mode === "keys" ? 0 : 1
|
|
1305
|
+
});
|
|
1306
|
+
assertEquals(exporter.getSpans("http_signatures.verify").length, 1);
|
|
1307
|
+
assert(await ctx.getSignedKey());
|
|
1308
|
+
if (mode !== "keys") assert(await ctx.getSignedKeyOwner());
|
|
1309
|
+
assertEquals(loads, {
|
|
1310
|
+
key: 1,
|
|
1311
|
+
owner: mode === "keys" ? 0 : 1
|
|
1312
|
+
});
|
|
1313
|
+
});
|
|
1314
|
+
await t.step("memoizes null keys", async () => {
|
|
1315
|
+
exporter.clear();
|
|
1316
|
+
const { ctx, loads } = setup({ load: () => Promise.reject(/* @__PURE__ */ new Error("unavailable")) });
|
|
1317
|
+
for (let i = 0; i < 3; i++) {
|
|
1318
|
+
assertEquals(await ctx.getSignedKey(), null);
|
|
1319
|
+
assertEquals(await ctx.getSignedKeyOwner(), null);
|
|
1320
|
+
}
|
|
1321
|
+
assertEquals(loads, {
|
|
1322
|
+
key: 1,
|
|
1323
|
+
owner: 0
|
|
1324
|
+
});
|
|
1325
|
+
assertEquals(exporter.getSpans("http_signatures.verify").length, 1);
|
|
1326
|
+
assert(await ctx.getSignedKey({ documentLoader: mockDocumentLoader }));
|
|
1327
|
+
assert(await ctx.getSignedKeyOwner({ documentLoader: mockDocumentLoader }));
|
|
1328
|
+
});
|
|
1329
|
+
await t.step("coalesces key-owner FetchError results", async () => {
|
|
1330
|
+
const { ctx, loads } = setup({ load: async (url, options) => {
|
|
1331
|
+
if (url === ownerId) throw new FetchError(new URL(url), "HTTP 401: Unauthorized");
|
|
1332
|
+
return await mockDocumentLoader(url, options);
|
|
1333
|
+
} });
|
|
1334
|
+
assert(await ctx.getSignedKey());
|
|
1335
|
+
assertEquals(await Promise.all(Array.from({ length: 8 }, () => ctx.getSignedKeyOwner())), Array(8).fill(null));
|
|
1336
|
+
assertEquals(await ctx.getSignedKeyOwner(), null);
|
|
1337
|
+
assertEquals(loads, {
|
|
1338
|
+
key: 1,
|
|
1339
|
+
owner: 1
|
|
1340
|
+
});
|
|
1341
|
+
assert(await ctx.getSignedKeyOwner({ documentLoader: mockDocumentLoader }));
|
|
1342
|
+
});
|
|
1343
|
+
await t.step("memoizes unsigned requests", async () => {
|
|
1344
|
+
exporter.clear();
|
|
1345
|
+
const { ctx, loads } = setup({ request: new Request("https://example.com/") });
|
|
1346
|
+
for (let i = 0; i < 3; i++) {
|
|
1347
|
+
assertEquals(await ctx.getSignedKey(), null);
|
|
1348
|
+
assertEquals(await ctx.getSignedKeyOwner(), null);
|
|
1349
|
+
}
|
|
1350
|
+
assertEquals(loads, {
|
|
1351
|
+
key: 0,
|
|
1352
|
+
owner: 0
|
|
1353
|
+
});
|
|
1354
|
+
assertEquals(exporter.getSpans("http_signatures.verify").length, 1);
|
|
1355
|
+
});
|
|
1356
|
+
await t.step("memoizes null owners without trusting a key's owner claim", async () => {
|
|
1357
|
+
const { ctx, loads } = setup({ load: async (url, options) => url === ownerId ? {
|
|
1358
|
+
documentUrl: url,
|
|
1359
|
+
contextUrl: null,
|
|
1360
|
+
document: {
|
|
1361
|
+
...person2_default,
|
|
1362
|
+
publicKey: []
|
|
1363
|
+
}
|
|
1364
|
+
} : await mockDocumentLoader(url, options) });
|
|
1365
|
+
assert(await ctx.getSignedKey());
|
|
1366
|
+
assertEquals(await Promise.all(Array.from({ length: 8 }, () => ctx.getSignedKeyOwner())), Array(8).fill(null));
|
|
1367
|
+
for (let i = 0; i < 3; i++) assertEquals(await ctx.getSignedKeyOwner(), null);
|
|
1368
|
+
assertEquals(loads, {
|
|
1369
|
+
key: 1,
|
|
1370
|
+
owner: 1
|
|
1371
|
+
});
|
|
1372
|
+
assert(await ctx.getSignedKeyOwner({ documentLoader: mockDocumentLoader }));
|
|
1373
|
+
assert(await ctx.getSignedKeyOwner());
|
|
1374
|
+
});
|
|
1375
|
+
await t.step("isolates concurrent document loaders before success", async () => {
|
|
1376
|
+
const { ctx, loads } = setup();
|
|
1377
|
+
let otherLoads = 0;
|
|
1378
|
+
const otherLoader = async (url, options) => {
|
|
1379
|
+
otherLoads++;
|
|
1380
|
+
if (url === ownerId) return {
|
|
1381
|
+
documentUrl: url,
|
|
1382
|
+
contextUrl: null,
|
|
1383
|
+
document: {
|
|
1384
|
+
...person2_default,
|
|
1385
|
+
name: "Other view"
|
|
1386
|
+
}
|
|
1387
|
+
};
|
|
1388
|
+
return await mockDocumentLoader(url, options);
|
|
1389
|
+
};
|
|
1390
|
+
const [first, second] = await Promise.all([ctx.getSignedKeyOwner(), ctx.getSignedKeyOwner({ documentLoader: otherLoader })]);
|
|
1391
|
+
assertEquals(first?.name, "Jane Doe");
|
|
1392
|
+
assertEquals(second?.name, "Other view");
|
|
1393
|
+
const cached = await ctx.getSignedKeyOwner();
|
|
1394
|
+
assert(cached === first || cached === second);
|
|
1395
|
+
assertStrictEquals(await ctx.getSignedKeyOwner({ documentLoader: otherLoader }), cached);
|
|
1396
|
+
assertEquals(loads, {
|
|
1397
|
+
key: 1,
|
|
1398
|
+
owner: 1
|
|
1399
|
+
});
|
|
1400
|
+
assertEquals(otherLoads, 2);
|
|
1401
|
+
});
|
|
1402
|
+
await t.step("preserves in-flight null while another loader succeeds", async () => {
|
|
1403
|
+
exporter.clear();
|
|
1404
|
+
let release;
|
|
1405
|
+
const gate = new Promise((resolve) => release = resolve);
|
|
1406
|
+
const { ctx, loads } = setup({ load: async () => {
|
|
1407
|
+
await gate;
|
|
1408
|
+
throw new TypeError("fetch failed");
|
|
1409
|
+
} });
|
|
1410
|
+
const failed = Promise.all([ctx.getSignedKey(), ctx.getSignedKeyOwner()]);
|
|
1411
|
+
const owner = await ctx.getSignedKeyOwner({ documentLoader: mockDocumentLoader });
|
|
1412
|
+
assert(owner);
|
|
1413
|
+
release();
|
|
1414
|
+
assertEquals(await failed, [null, null]);
|
|
1415
|
+
assertStrictEquals(await ctx.getSignedKeyOwner(), owner);
|
|
1416
|
+
assert(await ctx.getSignedKey());
|
|
1417
|
+
assertEquals(loads, {
|
|
1418
|
+
key: 1,
|
|
1419
|
+
owner: 0
|
|
1420
|
+
});
|
|
1421
|
+
assertEquals(exporter.getSpans("http_signatures.verify").length, 2);
|
|
1422
|
+
});
|
|
1423
|
+
await t.step("isolates context loaders", async () => {
|
|
1424
|
+
exporter.clear();
|
|
1425
|
+
const { ctx, loads } = setup();
|
|
1426
|
+
const otherContextLoader = (url, options) => mockDocumentLoader(url, options);
|
|
1427
|
+
await Promise.all([ctx.getSignedKeyOwner(), ctx.getSignedKeyOwner({ contextLoader: otherContextLoader })]);
|
|
1428
|
+
assertEquals(loads, {
|
|
1429
|
+
key: 2,
|
|
1430
|
+
owner: 2
|
|
1431
|
+
});
|
|
1432
|
+
assertEquals(exporter.getSpans("http_signatures.verify").length, 2);
|
|
1433
|
+
});
|
|
1434
|
+
await t.step("shares work across tracer providers", async () => {
|
|
1435
|
+
exporter.clear();
|
|
1436
|
+
const [otherProvider, otherExporter] = createTestTracerProvider();
|
|
1437
|
+
try {
|
|
1438
|
+
const { ctx, loads } = setup();
|
|
1439
|
+
await Promise.all([
|
|
1440
|
+
ctx.getSignedKeyOwner(),
|
|
1441
|
+
ctx.getSignedKeyOwner({ tracerProvider: otherProvider }),
|
|
1442
|
+
ctx.getSignedKey(),
|
|
1443
|
+
ctx.getSignedKey({ tracerProvider: otherProvider })
|
|
1444
|
+
]);
|
|
1445
|
+
assert(await ctx.getSignedKeyOwner({ tracerProvider: otherProvider }));
|
|
1446
|
+
assertEquals(loads, {
|
|
1447
|
+
key: 1,
|
|
1448
|
+
owner: 1
|
|
1449
|
+
});
|
|
1450
|
+
assertEquals(exporter.getSpans("http_signatures.verify").length, 1);
|
|
1451
|
+
assertEquals(otherExporter.getSpans("http_signatures.verify").length, 0);
|
|
1452
|
+
} finally {
|
|
1453
|
+
await otherProvider.shutdown();
|
|
1454
|
+
}
|
|
1455
|
+
});
|
|
1456
|
+
await t.step("reuses success with a fresh authorization loader", async () => {
|
|
1457
|
+
exporter.clear();
|
|
1458
|
+
const { ctx, loads } = setup();
|
|
1459
|
+
let freshLoads = 0;
|
|
1460
|
+
const freshLoader = (url, options) => {
|
|
1461
|
+
freshLoads++;
|
|
1462
|
+
return mockDocumentLoader(url, options);
|
|
1463
|
+
};
|
|
1464
|
+
const key = await ctx.getSignedKey({ documentLoader: freshLoader });
|
|
1465
|
+
assert(key);
|
|
1466
|
+
assertStrictEquals(await ctx.getSignedKey(), key);
|
|
1467
|
+
const owner = await ctx.getSignedKeyOwner();
|
|
1468
|
+
assert(owner);
|
|
1469
|
+
assertStrictEquals(await ctx.getSignedKeyOwner({ documentLoader: freshLoader }), owner);
|
|
1470
|
+
assertEquals(loads, {
|
|
1471
|
+
key: 0,
|
|
1472
|
+
owner: 1
|
|
1473
|
+
});
|
|
1474
|
+
assertEquals(freshLoads, 1);
|
|
1475
|
+
assertEquals(exporter.getSpans("http_signatures.verify").length, 1);
|
|
1476
|
+
});
|
|
1477
|
+
await t.step("retains a transient owner lookup failure as null", async () => {
|
|
1478
|
+
let fail = true;
|
|
1479
|
+
const { ctx, loads } = setup({ load: async (url, options) => {
|
|
1480
|
+
if (url === ownerId && fail) throw new TypeError("fetch failed");
|
|
1481
|
+
return await mockDocumentLoader(url, options);
|
|
1482
|
+
} });
|
|
1483
|
+
assert(await ctx.getSignedKey());
|
|
1484
|
+
assertEquals(await ctx.getSignedKeyOwner(), null);
|
|
1485
|
+
fail = false;
|
|
1486
|
+
assertEquals(await ctx.getSignedKeyOwner(), null);
|
|
1487
|
+
assertEquals(loads, {
|
|
1488
|
+
key: 1,
|
|
1489
|
+
owner: 1
|
|
1490
|
+
});
|
|
1491
|
+
assert(await ctx.getSignedKeyOwner({ documentLoader: mockDocumentLoader }));
|
|
1492
|
+
});
|
|
1493
|
+
await t.step("rechecks an owner after another loader verifies the key", async () => {
|
|
1494
|
+
let fail = true;
|
|
1495
|
+
const { ctx, loads } = setup({ load: async (url, options) => {
|
|
1496
|
+
if (url === keyId && fail) throw new TypeError("fetch failed");
|
|
1497
|
+
return await mockDocumentLoader(url, options);
|
|
1498
|
+
} });
|
|
1499
|
+
assertEquals(await ctx.getSignedKeyOwner(), null);
|
|
1500
|
+
fail = false;
|
|
1501
|
+
assertEquals(await ctx.getSignedKeyOwner(), null);
|
|
1502
|
+
assertEquals(loads, {
|
|
1503
|
+
key: 1,
|
|
1504
|
+
owner: 0
|
|
1505
|
+
});
|
|
1506
|
+
assert(await ctx.getSignedKey({ documentLoader: mockDocumentLoader }));
|
|
1507
|
+
assertEquals((await ctx.getSignedKeyOwner())?.id?.href, ownerId);
|
|
1508
|
+
assertEquals(loads, {
|
|
1509
|
+
key: 1,
|
|
1510
|
+
owner: 1
|
|
1511
|
+
});
|
|
1512
|
+
});
|
|
1513
|
+
await t.step("retries unexpected key rejections from a rejection handler", async () => {
|
|
1514
|
+
exporter.clear();
|
|
1515
|
+
const failure = /* @__PURE__ */ new Error("transient context loader failure");
|
|
1516
|
+
let rejectContext = true;
|
|
1517
|
+
const contextLoader = async (url, options) => {
|
|
1518
|
+
if (rejectContext) throw failure;
|
|
1519
|
+
return await mockDocumentLoader(url, options);
|
|
1520
|
+
};
|
|
1521
|
+
const { ctx, loads } = setup();
|
|
1522
|
+
const options = { contextLoader };
|
|
1523
|
+
const failedKeys = Promise.allSettled([
|
|
1524
|
+
ctx.getSignedKey(options),
|
|
1525
|
+
ctx.getSignedKeyOwner(options),
|
|
1526
|
+
ctx.getSignedKey(options)
|
|
1527
|
+
]);
|
|
1528
|
+
const recovery = ctx.getSignedKey(options).catch(() => {
|
|
1529
|
+
rejectContext = false;
|
|
1530
|
+
return ctx.getSignedKey(options);
|
|
1531
|
+
});
|
|
1532
|
+
for (const result of await failedKeys) assertEquals(result.status, "rejected");
|
|
1533
|
+
assert(await recovery);
|
|
1534
|
+
assertEquals(loads, {
|
|
1535
|
+
key: 2,
|
|
1536
|
+
owner: 0
|
|
1537
|
+
});
|
|
1538
|
+
assertEquals(exporter.getSpans("http_signatures.verify").length, 2);
|
|
1539
|
+
});
|
|
1540
|
+
await t.step("retries owner rejections while retaining the verified key", async () => {
|
|
1541
|
+
exporter.clear();
|
|
1542
|
+
let rejectContext = false;
|
|
1543
|
+
const contextLoader = async (url, options) => {
|
|
1544
|
+
if (rejectContext) throw new Error("transient owner context failure");
|
|
1545
|
+
return await mockDocumentLoader(url, options);
|
|
1546
|
+
};
|
|
1547
|
+
const { ctx, loads } = setup();
|
|
1548
|
+
const options = { contextLoader };
|
|
1549
|
+
assert(await ctx.getSignedKey(options));
|
|
1550
|
+
assertEquals(loads, {
|
|
1551
|
+
key: 1,
|
|
1552
|
+
owner: 0
|
|
1553
|
+
});
|
|
1554
|
+
rejectContext = true;
|
|
1555
|
+
const failures = await Promise.allSettled([ctx.getSignedKeyOwner(options), ctx.getSignedKeyOwner(options)]);
|
|
1556
|
+
for (const result of failures) assertEquals(result.status, "rejected");
|
|
1557
|
+
assertEquals(loads, {
|
|
1558
|
+
key: 1,
|
|
1559
|
+
owner: 1
|
|
1560
|
+
});
|
|
1561
|
+
rejectContext = false;
|
|
1562
|
+
assert(await ctx.getSignedKeyOwner(options));
|
|
1563
|
+
assertEquals(loads, {
|
|
1564
|
+
key: 1,
|
|
1565
|
+
owner: 2
|
|
1566
|
+
});
|
|
1567
|
+
assertEquals(exporter.getSpans("http_signatures.verify").length, 1);
|
|
1568
|
+
});
|
|
1569
|
+
await t.step("keeps clones and separate requests independent", async () => {
|
|
1570
|
+
exporter.clear();
|
|
1571
|
+
const { ctx, loads, federation } = setup();
|
|
1572
|
+
assert(await ctx.getSignedKeyOwner());
|
|
1573
|
+
const clone = ctx.clone(void 0);
|
|
1574
|
+
const separate = federation.createContext(new Request(request), void 0);
|
|
1575
|
+
await Promise.all([clone.getSignedKeyOwner(), separate.getSignedKeyOwner()]);
|
|
1576
|
+
assertEquals(loads, {
|
|
1577
|
+
key: 3,
|
|
1578
|
+
owner: 3
|
|
1579
|
+
});
|
|
1580
|
+
assertEquals(exporter.getSpans("http_signatures.verify").length, 3);
|
|
1581
|
+
});
|
|
1582
|
+
} finally {
|
|
1583
|
+
await tracerProvider.shutdown();
|
|
1584
|
+
}
|
|
1585
|
+
});
|
|
1179
1586
|
test("Federation.fetch() [rfc9421] multiple POST signatures", async (t) => {
|
|
1180
1587
|
for (const [name, validSecond, maxHttpSignatures] of [
|
|
1181
1588
|
[
|
|
@@ -1278,7 +1685,7 @@ test("Federation.fetch()", async (t) => {
|
|
|
1278
1685
|
kv,
|
|
1279
1686
|
documentLoaderFactory: () => mockDocumentLoader,
|
|
1280
1687
|
authenticatedDocumentLoaderFactory(identity) {
|
|
1281
|
-
const docLoader = getAuthenticatedDocumentLoader(identity);
|
|
1688
|
+
const docLoader = getAuthenticatedDocumentLoader(identity, { allowPrivateAddress: true });
|
|
1282
1689
|
return (url) => {
|
|
1283
1690
|
if (new URL(url).host === "example.com") return docLoader(url);
|
|
1284
1691
|
return mockDocumentLoader(url);
|
|
@@ -1729,7 +2136,7 @@ test("Federation.setInboxListeners()", async (t) => {
|
|
|
1729
2136
|
kv,
|
|
1730
2137
|
documentLoaderFactory: () => mockDocumentLoader,
|
|
1731
2138
|
authenticatedDocumentLoaderFactory(identity) {
|
|
1732
|
-
const docLoader = getAuthenticatedDocumentLoader(identity);
|
|
2139
|
+
const docLoader = getAuthenticatedDocumentLoader(identity, { allowPrivateAddress: true });
|
|
1733
2140
|
return (url) => {
|
|
1734
2141
|
const urlObj = new URL(url);
|
|
1735
2142
|
authenticatedRequests.push([url, identity.keyId.href]);
|
|
@@ -2052,7 +2459,7 @@ test("Federation.setInboxListeners()", async (t) => {
|
|
|
2052
2459
|
kv,
|
|
2053
2460
|
documentLoaderFactory: () => mockDocumentLoader,
|
|
2054
2461
|
authenticatedDocumentLoaderFactory(identity) {
|
|
2055
|
-
const docLoader = getAuthenticatedDocumentLoader(identity);
|
|
2462
|
+
const docLoader = getAuthenticatedDocumentLoader(identity, { allowPrivateAddress: true });
|
|
2056
2463
|
return (url) => {
|
|
2057
2464
|
if (new URL(url).host === "example.com") return docLoader(url);
|
|
2058
2465
|
return mockDocumentLoader(url);
|
|
@@ -2265,10 +2672,11 @@ test("Federation.setOutboxListeners()", async (t) => {
|
|
|
2265
2672
|
sinks: ["buffer"]
|
|
2266
2673
|
}]
|
|
2267
2674
|
});
|
|
2268
|
-
const federation =
|
|
2675
|
+
const federation = new FederationImpl({
|
|
2269
2676
|
kv,
|
|
2270
2677
|
documentLoaderFactory: () => mockDocumentLoader
|
|
2271
2678
|
});
|
|
2679
|
+
federation.allowPrivateAddress = true;
|
|
2272
2680
|
federation.setActorDispatcher("/users/{identifier}", (_ctx, identifier) => identifier === "john" ? new vocab.Person({}) : null).setKeyPairsDispatcher(() => [{
|
|
2273
2681
|
privateKey: rsaPrivateKey2,
|
|
2274
2682
|
publicKey: rsaPublicKey2.publicKey
|
|
@@ -2371,10 +2779,11 @@ test("Federation.setOutboxListeners()", async (t) => {
|
|
|
2371
2779
|
sinks: ["buffer"]
|
|
2372
2780
|
}]
|
|
2373
2781
|
});
|
|
2374
|
-
const federation =
|
|
2782
|
+
const federation = new FederationImpl({
|
|
2375
2783
|
kv,
|
|
2376
2784
|
documentLoaderFactory: () => mockDocumentLoader
|
|
2377
2785
|
});
|
|
2786
|
+
federation.allowPrivateAddress = true;
|
|
2378
2787
|
federation.setActorDispatcher("/{identifier}", (_ctx, identifier) => identifier === "person2" ? new vocab.Person({}) : null).setKeyPairsDispatcher(() => [{
|
|
2379
2788
|
privateKey: rsaPrivateKey2,
|
|
2380
2789
|
publicKey: rsaPublicKey2.publicKey
|
|
@@ -3054,7 +3463,7 @@ test("FederationImpl.sendActivity()", async (t) => {
|
|
|
3054
3463
|
});
|
|
3055
3464
|
const federation = new FederationImpl({
|
|
3056
3465
|
kv: new MemoryKvStore(),
|
|
3057
|
-
|
|
3466
|
+
allowPrivateAddress: true
|
|
3058
3467
|
});
|
|
3059
3468
|
const context = federation.createContext(new URL("https://example.com/"));
|
|
3060
3469
|
await t.step("success", async () => {
|
|
@@ -6689,7 +7098,7 @@ test("ContextImpl.sendActivity()", async (t) => {
|
|
|
6689
7098
|
const kv = new MemoryKvStore();
|
|
6690
7099
|
const federation = new FederationImpl({
|
|
6691
7100
|
kv,
|
|
6692
|
-
|
|
7101
|
+
allowPrivateAddress: true
|
|
6693
7102
|
});
|
|
6694
7103
|
federation.setActorDispatcher("/{identifier}", async (ctx, identifier) => {
|
|
6695
7104
|
if (identifier !== "1") return null;
|
|
@@ -6812,11 +7221,13 @@ test("ContextImpl.sendActivity()", async (t) => {
|
|
|
6812
7221
|
});
|
|
6813
7222
|
await t.step("records recipient span attributes correctly", async () => {
|
|
6814
7223
|
const [tracerProvider, exporter] = createTestTracerProvider();
|
|
6815
|
-
const
|
|
7224
|
+
const federation3 = new FederationImpl({
|
|
6816
7225
|
kv,
|
|
6817
7226
|
contextLoaderFactory: () => mockDocumentLoader,
|
|
6818
7227
|
tracerProvider
|
|
6819
|
-
})
|
|
7228
|
+
});
|
|
7229
|
+
federation3.allowPrivateAddress = true;
|
|
7230
|
+
const ctx = federation3.createContext(new URL("https://example.com/"), void 0);
|
|
6820
7231
|
const activity = new vocab.Create({
|
|
6821
7232
|
id: new URL("https://example.com/activity/telemetry"),
|
|
6822
7233
|
actor: new URL("https://example.com/person"),
|
|
@@ -6850,7 +7261,7 @@ test("ContextImpl.sendActivity()", async (t) => {
|
|
|
6850
7261
|
};
|
|
6851
7262
|
const federation2 = new FederationImpl({
|
|
6852
7263
|
kv,
|
|
6853
|
-
|
|
7264
|
+
allowPrivateAddress: true,
|
|
6854
7265
|
queue
|
|
6855
7266
|
});
|
|
6856
7267
|
federation2.setActorDispatcher("/{identifier}", async (ctx, identifier) => {
|
|
@@ -7021,7 +7432,7 @@ test("ContextImpl.sendActivity()", async (t) => {
|
|
|
7021
7432
|
const asyncEnqueued = [];
|
|
7022
7433
|
const fed = new FederationImpl({
|
|
7023
7434
|
kv,
|
|
7024
|
-
|
|
7435
|
+
allowPrivateAddress: true,
|
|
7025
7436
|
queue: {
|
|
7026
7437
|
enqueue(message) {
|
|
7027
7438
|
return new Promise((resolve) => {
|
|
@@ -7248,10 +7659,10 @@ test({
|
|
|
7248
7659
|
const signedInvite = await signObject(new vocab.Invite({ actor: new URL("https://example.com/person2") }), ed25519PrivateKey, ed25519Multikey.id);
|
|
7249
7660
|
assert(await ctx.routeActivity("id", signedInvite));
|
|
7250
7661
|
assertEquals(activities, [[null, signedOffer], ["id", signedInvite]]);
|
|
7251
|
-
assertFalse(await ctx.routeActivity(null, new vocab.Create({
|
|
7662
|
+
assertFalse(await withDnsLookup(() => Promise.reject(/* @__PURE__ */ new Error("Resolver unavailable")), () => ctx.routeActivity(null, new vocab.Create({
|
|
7252
7663
|
id: new URL("https://example.com/not-found"),
|
|
7253
7664
|
actor: new URL("https://example.com/person")
|
|
7254
|
-
})));
|
|
7665
|
+
}))));
|
|
7255
7666
|
assertEquals(activities, [[null, signedOffer], ["id", signedInvite]]);
|
|
7256
7667
|
assertFalse(await ctx.routeActivity(null, new vocab.Create({
|
|
7257
7668
|
id: new URL("https://example.com/person"),
|
|
@@ -7758,7 +8169,7 @@ test("InboxContextImpl.forwardActivity()", async (t) => {
|
|
|
7758
8169
|
const kv = new MemoryKvStore();
|
|
7759
8170
|
const federation = new FederationImpl({
|
|
7760
8171
|
kv,
|
|
7761
|
-
|
|
8172
|
+
allowPrivateAddress: true
|
|
7762
8173
|
});
|
|
7763
8174
|
await t.step("skip", async () => {
|
|
7764
8175
|
await new InboxContextImpl(null, {
|
|
@@ -7881,6 +8292,7 @@ test("InboxContextImpl.forwardActivity()", async (t) => {
|
|
|
7881
8292
|
contextLoaderFactory: () => mockDocumentLoader,
|
|
7882
8293
|
tracerProvider
|
|
7883
8294
|
});
|
|
8295
|
+
federationWithTracing.allowPrivateAddress = true;
|
|
7884
8296
|
await new InboxContextImpl(null, await signJsonLd({
|
|
7885
8297
|
"@context": "https://www.w3.org/ns/activitystreams",
|
|
7886
8298
|
"type": "Create",
|
|
@@ -8004,12 +8416,14 @@ test("createFederation() applies httpMessageSignaturesSpecTtl to remembered spec
|
|
|
8004
8416
|
return new Response(null, { status: key == null ? 401 : 202 });
|
|
8005
8417
|
});
|
|
8006
8418
|
const kv = new TtlRecordingKvStore();
|
|
8007
|
-
const
|
|
8419
|
+
const federation = new FederationImpl({
|
|
8008
8420
|
kv,
|
|
8009
8421
|
documentLoaderFactory: () => mockDocumentLoader,
|
|
8010
8422
|
contextLoaderFactory: () => mockDocumentLoader,
|
|
8011
8423
|
httpMessageSignaturesSpecTtl: { days: 14 }
|
|
8012
|
-
})
|
|
8424
|
+
});
|
|
8425
|
+
federation.allowPrivateAddress = true;
|
|
8426
|
+
const ctx = federation.createContext(new URL("https://example.com/"), void 0);
|
|
8013
8427
|
const specKey = [
|
|
8014
8428
|
"_fedify",
|
|
8015
8429
|
"httpMessageSignaturesSpec",
|
|
@@ -8056,12 +8470,17 @@ test("createFederation() applies publicKeyTtl to cached public keys", async () =
|
|
|
8056
8470
|
});
|
|
8057
8471
|
const keyId = "https://example.com/person2#key3";
|
|
8058
8472
|
const kv = new TtlRecordingKvStore();
|
|
8059
|
-
const federation =
|
|
8473
|
+
const federation = new FederationImpl({
|
|
8060
8474
|
kv,
|
|
8061
8475
|
documentLoaderFactory: () => mockDocumentLoader,
|
|
8062
8476
|
contextLoaderFactory: () => mockDocumentLoader,
|
|
8477
|
+
authenticatedDocumentLoaderFactory: (identity, options) => getAuthenticatedDocumentLoader(identity, {
|
|
8478
|
+
...options,
|
|
8479
|
+
allowPrivateAddress: true
|
|
8480
|
+
}),
|
|
8063
8481
|
publicKeyTtl: { days: 7 }
|
|
8064
8482
|
});
|
|
8483
|
+
federation.allowPrivateAddress = true;
|
|
8065
8484
|
const inbox = [];
|
|
8066
8485
|
federation.setActorDispatcher("/users/{identifier}", (_, identifier) => identifier === "john" ? new vocab.Person({}) : null).setKeyPairsDispatcher(() => [{
|
|
8067
8486
|
privateKey: rsaPrivateKey2,
|
|
@@ -8683,5 +9102,19 @@ test("createFederation() validates documentLoaderTimeout", () => {
|
|
|
8683
9102
|
documentLoaderTimeout: Temporal.Duration.from({ seconds: 30 })
|
|
8684
9103
|
});
|
|
8685
9104
|
});
|
|
9105
|
+
/**
|
|
9106
|
+
* Runs the given function while `node:dns/promises`' `lookup()`, which the
|
|
9107
|
+
* default public fetch resolves hostnames with, is replaced, so that no test
|
|
9108
|
+
* depends on the real DNS.
|
|
9109
|
+
*/
|
|
9110
|
+
async function withDnsLookup(lookup, fn) {
|
|
9111
|
+
const originalLookup = dns.lookup;
|
|
9112
|
+
dns.lookup = lookup;
|
|
9113
|
+
try {
|
|
9114
|
+
return await fn();
|
|
9115
|
+
} finally {
|
|
9116
|
+
dns.lookup = originalLookup;
|
|
9117
|
+
}
|
|
9118
|
+
}
|
|
8686
9119
|
//#endregion
|
|
8687
9120
|
export {};
|