@fedify/fedify 2.3.8 → 2.3.9

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 (73) hide show
  1. package/dist/{builder-CDForRSL.mjs → builder-Dm72xfCy.mjs} +2 -2
  2. package/dist/{client-DYDoDpMa.mjs → client-Ccbsek6u.mjs} +7 -3
  3. package/dist/compat/transformers.test.mjs +1 -1
  4. package/dist/{context-BepjCFHN.mjs → context-CxbCIptr.mjs} +2 -1
  5. package/dist/{deno--DCBC7WG.mjs → deno-Deo5dZf_.mjs} +1 -1
  6. package/dist/docloader-BAVVKXPl.mjs +79 -0
  7. package/dist/federation/builder.test.mjs +2 -2
  8. package/dist/federation/circuit-breaker.test.mjs +1 -1
  9. package/dist/federation/collection.test.mjs +1 -1
  10. package/dist/federation/handler.test.mjs +7 -7
  11. package/dist/federation/idempotency.test.mjs +3 -3
  12. package/dist/federation/kv.test.mjs +1 -1
  13. package/dist/federation/metrics.test.mjs +2 -2
  14. package/dist/federation/middleware.test.mjs +187 -8
  15. package/dist/federation/mod.cjs +1 -1
  16. package/dist/federation/mod.js +1 -1
  17. package/dist/federation/mq.test.mjs +98 -43
  18. package/dist/federation/negotiation.test.mjs +1 -1
  19. package/dist/federation/router.test.mjs +1 -1
  20. package/dist/federation/send.test.mjs +85 -4
  21. package/dist/federation/temporal.test.mjs +2 -2
  22. package/dist/federation/webfinger.test.mjs +3 -3
  23. package/dist/{http-ClEDLGpr.mjs → http-BqObaL32.mjs} +8 -4
  24. package/dist/{http-ChVf88XZ.cjs → http-CjvY7wBM.cjs} +6 -2
  25. package/dist/{http-BKJYj9oz.js → http-DuWPL6ic.js} +6 -2
  26. package/dist/{key-C-7sqo8D.mjs → key-D1TEfV4d.mjs} +2 -2
  27. package/dist/{kv-cache-DFBOAyNj.cjs → kv-cache-C_4ufNbl.cjs} +34 -8
  28. package/dist/{kv-cache-C5FK4PLG.mjs → kv-cache-DGnB9ZX_.mjs} +1 -1
  29. package/dist/{kv-cache-C_w70bFh.js → kv-cache-w0De7ODp.js} +35 -9
  30. package/dist/{ld-CtFHB6rU.mjs → ld-DnGqAKWi.mjs} +4 -4
  31. package/dist/{metrics-FDcYXRP5.mjs → metrics-jUJmtE2u.mjs} +1 -1
  32. package/dist/{middleware-DunPXFJL.js → middleware-A-SjayRO.js} +19 -5
  33. package/dist/{middleware-BdDtzhXb.cjs → middleware-BW5W0CTK.cjs} +19 -5
  34. package/dist/{middleware-K7mJeIgT.mjs → middleware-C70IPcBw.mjs} +1 -1
  35. package/dist/{middleware-COzI8bCL.mjs → middleware-XCr66VRe.mjs} +13 -13
  36. package/dist/{mod-DHgc4ygG.d.ts → mod-CQ8EjnN5.d.ts} +2 -0
  37. package/dist/{mod-1TuoXkGs.d.cts → mod-EbRFjVlY.d.cts} +2 -0
  38. package/dist/mod.cjs +5 -5
  39. package/dist/mod.d.cts +1 -1
  40. package/dist/mod.d.ts +1 -1
  41. package/dist/mod.js +5 -5
  42. package/dist/nodeinfo/client.test.mjs +50 -2
  43. package/dist/nodeinfo/handler.test.mjs +3 -3
  44. package/dist/nodeinfo/mod.cjs +1 -1
  45. package/dist/nodeinfo/mod.js +1 -1
  46. package/dist/nodeinfo/types.test.mjs +1 -1
  47. package/dist/otel/exporter.test.mjs +1 -1
  48. package/dist/{owner-CGuS9lE7.mjs → owner-C4iDnghj.mjs} +2 -2
  49. package/dist/{proof--56drUyt.mjs → proof-CpU6r25U.mjs} +3 -3
  50. package/dist/{proof-tjbzynMa.js → proof-DqO9zYcc.js} +2 -2
  51. package/dist/{proof-Dgozne0w.cjs → proof-DvVA2Bmk.cjs} +2 -2
  52. package/dist/{send-CC1A7ATC.mjs → send-YVatAj-a.mjs} +18 -4
  53. package/dist/sig/http.test.mjs +43 -3
  54. package/dist/sig/key.test.mjs +2 -2
  55. package/dist/sig/ld.test.mjs +5 -2
  56. package/dist/sig/mod.cjs +2 -2
  57. package/dist/sig/mod.js +2 -2
  58. package/dist/sig/owner.test.mjs +2 -2
  59. package/dist/sig/proof.test.mjs +2 -2
  60. package/dist/{std__assert-BBjXFNOb.mjs → std__assert-BvGuO4xO.mjs} +2 -27
  61. package/dist/{temporal-DvptXZ7V.mjs → temporal-BJ-5wxWU.mjs} +1 -1
  62. package/dist/testing/mod.mjs +1 -1
  63. package/dist/{types-DWs-qZaY.cjs → types-DRPjmHVV.cjs} +7 -3
  64. package/dist/{types-sXITXcWa.js → types-miAYfiGO.js} +7 -3
  65. package/dist/utils/docloader.test.mjs +299 -3
  66. package/dist/utils/kv-cache.test.mjs +1 -1
  67. package/dist/utils/mod.cjs +1 -1
  68. package/dist/utils/mod.d.cts +1 -1
  69. package/dist/utils/mod.d.ts +1 -1
  70. package/dist/utils/mod.js +1 -1
  71. package/dist/vocab/cjs.test.mjs +1 -1
  72. package/package.json +8 -8
  73. package/dist/docloader-BAdp-ZeK.mjs +0 -53
