@fedify/fedify 2.2.14-dev.2082 → 2.2.14

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.
Files changed (67) hide show
  1. package/dist/{builder-Cwgo1nK5.mjs → builder-ky-Enw7B.mjs} +2 -2
  2. package/dist/{client-CQ7zd8tW.mjs → client-CJ4Y0keQ.mjs} +7 -3
  3. package/dist/compat/transformers.test.mjs +1 -1
  4. package/dist/{deno-CV4wsQzy.mjs → deno-BnfWZfzD.mjs} +1 -1
  5. package/dist/docloader-Ctm-HtB1.mjs +79 -0
  6. package/dist/federation/builder.test.mjs +2 -2
  7. package/dist/federation/collection.test.mjs +1 -1
  8. package/dist/federation/handler.test.mjs +6 -6
  9. package/dist/federation/idempotency.test.mjs +3 -3
  10. package/dist/federation/kv.test.mjs +1 -1
  11. package/dist/federation/middleware.test.mjs +134 -7
  12. package/dist/federation/mod.cjs +1 -1
  13. package/dist/federation/mod.js +1 -1
  14. package/dist/federation/mq.test.mjs +98 -43
  15. package/dist/federation/negotiation.test.mjs +1 -1
  16. package/dist/federation/router.test.mjs +1 -1
  17. package/dist/federation/send.test.mjs +68 -5
  18. package/dist/federation/temporal.test.mjs +2 -2
  19. package/dist/federation/webfinger.test.mjs +2 -2
  20. package/dist/{http-C5T-lWqB.js → http-BBGyG2ez.js} +6 -2
  21. package/dist/{http-BWNuy1sb.cjs → http-DXyLMSXp.cjs} +6 -2
  22. package/dist/{http-C9MzFrOj.mjs → http-xscrPdwI.mjs} +7 -3
  23. package/dist/{key-DiziIx9e.mjs → key-DwyWxSP6.mjs} +1 -1
  24. package/dist/{kv-cache-DGPEHc1T.cjs → kv-cache-BATy5Vhq.cjs} +34 -8
  25. package/dist/{kv-cache-CM-zJoep.js → kv-cache-D_17I8jy.js} +35 -9
  26. package/dist/{ld-BAeuDKjx.mjs → ld-CQLGYzwy.mjs} +3 -3
  27. package/dist/{middleware-BbbKc-Tr.cjs → middleware-BbomR2ro.cjs} +14 -5
  28. package/dist/{middleware-J32whqNP.js → middleware-CdgK6YM3.js} +13 -4
  29. package/dist/{middleware-XNvq3NUZ.cjs → middleware-DRzTRfmd.cjs} +1 -1
  30. package/dist/{middleware-CkJOjZjS.mjs → middleware-Dv-j_sRD.mjs} +11 -11
  31. package/dist/{middleware-DA2CvTQL.mjs → middleware-kjzamfZM.mjs} +1 -1
  32. package/dist/{mod-DzPMtIV3.d.cts → mod-BYg1qMPB.d.cts} +2 -0
  33. package/dist/{mod-0MWo2Nu5.d.ts → mod-C9jqpInC.d.ts} +2 -0
  34. package/dist/mod.cjs +5 -5
  35. package/dist/mod.d.cts +1 -1
  36. package/dist/mod.d.ts +1 -1
  37. package/dist/mod.js +5 -5
  38. package/dist/nodeinfo/client.test.mjs +50 -2
  39. package/dist/nodeinfo/handler.test.mjs +2 -2
  40. package/dist/nodeinfo/mod.cjs +1 -1
  41. package/dist/nodeinfo/mod.js +1 -1
  42. package/dist/nodeinfo/types.test.mjs +1 -1
  43. package/dist/otel/exporter.test.mjs +1 -1
  44. package/dist/{owner-D08_JUSb.mjs → owner-pFTUYlVh.mjs} +2 -2
  45. package/dist/{proof-BdRnLnh7.js → proof-BCHw9az4.js} +2 -2
  46. package/dist/{proof-7T47tIu5.cjs → proof-BLZMJa2g.cjs} +2 -2
  47. package/dist/{proof-_S-wFUFM.mjs → proof-CJxlMyX3.mjs} +2 -2
  48. package/dist/{send-DmcKKipr.mjs → send-C_Pfo7Lr.mjs} +12 -3
  49. package/dist/sig/http.test.mjs +43 -3
  50. package/dist/sig/key.test.mjs +2 -2
  51. package/dist/sig/ld.test.mjs +5 -2
  52. package/dist/sig/mod.cjs +2 -2
  53. package/dist/sig/mod.js +2 -2
  54. package/dist/sig/owner.test.mjs +2 -2
  55. package/dist/sig/proof.test.mjs +2 -2
  56. package/dist/{std__assert-CRDpx_HF.mjs → std__assert-BVFj8-_V.mjs} +1 -26
  57. package/dist/{temporal-CX_L28b-.mjs → temporal-DA4sccOr.mjs} +1 -1
  58. package/dist/{types-DWs-qZaY.cjs → types-DRPjmHVV.cjs} +7 -3
  59. package/dist/{types-sXITXcWa.js → types-miAYfiGO.js} +7 -3
  60. package/dist/utils/docloader.test.mjs +299 -3
  61. package/dist/utils/mod.cjs +1 -1
  62. package/dist/utils/mod.d.cts +1 -1
  63. package/dist/utils/mod.d.ts +1 -1
  64. package/dist/utils/mod.js +1 -1
  65. package/dist/vocab/cjs.test.mjs +1 -1
  66. package/package.json +7 -7
  67. package/dist/docloader-DV9F08Dn.mjs +0 -53
@@ -3,17 +3,18 @@ import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as esm_default } from "../esm-DVILvP5e.mjs";
5
5
  import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
6
- import "../std__assert-CRDpx_HF.mjs";
6
+ import "../std__assert-BVFj8-_V.mjs";
7
7
  import { n as assertFalse, t as assertRejects } from "../assert_rejects-B-qJtC9Z.mjs";