@@ -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--DCBC7WG.mjs";
4
+ import { n as version, t as name } from "./deno-Deo5dZf_.mjs";
5
5
  import { t as ActivityListenerSet } from "./activity-listener-ell7W1s9.mjs";
6
6
  import { getLogger } from "@logtape/logtape";
7
7
  import { Router, RouterError, assertPath } from "@fedify/uri-template";
@@ -80,7 +80,7 @@ var FederationBuilderImpl = class {
80
80
  * specified.
81
81
  */
82
82
  async build(options) {
83
- const { FederationImpl } = await import("./middleware-K7mJeIgT.mjs");
83
+ const { FederationImpl } = await import("./middleware-C70IPcBw.mjs");
84
84
  const f = new FederationImpl(options);
85
85
  const trailingSlashInsensitiveValue = f.router.trailingSlashInsensitive;
86
86
  f.router = this.router.clone();
@@ -93,8 +93,8 @@ const DEFAULT_MAX_REDIRECTION = 5;
93
93
  * bounce the request to an internal address.
94
94
  * @param url The URL to fetch.
95
95
  * @param headers The request headers.
96
- * @returns The final response, or `null` if the URL is disallowed or there
97
- * are too many redirects.
96
+ * @returns The final response, or `null` if the URL is disallowed, its
97
+ * hostname cannot be resolved, or there are too many redirects.
98
98
  */
99
99
  async function fetchPublicUrl(url, headers) {
100
100
  let current = url instanceof URL ? url : new URL(url);
@@ -104,7 +104,11 @@ async function fetchPublicUrl(url, headers) {
104
104
  await validatePublicUrl(current.href);
105
105
  } catch (e) {
106
106
  if (e instanceof UrlError) {
107
- logger.error("Refused to fetch a private or invalid URL {url}: {error}", {
107
+ if (e.reason === "dns") logger.error("DNS lookup failed for {url}: {error}", {
108
+ url: current.href,
109
+ error: e
110
+ });
111
+ else logger.error("Refused to fetch a private or invalid URL {url}: {error}", {
108
112
  url: current.href,
109
113
  error: e
110
114
  });
@@ -4,7 +4,7 @@ globalThis.addEventListener = () => {};
4
4
  import { t as assertEquals } from "../assert_equals-C-ZRDbaf.mjs";
5
5
  import { n as assertInstanceOf, t as assert } from "../assert-jSdZMYam.mjs";
6
6
  import { t as MemoryKvStore } from "../kv-CX8qfaad.mjs";
7
- import { b as autoIdAssigner, n as FederationImpl, y as actorDehydrator } from "../middleware-COzI8bCL.mjs";
7
+ import { b as autoIdAssigner, n as FederationImpl, y as actorDehydrator } from "../middleware-XCr66VRe.mjs";
8
8
  import { Follow, Person } from "@fedify/vocab";
9
9
  import { test } from "@fedify/fixture";
10
10
  //#region src/compat/transformers.test.ts
@@ -10,6 +10,7 @@ import { lookupObject, traverseCollection } from "@fedify/vocab";
10
10
  import { lookupWebFinger } from "@fedify/webfinger";
11
11
  import { metrics, trace } from "@opentelemetry/api";
12
12
  //#region ../fixture/dist/mod.js
13
+ const fixtureFetch = globalThis.fetch.bind(globalThis);
13
14
  const logger = getLogger(["fixture", "docloader"]);
14
15
  /**
15
16
  * A mock of the document loader. This does not make any actual HTTP requests
@@ -24,7 +25,7 @@ async function mockDocumentLoader(resource, _options) {
24
25
  if ("navigator" in globalThis && navigator.userAgent === "Cloudflare-Workers") {
25
26
  const testUrl = new URL(url);
26
27
  testUrl.hostname += ".test";
27
- const resp = await fetch(testUrl);
28
+ const resp = await fixtureFetch(testUrl);
28
29
  if (resp.ok) {
29
30
  const document = await resp.json();
30
31
  logger.debug("Successfully fetched fixture {resource}: {status} {statusText}\n{body}", {
@@ -3,6 +3,6 @@ import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  //#region deno.json
5
5
  var name = "@fedify/fedify";
6
- var version = "2.3.8";
6
+ var version = "2.3.9";
7
7
  //#endregion
8
8
  export { version as n, name as t };
@@ -0,0 +1,79 @@
1
+ import "@js-temporal/polyfill";
2
+ import "urlpattern-polyfill";
3
+ globalThis.addEventListener = () => {};
4
+ import { s as validateCryptoKey } from "./key-D1TEfV4d.mjs";
5
+ import { n as doubleKnock } from "./http-BqObaL32.mjs";
6
+ import { getLogger } from "@logtape/logtape";
7
+ import { FetchError, UrlError, createActivityPubRequest, getRemoteDocument, logRequest, validatePublicUrl } from "@fedify/vocab-runtime";
8
+ import { curry } from "es-toolkit";
9
+ //#region src/utils/docloader.ts
10
+ const logger = getLogger([
11
+ "fedify",
12
+ "utils",
13
+ "docloader"
14
+ ]);
15
+ const DEFAULT_MAX_REDIRECTION = 20;
16
+ /**
17
+ * Gets an authenticated {@link DocumentLoader} for the given identity.
18
+ * Note that an authenticated document loader intentionally does not cache
19
+ * the fetched documents.
20
+ * At most 20 HTTP redirects and alternate document links are followed in total
21
+ * per call. Revisiting a URL within that chain throws a {@link FetchError}.
22
+ * @param identity The identity to get the document loader for.
23
+ * The actor's key pair.
24
+ * @param options The options for the document loader.
25
+ * @returns The authenticated document loader.
26
+ * @throws {TypeError} If the key is invalid or unsupported.
27
+ * @since 0.4.0
28
+ */
29
+ function getAuthenticatedDocumentLoader(identity, { allowPrivateAddress, maxRedirection, userAgent, specDeterminer, tracerProvider } = {}) {
30
+ validateCryptoKey(identity.privateKey);
31
+ async function load(url, options, redirected = 0, visited = /* @__PURE__ */ new Set()) {
32
+ options?.signal?.throwIfAborted();
33
+ let currentUrl = new URL(url).href;
34
+ await validateUrl(currentUrl);
35
+ visited.add(currentUrl);
36
+ const originalRequest = createActivityPubRequest(currentUrl, { userAgent });
37
+ function follow(nextUrl) {
38
+ options?.signal?.throwIfAborted();
39
+ if (redirected >= DEFAULT_MAX_REDIRECTION) throw new FetchError(currentUrl, `Too many redirections (${redirected + 1})`);
40
+ if (visited.has(nextUrl)) throw new FetchError(currentUrl, `Redirect loop detected: ${nextUrl}`);
41
+ redirected++;
42
+ }
43
+ const response = await doubleKnock(originalRequest, identity, {
44
+ maxRedirection,
45
+ specDeterminer,
46
+ log: curry(logRequest)(logger),
47
+ tracerProvider,
48
+ signal: options?.signal,
49
+ validateRedirect: async (nextUrl) => {
50
+ follow(nextUrl);
51
+ await validateUrl(nextUrl);
52
+ visited.add(nextUrl);
53
+ currentUrl = nextUrl;
54
+ }
55
+ });
56
+ return getRemoteDocument(currentUrl, response, (alternateUrl) => {
57
+ follow(alternateUrl);
58
+ return load(alternateUrl, options, redirected, visited);
59
+ });
60
+ }
61
+ async function validateUrl(url) {
62
+ if (!allowPrivateAddress) try {
63
+ await validatePublicUrl(url);
64
+ } catch (error) {
65
+ if (error instanceof UrlError) if (error.reason === "dns") logger.debug("DNS lookup failed for {url}", {
66
+ url,
67
+ error
68
+ });
69
+ else logger.error("Disallowed private URL: {url}", {
70
+ url,
71
+ error
72
+ });
73
+ throw error;
74
+ }
75
+ }
76
+ return (url, options) => load(url, options);
77
+ }
78
+ //#endregion
79
+ export { getAuthenticatedDocumentLoader as t };
@@ -2,10 +2,10 @@ import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as assertEquals } from "../assert_equals-C-ZRDbaf.mjs";
5
- import { r as assertExists } from "../std__assert-BBjXFNOb.mjs";
5
+ import { n as assertExists } from "../std__assert-BvGuO4xO.mjs";
6
6
  import { t as assertThrows } from "../assert_throws-BOkhLGYc.mjs";
7
7
  import { t as MemoryKvStore } from "../kv-CX8qfaad.mjs";
8
- import { r as createFederationBuilder } from "../builder-CDForRSL.mjs";
8
+ import { r as createFederationBuilder } from "../builder-Dm72xfCy.mjs";
9
9
  import { DisallowedOperatorError, DisallowedVarSpecModifierError, DuplicateRouteVariableError, RouteTemplateOptionsNotMatchedError, RouterError } from "@fedify/uri-template";
10
10
  import { Activity, Note, Person } from "@fedify/vocab";
11
11
  import { test } from "@fedify/fixture";
@@ -2,7 +2,7 @@ import { Temporal } from "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as assertEquals } from "../assert_equals-C-ZRDbaf.mjs";
5
- import "../std__assert-BBjXFNOb.mjs";
5
+ import "../std__assert-BvGuO4xO.mjs";
6
6
  import { t as assertThrows } from "../assert_throws-BOkhLGYc.mjs";
7
7
  import { t as MemoryKvStore } from "../kv-CX8qfaad.mjs";
8
8
  import { n as normalizeCircuitBreakerOptions, r as parseCircuitBreakerKvState, t as CircuitBreaker } from "../circuit-breaker-CDABzqVf.mjs";
@@ -2,7 +2,7 @@ import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as assertEquals } from "../assert_equals-C-ZRDbaf.mjs";
5
- import "../std__assert-BBjXFNOb.mjs";
5
+ import "../std__assert-BvGuO4xO.mjs";
6
6
  import { n as digest, t as buildCollectionSynchronizationHeader } from "../collection-CA3V5zyK.mjs";
7
7
  import { test } from "@fedify/fixture";
8
8
  import { decodeHex } from "byte-encodings/hex";
@@ -1,19 +1,19 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as createOutboxContext, r as createRequestContext, t as createInboxContext } from "../context-BepjCFHN.mjs";
4
+ import { n as createOutboxContext, r as createRequestContext, t as createInboxContext } from "../context-CxbCIptr.mjs";
5
5
  import { t as assertEquals } from "../assert_equals-C-ZRDbaf.mjs";
6
- import "../std__assert-BBjXFNOb.mjs";
6
+ import "../std__assert-BvGuO4xO.mjs";
7
7
  import { n as assertGreaterOrEqual, t as assertRejects } from "../assert_rejects-DN60FHPX.mjs";
8
8
  import { n as assertInstanceOf, t as assert } from "../assert-jSdZMYam.mjs";
9
9
  import { r as parseAcceptSignature } from "../accept-Dd__NiUL.mjs";
10
- import { a as generateCryptoKeyPair } from "../key-C-7sqo8D.mjs";
11
- import { s as signRequest } from "../http-ClEDLGpr.mjs";
10
+ import { a as generateCryptoKeyPair } from "../key-D1TEfV4d.mjs";
11
+ import { s as signRequest } from "../http-BqObaL32.mjs";
12
12
  import { a as rsaPrivateKey3, c as rsaPublicKey3, s as rsaPublicKey2 } from "../keys-BAK-tUlf.mjs";
13
- import { a as compactJsonLd, p as signJsonLd } from "../ld-CtFHB6rU.mjs";
14
- import { r as signObject } from "../proof--56drUyt.mjs";
13
+ import { a as compactJsonLd, p as signJsonLd } from "../ld-DnGqAKWi.mjs";
14
+ import { r as signObject } from "../proof-CpU6r25U.mjs";
15
15
  import { t as MemoryKvStore } from "../kv-CX8qfaad.mjs";
16
- import { c as handleActor, d as handleInbox, f as handleObject, h as respondWithObjectIfAcceptable, l as handleCollection, m as respondWithObject, o as createFederation, p as handleOutbox, u as handleCustomCollection } from "../middleware-COzI8bCL.mjs";
16
+ import { c as handleActor, d as handleInbox, f as handleObject, h as respondWithObjectIfAcceptable, l as handleCollection, m as respondWithObject, o as createFederation, p as handleOutbox, u as handleCustomCollection } from "../middleware-XCr66VRe.mjs";
17
17
  import { t as ActivityListenerSet } from "../activity-listener-ell7W1s9.mjs";
18
18
  import { Activity, Create, CryptographicKey, Multikey, Note, Person, Tombstone } from "@fedify/vocab";
19
19
  import { createTestMeterProvider, createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
@@ -2,11 +2,11 @@ import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as assertEquals } from "../assert_equals-C-ZRDbaf.mjs";
5
- import "../std__assert-BBjXFNOb.mjs";
5
+ import "../std__assert-BvGuO4xO.mjs";
6
6
  import { n as ed25519PrivateKey, r as ed25519PublicKey, t as ed25519Multikey } from "../keys-BAK-tUlf.mjs";
7
- import { r as signObject } from "../proof--56drUyt.mjs";
7
+ import { r as signObject } from "../proof-CpU6r25U.mjs";
8
8
  import { t as MemoryKvStore } from "../kv-CX8qfaad.mjs";
9
- import { o as createFederation } from "../middleware-COzI8bCL.mjs";
9
+ import { o as createFederation } from "../middleware-XCr66VRe.mjs";
10
10
  import { Create, Follow, Person } from "@fedify/vocab";
11
11
  import { mockDocumentLoader, test } from "@fedify/fixture";
12
12
  //#region src/federation/idempotency.test.ts
@@ -2,7 +2,7 @@ import { Temporal } from "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as assertEquals } from "../assert_equals-C-ZRDbaf.mjs";
5
- import "../std__assert-BBjXFNOb.mjs";
5
+ import "../std__assert-BvGuO4xO.mjs";
6
6
  import { t as MemoryKvStore } from "../kv-CX8qfaad.mjs";
7
7
  import { test } from "@fedify/fixture";
8
8
  //#region src/federation/kv.test.ts
@@ -2,9 +2,9 @@ import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as assertEquals } from "../assert_equals-C-ZRDbaf.mjs";
5
- import "../std__assert-BBjXFNOb.mjs";
5
+ import "../std__assert-BvGuO4xO.mjs";
6
6
  import { t as assertRejects } from "../assert_rejects-DN60FHPX.mjs";
7
- import { _ as recordKeyLookup, a as instrumentDocumentLoader, b as recordWebFingerHandle, c as recordCircuitBreakerStateChange, d as recordCollectionRequest, f as recordCollectionTotalItems, g as recordInboxActivity, h as recordFanoutRecipients, i as getRemoteHost, l as recordCollectionDispatchDuration, m as recordDocumentFetch, p as recordDocumentCache, r as getFederationMetrics, t as classifyFetchError, u as recordCollectionPageItems, v as recordOutboxActivity, x as registerQueueDepthGauge, y as recordOutboxEnqueue } from "../metrics-FDcYXRP5.mjs";
7
+ import { _ as recordKeyLookup, a as instrumentDocumentLoader, b as recordWebFingerHandle, c as recordCircuitBreakerStateChange, d as recordCollectionRequest, f as recordCollectionTotalItems, g as recordInboxActivity, h as recordFanoutRecipients, i as getRemoteHost, l as recordCollectionDispatchDuration, m as recordDocumentFetch, p as recordDocumentCache, r as getFederationMetrics, t as classifyFetchError, u as recordCollectionPageItems, v as recordOutboxActivity, x as registerQueueDepthGauge, y as recordOutboxEnqueue } from "../metrics-jUJmtE2u.mjs";
8
8
  import { createTestMeterProvider, test } from "@fedify/fixture";
9
9
  import { FetchError } from "@fedify/vocab-runtime";
10
10
  import { DataPointType, MeterProvider, MetricReader } from "@opentelemetry/sdk-metrics";
@@ -3,21 +3,21 @@ import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { t as esm_default } from "../esm-vrlUxr60.mjs";
5
5
  import { t as assertEquals } from "../assert_equals-C-ZRDbaf.mjs";
6
- import { r as assertExists } from "../std__assert-BBjXFNOb.mjs";
6
+ import { n as assertExists } from "../std__assert-BvGuO4xO.mjs";
7
7
  import { r as assertFalse, t as assertRejects } from "../assert_rejects-DN60FHPX.mjs";
8
8
  import { n as assertInstanceOf, t as assert } from "../assert-jSdZMYam.mjs";
9
9
  import { t as assertThrows } from "../assert_throws-BOkhLGYc.mjs";
10
10
  import { t as assertNotEquals } from "../assert_not_equals-DkVK8oqV.mjs";
11
11
  import { t as assertStrictEquals } from "../assert_strict_equals-XEgZAlrj.mjs";
12
- import { g as recordInboxActivity } from "../metrics-FDcYXRP5.mjs";
13
- import { l as verifyRequest, s as signRequest } from "../http-ClEDLGpr.mjs";
12
+ import { g as recordInboxActivity } from "../metrics-jUJmtE2u.mjs";
13
+ import { l as verifyRequest, s as signRequest } from "../http-BqObaL32.mjs";
14
14
  import { a as rsaPrivateKey3, c as rsaPublicKey3, i as rsaPrivateKey2, n as ed25519PrivateKey, r as ed25519PublicKey, s as rsaPublicKey2, t as ed25519Multikey } from "../keys-BAK-tUlf.mjs";
15
- import { t as getAuthenticatedDocumentLoader } from "../docloader-BAdp-ZeK.mjs";
16
- import { a as compactJsonLd, h as verifyJsonLd, p as signJsonLd, s as detachSignature } from "../ld-CtFHB6rU.mjs";
17
- import { t as doesActorOwnKey } from "../owner-CGuS9lE7.mjs";
18
- import { i as verifyObject, r as signObject } from "../proof--56drUyt.mjs";
15
+ import { t as getAuthenticatedDocumentLoader } from "../docloader-BAVVKXPl.mjs";
16
+ import { a as compactJsonLd, h as verifyJsonLd, p as signJsonLd, s as detachSignature } from "../ld-DnGqAKWi.mjs";
17
+ import { t as doesActorOwnKey } from "../owner-C4iDnghj.mjs";
18
+ import { i as verifyObject, r as signObject } from "../proof-CpU6r25U.mjs";
19
19
  import { t as MemoryKvStore } from "../kv-CX8qfaad.mjs";
20
- import { g as handleBenchmarkTrigger, i as KvSpecDeterminer, n as FederationImpl, o as createFederation, r as InboxContextImpl, t as ContextImpl } from "../middleware-COzI8bCL.mjs";
20
+ import { g as handleBenchmarkTrigger, i as KvSpecDeterminer, n as FederationImpl, o as createFederation, r as InboxContextImpl, t as ContextImpl } from "../middleware-XCr66VRe.mjs";
21
21
  import { t as CircuitBreaker } from "../circuit-breaker-CDABzqVf.mjs";
22
22
  import { t as markCircuitBreakerLegacySweepDone } from "../circuit-breaker-test-utils-Dm5MM6l9.mjs";
23
23
  import { configure, reset } from "@logtape/logtape";
@@ -27,6 +27,7 @@ import { Create, Offer, Person, getTypeId, lookupObject } from "@fedify/vocab";
27
27
  import { SpanStatusCode, metrics } from "@opentelemetry/api";
28
28
  import { createTestMeterProvider, createTestTracerProvider, mockDocumentLoader, test } from "@fedify/fixture";
29
29
  import { FetchError, UrlError, getDocumentLoader } from "@fedify/vocab-runtime";
30
+ import dns from "node:dns/promises";
30
31
  import serialize from "json-canon";
31
32
  import { DataPointType, MeterProvider as MeterProvider$1, MetricReader } from "@opentelemetry/sdk-metrics";
32
33
  //#region ../fixture/src/fixtures/example.com/create.json
@@ -1168,6 +1169,31 @@ test({
1168
1169
  esm_default.hardReset();
1169
1170
  }
1170
1171
  });
1172
+ test("Federation.fetch() [rfc9421] multiple POST signatures", async (t) => {
1173
+ for (const validSecond of [true, false]) await t.step(validSecond ? "valid second signature" : "both signatures invalid", async () => {
1174
+ const inbox = [];
1175
+ const federation = createFederation({
1176
+ kv: new MemoryKvStore(),
1177
+ documentLoaderFactory: () => mockDocumentLoader,
1178
+ contextLoaderFactory: () => mockDocumentLoader
1179
+ });
1180
+ federation.setActorDispatcher("/users/{identifier}", (ctx, identifier) => new vocab.Person({ id: ctx.getActorUri(identifier) }));
1181
+ federation.setInboxListeners("/users/{identifier}/inbox", "/inbox").on(vocab.Create, (_ctx, activity) => {
1182
+ inbox.push(activity.id.href);
1183
+ });
1184
+ const signed = await signRequest(new Request("https://example.com/inbox", {
1185
+ method: "POST",
1186
+ headers: { "Content-Type": "application/activity+json" },
1187
+ body: JSON.stringify(create_default)
1188
+ }), rsaPrivateKey2, rsaPublicKey2.id, { spec: "rfc9421" });
1189
+ const input = signed.headers.get("Signature-Input");
1190
+ const signature = signed.headers.get("Signature");
1191
+ signed.headers.set("Signature-Input", `${input}, ${input.replace(/^sig1=/, "sig2=")}`);
1192
+ signed.headers.set("Signature", `sig1=:AAAAAA==:, ${validSecond ? signature.replace(/^sig1=/, "sig2=") : "sig2=:AAAAAA==:"}`);
1193
+ assertEquals((await federation.fetch(signed, { contextData: void 0 })).status, validSecond ? 202 : 401);
1194
+ assertEquals(inbox, validSecond ? [id$2] : []);
1195
+ });
1196
+ });
1171
1197
  test("Federation.fetch()", async (t) => {
1172
1198
  esm_default.spyGlobal();
1173
1199
  esm_default.get("https://example.com/key2", {
@@ -2655,6 +2681,7 @@ test("FederationImpl.processQueuedTask()", async (t) => {
2655
2681
  const kv = new MemoryKvStore();
2656
2682
  const queuedMessages = [];
2657
2683
  const federation = new FederationImpl({
2684
+ allowPrivateAddress: true,
2658
2685
  kv,
2659
2686
  queue: {
2660
2687
  nativeRetrial: true,
@@ -2717,6 +2744,7 @@ test("FederationImpl.processQueuedTask()", async (t) => {
2717
2744
  const kv = new MemoryKvStore();
2718
2745
  let queuedMessages = [];
2719
2746
  const federation = new FederationImpl({
2747
+ allowPrivateAddress: true,
2720
2748
  kv,
2721
2749
  queue: {
2722
2750
  enqueue(message, _options) {
@@ -4484,6 +4512,157 @@ test("FederationImpl.processQueuedTask()", async (t) => {
4484
4512
  assertEquals(queuedMessages, []);
4485
4513
  });
4486
4514
  });
4515
+ test({
4516
+ name: "FederationImpl.processQueuedTask() accounts for destination validation failures",
4517
+ ignore: "Deno" in globalThis && (await Deno.permissions.query({ name: "net" })).state !== "granted",
4518
+ async fn(t) {
4519
+ for (const result of [
4520
+ "throws",
4521
+ "empty",
4522
+ "private"
4523
+ ]) for (const failureThreshold of [1, 5]) for (const redirected of [false, true]) await t.step(`${result}, threshold: ${failureThreshold}, redirected: ${redirected}`, async () => {
4524
+ const destination = "https://delivery.invalid/inbox";
4525
+ const publicInbox = "https://8.8.8.8/inbox";
4526
+ const queuedMessages = [];
4527
+ const delays = [];
4528
+ const errors = [];
4529
+ const activities = [];
4530
+ const attempts = [];
4531
+ const delay = Temporal.Duration.from({ seconds: 5 });
4532
+ const kv = new MemoryKvStore();
4533
+ await markCircuitBreakerLegacySweepDone(kv);
4534
+ const [meterProvider, recorder] = createTestMeterProvider();
4535
+ const recoveryDelay = Temporal.Duration.from({ minutes: 30 });
4536
+ const federation = new FederationImpl({
4537
+ kv,
4538
+ meterProvider,
4539
+ circuitBreaker: {
4540
+ failureThreshold,
4541
+ recoveryDelay
4542
+ },
4543
+ queue: {
4544
+ enqueue(message, options) {
4545
+ queuedMessages.push(message);
4546
+ delays.push(options?.delay);
4547
+ return Promise.resolve();
4548
+ },
4549
+ listen() {
4550
+ return Promise.resolve();
4551
+ }
4552
+ },
4553
+ documentLoaderFactory: () => mockDocumentLoader,
4554
+ contextLoaderFactory: () => mockDocumentLoader,
4555
+ onOutboxError(error, activity) {
4556
+ errors.push(error);
4557
+ activities.push(activity);
4558
+ },
4559
+ outboxRetryPolicy(options) {
4560
+ attempts.push(options.attempts);
4561
+ return delay;
4562
+ }
4563
+ });
4564
+ const message = {
4565
+ type: "outbox",
4566
+ id: crypto.randomUUID(),
4567
+ baseUrl: "https://example.com",
4568
+ keys: [],
4569
+ activity: {
4570
+ "@context": "https://www.w3.org/ns/activitystreams",
4571
+ type: "Create",
4572
+ id: "https://example.com/activities/dns-failure",
4573
+ actor: "https://example.com/users/alice",
4574
+ object: {
4575
+ type: "Note",
4576
+ content: "test"
4577
+ }
4578
+ },
4579
+ activityType: "https://www.w3.org/ns/activitystreams#Create",
4580
+ inbox: redirected ? publicInbox : destination,
4581
+ sharedInbox: false,
4582
+ started: (/* @__PURE__ */ new Date()).toISOString(),
4583
+ attempt: 0,
4584
+ headers: {},
4585
+ traceContext: {}
4586
+ };
4587
+ const originalLookup = dns.lookup;
4588
+ const resolverError = /* @__PURE__ */ new Error("Resolver unavailable");
4589
+ const lookups = [];
4590
+ dns.lookup = ((hostname) => {
4591
+ lookups.push(hostname);
4592
+ assertEquals(hostname, "delivery.invalid");
4593
+ if (result === "throws") return Promise.reject(resolverError);
4594
+ return Promise.resolve(result === "empty" ? [] : [{
4595
+ address: "127.0.0.1",
4596
+ family: 4
4597
+ }]);
4598
+ });
4599
+ try {
4600
+ esm_default.mockGlobal().catch(202);
4601
+ if (redirected) esm_default.route(publicInbox, {
4602
+ status: 307,
4603
+ headers: { Location: destination }
4604
+ });
4605
+ await federation.processQueuedTask(void 0, message);
4606
+ assertEquals(errors.length, 1);
4607
+ const error = errors[0];
4608
+ if (result === "private") {
4609
+ assertInstanceOf(error, UrlError);
4610
+ assertEquals(error.reason, "disallowed");
4611
+ } else {
4612
+ assertInstanceOf(error, FetchError);
4613
+ assertEquals(error.url.href, destination);
4614
+ assertInstanceOf(error.cause, UrlError);
4615
+ assertEquals(error.cause.reason, "dns");
4616
+ assertStrictEquals(error.cause.cause, result === "throws" ? resolverError : void 0);
4617
+ }
4618
+ assertEquals(activities.length, 1);
4619
+ assertInstanceOf(activities[0], Create);
4620
+ assertEquals(activities[0].id?.href, "https://example.com/activities/dns-failure");
4621
+ assertEquals(attempts, [0]);
4622
+ const host = new URL(message.inbox).host;
4623
+ const state = await kv.get([
4624
+ "_fedify",
4625
+ "circuit",
4626
+ host
4627
+ ]);
4628
+ if (result === "private") assertEquals(state, void 0);
4629
+ else {
4630
+ assertExists(state);
4631
+ assertEquals(state.failures.length, 1);
4632
+ assertEquals(state.state, failureThreshold === 1 ? "open" : "closed");
4633
+ }
4634
+ if (result !== "private" && failureThreshold === 1) {
4635
+ assertEquals(queuedMessages.length, 1);
4636
+ const held = queuedMessages[0];
4637
+ assertEquals(held.attempt, 0);
4638
+ assertEquals(held.circuitHeld, true);
4639
+ assertExists(held.circuitHeldSince);
4640
+ assertEquals(delays, [recoveryDelay]);
4641
+ } else {
4642
+ assertEquals(queuedMessages, [{
4643
+ ...message,
4644
+ attempt: 1
4645
+ }]);
4646
+ assertEquals(delays, [delay]);
4647
+ }
4648
+ const expectedCount = result === "private" && !redirected ? 0 : 1;
4649
+ const sent = recorder.getMeasurements("activitypub.delivery.sent");
4650
+ assertEquals(sent.length, expectedCount);
4651
+ if (expectedCount === 1) {
4652
+ assertEquals(sent[0].value, 1);
4653
+ assertEquals(sent[0].attributes["activitypub.delivery.success"], false);
4654
+ assertEquals(sent[0].attributes["activitypub.remote.host"], host);
4655
+ }
4656
+ assertEquals(lookups, ["delivery.invalid"]);
4657
+ assertEquals(esm_default.callHistory.calls(destination).length, 0);
4658
+ assertEquals(esm_default.callHistory.calls().length, redirected ? 1 : 0);
4659
+ } finally {
4660
+ dns.lookup = originalLookup;
4661
+ esm_default.hardReset();
4662
+ }
4663
+ });
4664
+ }
4665
+ });
4487
4666
  test("FederationImpl.processQueuedTask() permanent failure", async (t) => {
4488
4667
  esm_default.spyGlobal();
4489
4668
  esm_default.post("https://gone.example/inbox", {
@@ -2,7 +2,7 @@ const { Temporal } = require("@js-temporal/polyfill");
2
2
  const { URLPattern } = require("urlpattern-polyfill");
3
3
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
4
4
  require("../chunk-DDcVe30Y.cjs");
5
- const require_middleware = require("../middleware-BdDtzhXb.cjs");
5
+ const require_middleware = require("../middleware-BW5W0CTK.cjs");
6
6
  let _logtape_logtape = require("@logtape/logtape");
7
7
  let _fedify_uri_template = require("@fedify/uri-template");
8
8
  let es_toolkit = require("es-toolkit");
@@ -1,6 +1,6 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
- import { a as SendActivityError, c as buildCollectionSynchronizationHeader, d as normalizeCircuitBreakerOptions, f as parseCircuitBreakerKvState, i as createExponentialBackoffPolicy, l as digest, o as respondWithObject, p as createFederationBuilder, r as handleWebFinger, s as respondWithObjectIfAcceptable, t as createFederation, u as CircuitBreaker } from "../middleware-DunPXFJL.js";
3
+ import { a as SendActivityError, c as buildCollectionSynchronizationHeader, d as normalizeCircuitBreakerOptions, f as parseCircuitBreakerKvState, i as createExponentialBackoffPolicy, l as digest, o as respondWithObject, p as createFederationBuilder, r as handleWebFinger, s as respondWithObjectIfAcceptable, t as createFederation, u as CircuitBreaker } from "../middleware-A-SjayRO.js";
4
4
  import { getLogger } from "@logtape/logtape";
5
5
  import { Router as Router$1, RouterError as RouterError$1, assertPath, isPath } from "@fedify/uri-template";
6
6
  import { isEqual } from "es-toolkit";