8
8
  import { n as assertInstanceOf, t as assert } from "../assert-BhQd8QHP.mjs";
9
9
  import { t as assertNotEquals } from "../assert_not_equals--wG9hV7u.mjs";
10
- import { l as verifyRequest } from "../http-C9MzFrOj.mjs";
10
+ import { l as verifyRequest } from "../http-xscrPdwI.mjs";
11
11
  import { i as rsaPrivateKey2, n as ed25519PrivateKey, s as rsaPublicKey2, t as ed25519Multikey } from "../keys-BAK-tUlf.mjs";
12
- import { t as doesActorOwnKey } from "../owner-D08_JUSb.mjs";
13
- import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "../send-DmcKKipr.mjs";
12
+ import { t as doesActorOwnKey } from "../owner-pFTUYlVh.mjs";
13
+ import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "../send-C_Pfo7Lr.mjs";
14
14
  import { createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
15
15
  import { Activity, Application, Endpoints, Group, Person, Service } from "@fedify/vocab";
16
- import { UrlError } from "@fedify/vocab-runtime";
16
+ import { FetchError, UrlError } from "@fedify/vocab-runtime";
17
+ import dns from "node:dns/promises";
17
18
  //#region src/federation/send.test.ts
18
19
  test("extractInboxes()", () => {
19
20
  const recipients = [
@@ -547,5 +548,67 @@ for (const signed of [false, true]) {
547
548
  });
548
549
  });
549
550
  }
551
+ for (const signed of [false, true]) test({
552
+ name: `sendActivity() classifies DNS failures (signed: ${signed})`,
553
+ ignore: "Deno" in globalThis && (await Deno.permissions.query({ name: "net" })).state !== "granted",
554
+ async fn(t) {
555
+ const activity = {
556
+ type: "Create",
557
+ id: "https://example.com/activity"
558
+ };
559
+ const destination = "https://delivery.invalid/inbox";
560
+ const publicInbox = "https://8.8.8.8/inbox";
561
+ const keys = signed ? [{
562
+ privateKey: rsaPrivateKey2,
563
+ keyId: rsaPublicKey2.id
564
+ }] : [];
565
+ for (const result of [
566
+ "throws",
567
+ "empty",
568
+ "cname",
569
+ "private"
570
+ ]) for (const redirected of [false, true]) await t.step(`${result}, redirected: ${redirected}`, async () => {
571
+ const originalLookup = dns.lookup;
572
+ const resolverError = /* @__PURE__ */ new Error("Resolver unavailable");
573
+ const lookups = [];
574
+ dns.lookup = ((hostname, options) => {
575
+ lookups.push(hostname);
576
+ assertEquals(hostname, "delivery.invalid");
577
+ assertEquals(options, { all: true });
578
+ if (result === "throws") return Promise.reject(resolverError);
579
+ return Promise.resolve(result === "empty" ? [] : [{
580
+ address: result === "private" ? "127.0.0.1" : "alias.invalid.",
581
+ family: 4
582
+ }]);
583
+ });
584
+ try {
585
+ esm_default.mockGlobal().catch(202);
586
+ if (redirected) esm_default.route(publicInbox, {
587
+ status: 307,
588
+ headers: { Location: destination }
589
+ });
590
+ const send = () => sendActivity({
591
+ activity,
592
+ keys,
593
+ inbox: new URL(redirected ? publicInbox : destination)
594
+ });
595
+ if (result === "private") assertEquals((await assertRejects(send, UrlError)).reason, "disallowed");
596
+ else {
597
+ const error = await assertRejects(send, FetchError);
598
+ assertEquals(error.url.href, destination);
599
+ assertInstanceOf(error.cause, UrlError);
600
+ assertEquals(error.cause.reason, "dns");
601
+ assertEquals(error.cause.cause, result === "throws" ? resolverError : void 0);
602
+ }
603
+ assertEquals(lookups, ["delivery.invalid"]);
604
+ assertEquals(esm_default.callHistory.calls(destination).length, 0);
605
+ assertEquals(esm_default.callHistory.calls().length, redirected ? 1 : 0);
606
+ } finally {
607
+ dns.lookup = originalLookup;
608
+ esm_default.hardReset();
609
+ }
610
+ });
611
+ }
612
+ });
550
613
  //#endregion
551
614
  export {};
@@ -1,10 +1,10 @@
1
1
  import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import "../std__assert-CRDpx_HF.mjs";
4
+ import "../std__assert-BVFj8-_V.mjs";
5
5
  import { n as assertFalse } from "../assert_rejects-B-qJtC9Z.mjs";
6
6
  import { t as assert } from "../assert-BhQd8QHP.mjs";
7
- import { t as hasMalformedKnownTemporalLiteral } from "../temporal-CX_L28b-.mjs";
7
+ import { t as hasMalformedKnownTemporalLiteral } from "../temporal-DA4sccOr.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 () => {
@@ -3,9 +3,9 @@ import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { r as createRequestContext } from "../context-Dk_tacqz.mjs";
5
5
  import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
6
- import "../std__assert-CRDpx_HF.mjs";
6
+ import "../std__assert-BVFj8-_V.mjs";
7
7
  import { t as MemoryKvStore } from "../kv-CX8qfaad.mjs";
8
- import { o as createFederation, s as handleWebFinger } from "../middleware-CkJOjZjS.mjs";
8
+ import { o as createFederation, s as handleWebFinger } from "../middleware-Dv-j_sRD.mjs";
9
9
  import { test } from "@fedify/fixture";
10
10
  import { Image, Link, Person, Tombstone } from "@fedify/vocab";
11
11
  //#region src/federation/webfinger.test.ts
@@ -10,7 +10,7 @@ import { ATTR_HTTP_REQUEST_HEADER, ATTR_HTTP_REQUEST_METHOD, ATTR_URL_FULL } fro
10
10
  import { decodeBase64, encodeBase64 } from "byte-encodings/base64";
11
11
  //#region deno.json
12
12
  var name = "@fedify/fedify";
13
- var version = "2.2.14-dev.2082+79cd52e9";
13
+ var version = "2.2.14";
14
14
  //#endregion
15
15
  //#region src/sig/accept.ts
16
16
  /**
@@ -1314,6 +1314,8 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
1314
1314
  return invalidSignatureResult(null);
1315
1315
  }
1316
1316
  let failure = noSignatureResult();
1317
+ let body = null;
1318
+ let digestValid = null;
1317
1319
  for (const sigName of signatureNames) {
1318
1320
  if (!signatures[sigName]) {
1319
1321
  failure = invalidSignatureResult(parseKeyId(signatureInputs[sigName]?.keyId));
@@ -1365,7 +1367,9 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
1365
1367
  failure = invalidSignatureResult(keyId);
1366
1368
  continue;
1367
1369
  }
1368
- if (!await verifyRfc9421ContentDigest(contentDigestHeader, await request.arrayBuffer())) {
1370
+ body ??= await request.arrayBuffer();
1371
+ digestValid ??= await verifyRfc9421ContentDigest(contentDigestHeader, body);
1372
+ if (!digestValid) {
1369
1373
  logger.debug("Failed to verify; Content-Digest verification failed.", { contentDigest: contentDigestHeader });
1370
1374
  failure = invalidSignatureResult(keyId);
1371
1375
  continue;
@@ -11,7 +11,7 @@ let _opentelemetry_semantic_conventions = require("@opentelemetry/semantic-conve
11
11
  let byte_encodings_base64 = require("byte-encodings/base64");
12
12
  //#region deno.json
13
13
  var name = "@fedify/fedify";
14
- var version = "2.2.14-dev.2082+79cd52e9";
14
+ var version = "2.2.14";
15
15
  //#endregion
16
16
  //#region src/sig/accept.ts
17
17
  /**
@@ -1315,6 +1315,8 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
1315
1315
  return invalidSignatureResult(null);
1316
1316
  }
1317
1317
  let failure = noSignatureResult();
1318
+ let body = null;
1319
+ let digestValid = null;
1318
1320
  for (const sigName of signatureNames) {
1319
1321
  if (!signatures[sigName]) {
1320
1322
  failure = invalidSignatureResult(parseKeyId(signatureInputs[sigName]?.keyId));
@@ -1366,7 +1368,9 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
1366
1368
  failure = invalidSignatureResult(keyId);
1367
1369
  continue;
1368
1370
  }
1369
- if (!await verifyRfc9421ContentDigest(contentDigestHeader, await request.arrayBuffer())) {
1371
+ body ??= await request.arrayBuffer();
1372
+ digestValid ??= await verifyRfc9421ContentDigest(contentDigestHeader, body);
1373
+ if (!digestValid) {
1370
1374
  logger.debug("Failed to verify; Content-Digest verification failed.", { contentDigest: contentDigestHeader });
1371
1375
  failure = invalidSignatureResult(keyId);
1372
1376
  continue;
@@ -1,9 +1,9 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-CV4wsQzy.mjs";
4
+ import { n as version, t as name } from "./deno-BnfWZfzD.mjs";
5
5
  import { i as validateAcceptSignature, n as fulfillAcceptSignature, r as parseAcceptSignature } from "./accept-Dd__NiUL.mjs";
6
- import { i as fetchKeyDetailed, s as validateCryptoKey } from "./key-DiziIx9e.mjs";
6
+ import { i as fetchKeyDetailed, s as validateCryptoKey } from "./key-DwyWxSP6.mjs";
7
7
  import { CryptographicKey } from "@fedify/vocab";
8
8
  import { SpanStatusCode, trace } from "@opentelemetry/api";
9
9
  import { FetchError } from "@fedify/vocab-runtime";
@@ -674,6 +674,8 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
674
674
  return invalidSignatureResult(null);
675
675
  }
676
676
  let failure = noSignatureResult();
677
+ let body = null;
678
+ let digestValid = null;
677
679
  for (const sigName of signatureNames) {
678
680
  if (!signatures[sigName]) {
679
681
  failure = invalidSignatureResult(parseKeyId(signatureInputs[sigName]?.keyId));
@@ -725,7 +727,9 @@ async function verifyRequestRfc9421(request, span, { documentLoader, contextLoad
725
727
  failure = invalidSignatureResult(keyId);
726
728
  continue;
727
729
  }
728
- if (!await verifyRfc9421ContentDigest(contentDigestHeader, await request.arrayBuffer())) {
730
+ body ??= await request.arrayBuffer();
731
+ digestValid ??= await verifyRfc9421ContentDigest(contentDigestHeader, body);
732
+ if (!digestValid) {
729
733
  logger.debug("Failed to verify; Content-Digest verification failed.", { contentDigest: contentDigestHeader });
730
734
  failure = invalidSignatureResult(keyId);
731
735
  continue;
@@ -1,7 +1,7 @@
1
1
  import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-CV4wsQzy.mjs";
4
+ import { n as version, t as name } from "./deno-BnfWZfzD.mjs";
5
5
  import { CryptographicKey, Object as Object$1, isActor } from "@fedify/vocab";
6
6
  import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
7
7
  import { FetchError, getDocumentLoader } from "@fedify/vocab-runtime";
@@ -1,7 +1,7 @@
1
1
  const { Temporal } = require("@js-temporal/polyfill");
2
2
  const { URLPattern } = require("urlpattern-polyfill");
3
3
  require("./chunk-DDcVe30Y.cjs");
4
- const require_http = require("./http-BWNuy1sb.cjs");
4
+ const require_http = require("./http-DXyLMSXp.cjs");
5
5
  let _logtape_logtape = require("@logtape/logtape");
6
6
  let es_toolkit = require("es-toolkit");
7
7
  let _fedify_vocab_runtime = require("@fedify/vocab-runtime");
@@ -11,10 +11,13 @@ const logger$1 = (0, _logtape_logtape.getLogger)([
11
11
  "utils",
12
12
  "docloader"
13
13
  ]);
14
+ const DEFAULT_MAX_REDIRECTION = 20;
14
15
  /**
15
16
  * Gets an authenticated {@link DocumentLoader} for the given identity.
16
17
  * Note that an authenticated document loader intentionally does not cache
17
18
  * the fetched documents.
19
+ * At most 20 HTTP redirects and alternate document links are followed in total
20
+ * per call. Revisiting a URL within that chain throws a {@link FetchError}.
18
21
  * @param identity The identity to get the document loader for.
19
22
  * The actor's key pair.
20
23
  * @param options The options for the document loader.
@@ -24,29 +27,52 @@ const logger$1 = (0, _logtape_logtape.getLogger)([
24
27
  */
25
28
  function getAuthenticatedDocumentLoader(identity, { allowPrivateAddress, maxRedirection, userAgent, specDeterminer, tracerProvider } = {}) {
26
29
  require_http.validateCryptoKey(identity.privateKey);
27
- async function load(url, options) {
28
- await validateUrl(url);
29
- return (0, _fedify_vocab_runtime.getRemoteDocument)(url, await require_http.doubleKnock((0, _fedify_vocab_runtime.createActivityPubRequest)(url, { userAgent }), identity, {
30
+ async function load(url, options, redirected = 0, visited = /* @__PURE__ */ new Set()) {
31
+ options?.signal?.throwIfAborted();
32
+ let currentUrl = new URL(url).href;
33
+ await validateUrl(currentUrl);
34
+ visited.add(currentUrl);
35
+ const originalRequest = (0, _fedify_vocab_runtime.createActivityPubRequest)(currentUrl, { userAgent });
36
+ function follow(nextUrl) {
37
+ options?.signal?.throwIfAborted();
38
+ if (redirected >= DEFAULT_MAX_REDIRECTION) throw new _fedify_vocab_runtime.FetchError(currentUrl, `Too many redirections (${redirected + 1})`);
39
+ if (visited.has(nextUrl)) throw new _fedify_vocab_runtime.FetchError(currentUrl, `Redirect loop detected: ${nextUrl}`);
40
+ redirected++;
41
+ }
42
+ const response = await require_http.doubleKnock(originalRequest, identity, {
30
43
  maxRedirection,
31
44
  specDeterminer,
32
45
  log: (0, es_toolkit.curry)(_fedify_vocab_runtime.logRequest)(logger$1),
33
46
  tracerProvider,
34
47
  signal: options?.signal,
35
- validateRedirect: validateUrl
36
- }), load);
48
+ validateRedirect: async (nextUrl) => {
49
+ follow(nextUrl);
50
+ await validateUrl(nextUrl);
51
+ visited.add(nextUrl);
52
+ currentUrl = nextUrl;
53
+ }
54
+ });
55
+ return (0, _fedify_vocab_runtime.getRemoteDocument)(currentUrl, response, (alternateUrl) => {
56
+ follow(alternateUrl);
57
+ return load(alternateUrl, options, redirected, visited);
58
+ });
37
59
  }
38
60
  async function validateUrl(url) {
39
61
  if (!allowPrivateAddress) try {
40
62
  await (0, _fedify_vocab_runtime.validatePublicUrl)(url);
41
63
  } catch (error) {
42
- if (error instanceof _fedify_vocab_runtime.UrlError) logger$1.error("Disallowed private URL: {url}", {
64
+ if (error instanceof _fedify_vocab_runtime.UrlError) if (error.reason === "dns") logger$1.debug("DNS lookup failed for {url}", {
65
+ url,
66
+ error
67
+ });
68
+ else logger$1.error("Disallowed private URL: {url}", {
43
69
  url,
44
70
  error
45
71
  });
46
72
  throw error;
47
73
  }
48
74
  }
49
- return load;
75
+ return (url, options) => load(url, options);
50
76
  }
51
77
  //#endregion
52
78
  //#region src/utils/kv-cache.ts
@@ -1,19 +1,22 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
- import { f as validateCryptoKey, t as doubleKnock } from "./http-C5T-lWqB.js";
3
+ import { f as validateCryptoKey, t as doubleKnock } from "./http-BBGyG2ez.js";
4
4
  import { getLogger } from "@logtape/logtape";
5
5
  import { curry } from "es-toolkit";
6
- import { UrlError, createActivityPubRequest, getRemoteDocument, logRequest, preloadedContexts, validatePublicUrl } from "@fedify/vocab-runtime";
6
+ import { FetchError, UrlError, createActivityPubRequest, getRemoteDocument, logRequest, preloadedContexts, validatePublicUrl } from "@fedify/vocab-runtime";
7
7
  //#region src/utils/docloader.ts
8
8
  const logger$1 = getLogger([
9
9
  "fedify",
10
10
  "utils",
11
11
  "docloader"
12
12
  ]);
13
+ const DEFAULT_MAX_REDIRECTION = 20;
13
14
  /**
14
15
  * Gets an authenticated {@link DocumentLoader} for the given identity.
15
16
  * Note that an authenticated document loader intentionally does not cache
16
17
  * the fetched documents.
18
+ * At most 20 HTTP redirects and alternate document links are followed in total
19
+ * per call. Revisiting a URL within that chain throws a {@link FetchError}.
17
20
  * @param identity The identity to get the document loader for.
18
21
  * The actor's key pair.
19
22
  * @param options The options for the document loader.
@@ -23,29 +26,52 @@ const logger$1 = getLogger([
23
26
  */
24
27
  function getAuthenticatedDocumentLoader(identity, { allowPrivateAddress, maxRedirection, userAgent, specDeterminer, tracerProvider } = {}) {
25
28
  validateCryptoKey(identity.privateKey);
26
- async function load(url, options) {
27
- await validateUrl(url);
28
- return getRemoteDocument(url, await doubleKnock(createActivityPubRequest(url, { userAgent }), identity, {
29
+ async function load(url, options, redirected = 0, visited = /* @__PURE__ */ new Set()) {
30
+ options?.signal?.throwIfAborted();
31
+ let currentUrl = new URL(url).href;
32
+ await validateUrl(currentUrl);
33
+ visited.add(currentUrl);
34
+ const originalRequest = createActivityPubRequest(currentUrl, { userAgent });
35
+ function follow(nextUrl) {
36
+ options?.signal?.throwIfAborted();
37
+ if (redirected >= DEFAULT_MAX_REDIRECTION) throw new FetchError(currentUrl, `Too many redirections (${redirected + 1})`);
38
+ if (visited.has(nextUrl)) throw new FetchError(currentUrl, `Redirect loop detected: ${nextUrl}`);
39
+ redirected++;
40
+ }
41
+ const response = await doubleKnock(originalRequest, identity, {
29
42
  maxRedirection,
30
43
  specDeterminer,
31
44
  log: curry(logRequest)(logger$1),
32
45
  tracerProvider,
33
46
  signal: options?.signal,
34
- validateRedirect: validateUrl
35
- }), load);
47
+ validateRedirect: async (nextUrl) => {
48
+ follow(nextUrl);
49
+ await validateUrl(nextUrl);
50
+ visited.add(nextUrl);
51
+ currentUrl = nextUrl;
52
+ }
53
+ });
54
+ return getRemoteDocument(currentUrl, response, (alternateUrl) => {
55
+ follow(alternateUrl);
56
+ return load(alternateUrl, options, redirected, visited);
57
+ });
36
58
  }
37
59
  async function validateUrl(url) {
38
60
  if (!allowPrivateAddress) try {
39
61
  await validatePublicUrl(url);
40
62
  } catch (error) {
41
- if (error instanceof UrlError) logger$1.error("Disallowed private URL: {url}", {
63
+ if (error instanceof UrlError) if (error.reason === "dns") logger$1.debug("DNS lookup failed for {url}", {
64
+ url,
65
+ error
66
+ });
67
+ else logger$1.error("Disallowed private URL: {url}", {
42
68
  url,
43
69
  error
44
70
  });
45
71
  throw error;
46
72
  }
47
73
  }
48
- return load;
74
+ return (url, options) => load(url, options);
49
75
  }
50
76
  //#endregion
51
77
  //#region src/utils/kv-cache.ts
@@ -1,8 +1,8 @@
1
1
  import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-CV4wsQzy.mjs";
5
- import { r as fetchKey, s as validateCryptoKey } from "./key-DiziIx9e.mjs";
4
+ import { n as version, t as name } from "./deno-BnfWZfzD.mjs";
5
+ import { r as fetchKey, s as validateCryptoKey } from "./key-DwyWxSP6.mjs";
6
6
  import { Activity, CryptographicKey, Object as Object$1, getTypeId } from "@fedify/vocab";
7
7
  import { SpanStatusCode, trace } from "@opentelemetry/api";
8
8
  import { getDocumentLoader } from "@fedify/vocab-runtime";
@@ -203,7 +203,7 @@ function normalizeContextReference(reference, baseUrl) {
203
203
  /** @internal */
204
204
  function isInvalidUrlTypeError(error) {
205
205
  const code = error.code;
206
- return error instanceof TypeError && (code === "ERR_INVALID_URL" || /^Invalid URL(?::|$)/.test(error.message) || / cannot be parsed as a URL\.?$/.test(error.message));
206
+ return error instanceof TypeError && (code === "ERR_INVALID_URL" || error.message === "Invalid URL string." || /^Invalid URL(?::|$)/.test(error.message) || / cannot be parsed as a URL\.?$/.test(error.message));
207
207
  }
208
208
  async function applyGraphAliasContext(state, context, documentLoader, remoteContextCache, baseUrl = null, processingContexts = /* @__PURE__ */ new Set()) {
209
209
  if (context === null) return createGraphAliasContextState();
@@ -2,10 +2,10 @@ const { Temporal } = require("@js-temporal/polyfill");
2
2
  const { URLPattern } = require("urlpattern-polyfill");
3
3
  const require_chunk = require("./chunk-DDcVe30Y.cjs");
4
4
  const require_transformers = require("./transformers-NeAONrAq.cjs");
5
- const require_http = require("./http-BWNuy1sb.cjs");
6
- const require_types = require("./types-DWs-qZaY.cjs");
7
- const require_proof = require("./proof-7T47tIu5.cjs");
8
- const require_kv_cache = require("./kv-cache-DGPEHc1T.cjs");
5
+ const require_http = require("./http-DXyLMSXp.cjs");
6
+ const require_types = require("./types-DRPjmHVV.cjs");
7
+ const require_proof = require("./proof-BLZMJa2g.cjs");
8
+ const require_kv_cache = require("./kv-cache-BATy5Vhq.cjs");
9
9
  let _logtape_logtape = require("@logtape/logtape");
10
10
  let _fedify_vocab = require("@fedify/vocab");
11
11
  let _opentelemetry_api = require("@opentelemetry/api");
@@ -212,7 +212,7 @@ var FederationBuilderImpl = class {
212
212
  this.collectionTypeIds = {};
213
213
  }
214
214
  async build(options) {
215
- const { FederationImpl } = await Promise.resolve().then(() => require("./middleware-XNvq3NUZ.cjs"));
215
+ const { FederationImpl } = await Promise.resolve().then(() => require("./middleware-DRzTRfmd.cjs"));
216
216
  const f = new FederationImpl(options);
217
217
  const trailingSlashInsensitiveValue = f.router.trailingSlashInsensitive;
218
218
  f.router = this.router.clone();
@@ -2731,6 +2731,15 @@ async function sendActivityInternal({ activity, activityId, keys, inbox, allowPr
2731
2731
  if (!allowPrivateAddress) try {
2732
2732
  await (0, _fedify_vocab_runtime.validatePublicUrl)(url);
2733
2733
  } catch (error) {
2734
+ if (error instanceof _fedify_vocab_runtime.UrlError && error.reason === "dns") {
2735
+ logger.error("DNS resolution failed for URL: {url}", {
2736
+ url,
2737
+ error
2738
+ });
2739
+ const failure = new _fedify_vocab_runtime.FetchError(url, error.message);
2740
+ failure.cause = error;
2741
+ throw failure;
2742
+ }
2734
2743
  if (error instanceof _fedify_vocab_runtime.UrlError) logger.error("Disallowed private URL: {url}", {
2735
2744
  url,
2736
2745
  error
@@ -2,10 +2,10 @@ import { Temporal } from "@js-temporal/polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
3
  import { t as __exportAll } from "./chunk-CRNNMoPX.js";
4
4
  import { r as getDefaultActivityTransformers } from "./transformers-BGMIq1cs.js";
5
- import { a as verifyRequestDetailed, d as importJwk, f as validateCryptoKey, i as verifyRequest, m as formatAcceptSignature, n as parseRfc9421SignatureInput, o as exportJwk, t as doubleKnock, v as name, y as version } from "./http-C5T-lWqB.js";
6
- import { a as MAX_BODY_SIZE, i as BodyTooLargeError, n as getNodeInfo, o as readBoundedText, t as nodeInfoToJson } from "./types-sXITXcWa.js";
7
- import { _ as hasSignatureLike, b as signJsonLd, c as getKeyOwner, f as compactJsonLd, g as hasSignature, h as getNormalizationContextLoader, i as verifyObject, l as InvalidContextReferenceError, m as detachSignature, n as hasProofLike, o as normalizeOutgoingActivityJsonLd, r as signObject, s as doesActorOwnKey, u as assertSafeJsonLd, v as isClearlyMalformedContextReference, w as wrapContextLoaderForJsonLd, x as verifyCompactJsonLd, y as isInvalidUrlTypeError } from "./proof-BdRnLnh7.js";
8
- import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-CM-zJoep.js";
5
+ import { a as verifyRequestDetailed, d as importJwk, f as validateCryptoKey, i as verifyRequest, m as formatAcceptSignature, n as parseRfc9421SignatureInput, o as exportJwk, t as doubleKnock, v as name, y as version } from "./http-BBGyG2ez.js";
6
+ import { a as MAX_BODY_SIZE, i as BodyTooLargeError, n as getNodeInfo, o as readBoundedText, t as nodeInfoToJson } from "./types-miAYfiGO.js";
7
+ import { _ as hasSignatureLike, b as signJsonLd, c as getKeyOwner, f as compactJsonLd, g as hasSignature, h as getNormalizationContextLoader, i as verifyObject, l as InvalidContextReferenceError, m as detachSignature, n as hasProofLike, o as normalizeOutgoingActivityJsonLd, r as signObject, s as doesActorOwnKey, u as assertSafeJsonLd, v as isClearlyMalformedContextReference, w as wrapContextLoaderForJsonLd, x as verifyCompactJsonLd, y as isInvalidUrlTypeError } from "./proof-BCHw9az4.js";
8
+ import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-D_17I8jy.js";
9
9
  import { getLogger, withContext } from "@logtape/logtape";
10
10
  import { Activity, Collection, CollectionPage, CryptographicKey, Link, Multikey, Object as Object$1, OrderedCollection, OrderedCollectionPage, Tombstone, getTypeId, lookupObject, traverseCollection } from "@fedify/vocab";
11
11
  import { SpanKind, SpanStatusCode, context, propagation, trace } from "@opentelemetry/api";
@@ -2730,6 +2730,15 @@ async function sendActivityInternal({ activity, activityId, keys, inbox, allowPr
2730
2730
  if (!allowPrivateAddress) try {
2731
2731
  await validatePublicUrl(url);
2732
2732
  } catch (error) {
2733
+ if (error instanceof UrlError && error.reason === "dns") {
2734
+ logger.error("DNS resolution failed for URL: {url}", {
2735
+ url,
2736
+ error
2737
+ });
2738
+ const failure = new FetchError(url, error.message);
2739
+ failure.cause = error;
2740
+ throw failure;
2741
+ }
2733
2742
  if (error instanceof UrlError) logger.error("Disallowed private URL: {url}", {
2734
2743
  url,
2735
2744
  error
@@ -1,4 +1,4 @@
1
1
  const { Temporal } = require("@js-temporal/polyfill");
2
2
  const { URLPattern } = require("urlpattern-polyfill");
3
- const require_middleware = require("./middleware-BbbKc-Tr.cjs");
3
+ const require_middleware = require("./middleware-BbomR2ro.cjs");
4
4
  exports.FederationImpl = require_middleware.FederationImpl;
@@ -2,25 +2,25 @@ import { Temporal } from "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { n as RouterError } from "./router-CrMLXoOr.mjs";
5
- import { n as version, t as name } from "./deno-CV4wsQzy.mjs";
5
+ import { n as version, t as name } from "./deno-BnfWZfzD.mjs";
6
6
  import { t as formatAcceptSignature } from "./accept-Dd__NiUL.mjs";
7
- import { o as importJwk, s as validateCryptoKey, t as exportJwk } from "./key-DiziIx9e.mjs";
8
- import { l as verifyRequest, o as parseRfc9421SignatureInput, u as verifyRequestDetailed } from "./http-C9MzFrOj.mjs";
9
- import { t as getAuthenticatedDocumentLoader } from "./docloader-DV9F08Dn.mjs";
7
+ import { o as importJwk, s as validateCryptoKey, t as exportJwk } from "./key-DwyWxSP6.mjs";
8
+ import { l as verifyRequest, o as parseRfc9421SignatureInput, u as verifyRequestDetailed } from "./http-xscrPdwI.mjs";
9
+ import { t as getAuthenticatedDocumentLoader } from "./docloader-Ctm-HtB1.mjs";
10
10
  import { n as kvCache } from "./kv-cache-FsT1ljhs.mjs";
11
- import { _ as wrapContextLoaderForJsonLd, a as compactJsonLd, c as getNormalizationContextLoader, d as isClearlyMalformedContextReference, f as isInvalidUrlTypeError, l as hasSignature, m as verifyCompactJsonLd, p as signJsonLd, r as assertSafeJsonLd, s as detachSignature, t as InvalidContextReferenceError, u as hasSignatureLike } from "./ld-BAeuDKjx.mjs";
12
- import { n as getKeyOwner, t as doesActorOwnKey } from "./owner-D08_JUSb.mjs";
11
+ import { _ as wrapContextLoaderForJsonLd, a as compactJsonLd, c as getNormalizationContextLoader, d as isClearlyMalformedContextReference, f as isInvalidUrlTypeError, l as hasSignature, m as verifyCompactJsonLd, p as signJsonLd, r as assertSafeJsonLd, s as detachSignature, t as InvalidContextReferenceError, u as hasSignatureLike } from "./ld-CQLGYzwy.mjs";
12
+ import { n as getKeyOwner, t as doesActorOwnKey } from "./owner-pFTUYlVh.mjs";
13
13
  import { r as normalizeOutgoingActivityJsonLd } from "./outgoing-jsonld-Dc2OXyB-.mjs";
14
- import { i as verifyObject, n as hasProofLike, r as signObject } from "./proof-_S-wFUFM.mjs";
15
- import { d as readBoundedText, l as BodyTooLargeError, t as getNodeInfo, u as MAX_BODY_SIZE } from "./client-CQ7zd8tW.mjs";
14
+ import { i as verifyObject, n as hasProofLike, r as signObject } from "./proof-CJxlMyX3.mjs";
15
+ import { d as readBoundedText, l as BodyTooLargeError, t as getNodeInfo, u as MAX_BODY_SIZE } from "./client-CJ4Y0keQ.mjs";
16
16
  import { t as nodeInfoToJson } from "./types-s4mCTGkT.mjs";
17
- import { t as FederationBuilderImpl } from "./builder-Cwgo1nK5.mjs";
17
+ import { t as FederationBuilderImpl } from "./builder-ky-Enw7B.mjs";
18
18
  import { t as buildCollectionSynchronizationHeader } from "./collection-DnoYNwBC.mjs";
19
19
  import { t as KvKeyCache } from "./keycache-C1aVprwy.mjs";
20
20
  import { t as acceptsJsonLd } from "./negotiation-BHtZN6Y1.mjs";
21
- import { t as hasMalformedKnownTemporalLiteral } from "./temporal-CX_L28b-.mjs";
21
+ import { t as hasMalformedKnownTemporalLiteral } from "./temporal-DA4sccOr.mjs";
22
22
  import { t as createExponentialBackoffPolicy } from "./retry-bMXBL97A.mjs";
23
- import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "./send-DmcKKipr.mjs";
23
+ import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "./send-C_Pfo7Lr.mjs";
24
24
  import { Activity, Collection, CollectionPage, CryptographicKey, Link, Multikey, Object as Object$1, OrderedCollection, OrderedCollectionPage, Tombstone, getTypeId, lookupObject, traverseCollection } from "@fedify/vocab";
25
25
  import { lookupWebFinger } from "@fedify/webfinger";
26
26
  import { SpanKind, SpanStatusCode, context, propagation, trace } from "@opentelemetry/api";
@@ -1,5 +1,5 @@
1
1
  import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as FederationImpl } from "./middleware-CkJOjZjS.mjs";
4
+ import { n as FederationImpl } from "./middleware-Dv-j_sRD.mjs";
5
5
  export { FederationImpl };
@@ -30,6 +30,8 @@ interface GetAuthenticatedDocumentLoaderOptions extends DocumentLoaderFactoryOpt
30
30
  * Gets an authenticated {@link DocumentLoader} for the given identity.
31
31
  * Note that an authenticated document loader intentionally does not cache
32
32
  * the fetched documents.
33
+ * At most 20 HTTP redirects and alternate document links are followed in total
34
+ * per call. Revisiting a URL within that chain throws a {@link FetchError}.
33
35
  * @param identity The identity to get the document loader for.
34
36
  * The actor's key pair.
35
37
  * @param options The options for the document loader.
@@ -30,6 +30,8 @@ interface GetAuthenticatedDocumentLoaderOptions extends DocumentLoaderFactoryOpt
30
30
  * Gets an authenticated {@link DocumentLoader} for the given identity.
31
31
  * Note that an authenticated document loader intentionally does not cache
32
32
  * the fetched documents.
33
+ * At most 20 HTTP redirects and alternate document links are followed in total
34
+ * per call. Revisiting a URL within that chain throws a {@link FetchError}.
33
35
  * @param identity The identity to get the document loader for.
34
36
  * The actor's key pair.
35
37
  * @param options The options for the document loader.
package/dist/mod.cjs CHANGED
@@ -4,11 +4,11 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
4
4
  require("./chunk-DDcVe30Y.cjs");
5
5
  const require_transformers = require("./transformers-NeAONrAq.cjs");
6
6
  require("./compat/mod.cjs");
7
- const require_http = require("./http-BWNuy1sb.cjs");
8
- const require_middleware = require("./middleware-BbbKc-Tr.cjs");
9
- const require_types = require("./types-DWs-qZaY.cjs");
10
- const require_proof = require("./proof-7T47tIu5.cjs");
11
- const require_kv_cache = require("./kv-cache-DGPEHc1T.cjs");
7
+ const require_http = require("./http-DXyLMSXp.cjs");
8
+ const require_middleware = require("./middleware-BbomR2ro.cjs");
9
+ const require_types = require("./types-DRPjmHVV.cjs");
10
+ const require_proof = require("./proof-BLZMJa2g.cjs");
11
+ const require_kv_cache = require("./kv-cache-BATy5Vhq.cjs");
12
12
  const require_federation_mod = require("./federation/mod.cjs");
13
13
  require("./nodeinfo/mod.cjs");
14
14
  require("./runtime/mod.cjs");
package/dist/mod.d.cts CHANGED
@@ -7,7 +7,7 @@ import { a as MemoryKvStore, i as KvStoreSetOptions, n as KvStore, r as KvStoreL
7
7
  import { actorDehydrator, autoIdAssigner, getDefaultActivityTransformers } from "./compat/mod.cjs";
8
8
  import { n as handleWebFinger, t as WebFingerHandlerParameters } from "./mod-DNvnDTS7.cjs";
9
9
  import { _ as hasSignatureLike, a as createProof, b as verifySignature, c as verifyObject, d as SignJsonLdOptions, f as VerifyJsonLdOptions, g as detachSignature, h as createSignature, i as VerifyProofOptions, l as verifyProof, m as attachSignature, n as SignObjectOptions, o as hasProofLike, p as VerifySignatureOptions, r as VerifyObjectOptions, s as signObject, t as CreateProofOptions, u as CreateSignatureOptions, v as signJsonLd, y as verifyJsonLd } from "./mod-D2JyWtW3.cjs";
10
- import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./mod-DzPMtIV3.cjs";
10
+ import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./mod-BYg1qMPB.cjs";
11
11
  export * from "@fedify/vocab-runtime";
12
12
 
13
13
  //#region src/mod.d.ts
package/dist/mod.d.ts CHANGED
@@ -7,7 +7,7 @@ import { a as MemoryKvStore, i as KvStoreSetOptions, n as KvStore, r as KvStoreL
7
7
  import { actorDehydrator, autoIdAssigner, getDefaultActivityTransformers } from "./compat/mod.js";
8
8
  import { n as handleWebFinger, t as WebFingerHandlerParameters } from "./mod-DaiJDJdQ.js";
9
9
  import { _ as hasSignatureLike, a as createProof, b as verifySignature, c as verifyObject, d as SignJsonLdOptions, f as VerifyJsonLdOptions, g as detachSignature, h as createSignature, i as VerifyProofOptions, l as verifyProof, m as attachSignature, n as SignObjectOptions, o as hasProofLike, p as VerifySignatureOptions, r as VerifyObjectOptions, s as signObject, t as CreateProofOptions, u as CreateSignatureOptions, v as signJsonLd, y as verifyJsonLd } from "./mod-ej_rLx8h.js";
10
- import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./mod-0MWo2Nu5.js";
10
+ import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./mod-C9jqpInC.js";
11
11
  export * from "@fedify/vocab-runtime";
12
12
 
13
13
  //#region src/mod.d.ts
package/dist/mod.js CHANGED
@@ -3,11 +3,11 @@ import { URLPattern } from "urlpattern-polyfill";
3
3
  import "./chunk-CRNNMoPX.js";
4
4
  import { n as autoIdAssigner, r as getDefaultActivityTransformers, t as actorDehydrator } from "./transformers-BGMIq1cs.js";
5
5
  import "./compat/mod.js";
6
- import { _ as validateAcceptSignature, a as verifyRequestDetailed, c as fetchKey, d as importJwk, g as parseAcceptSignature, h as fulfillAcceptSignature, i as verifyRequest, l as fetchKeyDetailed, m as formatAcceptSignature, o as exportJwk, r as signRequest, u as generateCryptoKeyPair } from "./http-C5T-lWqB.js";
7
- import { a as createExponentialBackoffPolicy, c as buildCollectionSynchronizationHeader, d as Router, f as RouterError, i as SendActivityError, l as digest, o as respondWithObject, r as handleWebFinger, s as respondWithObjectIfAcceptable, t as createFederation, u as createFederationBuilder } from "./middleware-J32whqNP.js";
8
- import { n as getNodeInfo, r as parseNodeInfo, t as nodeInfoToJson } from "./types-sXITXcWa.js";
9
- import { C as verifySignature, S as verifyJsonLd, _ as hasSignatureLike, a as verifyProof, b as signJsonLd, c as getKeyOwner, d as attachSignature, i as verifyObject, m as detachSignature, n as hasProofLike, p as createSignature, r as signObject, s as doesActorOwnKey, t as createProof } from "./proof-BdRnLnh7.js";
10
- import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-CM-zJoep.js";
6
+ import { _ as validateAcceptSignature, a as verifyRequestDetailed, c as fetchKey, d as importJwk, g as parseAcceptSignature, h as fulfillAcceptSignature, i as verifyRequest, l as fetchKeyDetailed, m as formatAcceptSignature, o as exportJwk, r as signRequest, u as generateCryptoKeyPair } from "./http-BBGyG2ez.js";
7
+ import { a as createExponentialBackoffPolicy, c as buildCollectionSynchronizationHeader, d as Router, f as RouterError, i as SendActivityError, l as digest, o as respondWithObject, r as handleWebFinger, s as respondWithObjectIfAcceptable, t as createFederation, u as createFederationBuilder } from "./middleware-CdgK6YM3.js";
8
+ import { n as getNodeInfo, r as parseNodeInfo, t as nodeInfoToJson } from "./types-miAYfiGO.js";
9
+ import { C as verifySignature, S as verifyJsonLd, _ as hasSignatureLike, a as verifyProof, b as signJsonLd, c as getKeyOwner, d as attachSignature, i as verifyObject, m as detachSignature, n as hasProofLike, p as createSignature, r as signObject, s as doesActorOwnKey, t as createProof } from "./proof-BCHw9az4.js";
10
+ import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-D_17I8jy.js";
11
11
  import { InProcessMessageQueue, MemoryKvStore, ParallelMessageQueue } from "./federation/mod.js";
12
12
  import "./nodeinfo/mod.js";
13
13
  import "./runtime/mod.js";