@fedify/fedify 2.4.0-dev.2077 → 2.4.0-dev.2084

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/dist/{builder-i3jxVe9m.mjs → builder-BGlpUocK.mjs} +2 -2
  2. package/dist/compat/transformers.test.mjs +1 -1
  3. package/dist/{compound-proof-Cayy-ECj.mjs → compound-proof-B6Aj2kGY.mjs} +1 -1
  4. package/dist/{deno-Djkzd-G_.mjs → deno-Dn-py9Zy.mjs} +1 -1
  5. package/dist/{docloader-dA0RswUV.mjs → docloader-BUq5bYJr.mjs} +2 -2
  6. package/dist/federation/builder.test.mjs +1 -1
  7. package/dist/federation/compound-fanout.test.mjs +3 -3
  8. package/dist/federation/handler-compound-proof.test.mjs +3 -3
  9. package/dist/federation/handler.test.mjs +5 -5
  10. package/dist/federation/hashlink-media.test.mjs +2 -2
  11. package/dist/federation/idempotency.test.mjs +2 -2
  12. package/dist/federation/metrics.test.mjs +1 -1
  13. package/dist/federation/middleware.test.mjs +7 -7
  14. package/dist/federation/mod.cjs +1 -1
  15. package/dist/federation/mod.js +1 -1
  16. package/dist/federation/outgoing-proof.test.mjs +5 -5
  17. package/dist/federation/portable-gateway-keys.test.mjs +3 -3
  18. package/dist/federation/portable-inbox.test.mjs +401 -6
  19. package/dist/federation/portable.test.mjs +3 -3
  20. package/dist/federation/send.test.mjs +3 -3
  21. package/dist/federation/tasks/enqueue.test.mjs +1 -1
  22. package/dist/federation/tasks/tasks.test.mjs +2 -2
  23. package/dist/federation/temporal.test.mjs +1 -1
  24. package/dist/federation/webfinger.test.mjs +1 -1
  25. package/dist/{federation-CFd0Jhhv.cjs → federation-BLGx8JUH.cjs} +115 -12
  26. package/dist/{http-aqh3yFdM.js → http-4PCCvhsU.js} +1 -1
  27. package/dist/{http-CCJ97Jql.mjs → http-B0VRNipE.mjs} +4 -4
  28. package/dist/{http-Dvy_IwRa.cjs → http-B5q1CHtL.cjs} +1 -1
  29. package/dist/{key-IxTTC8Yf.mjs → key-BJrp524f.mjs} +2 -2
  30. package/dist/{kv-cache-Y6SDRSX6.js → kv-cache-1KUmAPH7.js} +1 -1
  31. package/dist/{kv-cache-Bwwbh-xR.mjs → kv-cache-C4C3LyYW.mjs} +1 -1
  32. package/dist/{kv-cache-BP69NV0P.cjs → kv-cache-Dhhhrryv.cjs} +1 -1
  33. package/dist/{ld-l0B9nREb.mjs → ld-BmFz5NLA.mjs} +3 -3
  34. package/dist/{metrics-D1p6hv4z.mjs → metrics-Beod6tzO.mjs} +1 -1
  35. package/dist/{middleware-BDjCUHMS.mjs → middleware-B7CNsyqt.mjs} +1 -1
  36. package/dist/{middleware-BMgzqBtr.mjs → middleware-BKeQYPm0.mjs} +125 -22
  37. package/dist/{middleware-SSfryVzY.js → middleware-D_9pNYe9.js} +115 -12
  38. package/dist/mod.cjs +5 -5
  39. package/dist/mod.js +5 -5
  40. package/dist/nodeinfo/handler.test.mjs +1 -1
  41. package/dist/{owner-BpOxL5PG.cjs → owner-BQFZtFV4.cjs} +1 -1
  42. package/dist/{owner-DGbYmXqU.mjs → owner-CsKVgMzk.mjs} +3 -3
  43. package/dist/{owner-jH1z_YfD.js → owner-_pbbgLJK.js} +1 -1
  44. package/dist/{portable-key-CCwJGSsU.mjs → portable-key-B4R-CiIV.mjs} +2 -2
  45. package/dist/{proof-CVL7JnZd.mjs → proof-ozG4HfcQ.mjs} +4 -4
  46. package/dist/{send-Ejy1jOz9.mjs → send-ClPt8nYu.mjs} +3 -3
  47. package/dist/sig/compound-proof-discovery.test.mjs +1 -1
  48. package/dist/sig/compound-proof-producer.test.mjs +1 -1
  49. package/dist/sig/compound-proof-verification.test.mjs +2 -2
  50. package/dist/sig/compound-proof.test.mjs +2 -2
  51. package/dist/sig/http.test.mjs +2 -2
  52. package/dist/sig/key.test.mjs +1 -1
  53. package/dist/sig/ld.test.mjs +2 -2
  54. package/dist/sig/mod.cjs +3 -3
  55. package/dist/sig/mod.js +3 -3
  56. package/dist/sig/owner.test.mjs +1 -1
  57. package/dist/sig/portable-collection.test.mjs +2 -2
  58. package/dist/sig/portable-key.test.mjs +3 -3
  59. package/dist/sig/proof.test.mjs +3 -3
  60. package/dist/{sig-Dpy1B1Ad.js → sig-DhdomAN0.js} +2 -2
  61. package/dist/{sig-xoqy3ATq.cjs → sig-k3C8sF7g.cjs} +2 -2
  62. package/dist/{temporal-DNJ1cNpr.mjs → temporal-Ba0EAZAI.mjs} +1 -1
  63. package/dist/testing/kv.test.mjs +1 -1
  64. package/dist/utils/docloader.test.mjs +2 -2
  65. package/dist/utils/kv-cache.test.mjs +1 -1
  66. package/dist/utils/mod.cjs +1 -1
  67. package/dist/utils/mod.js +1 -1
  68. package/package.json +6 -6
@@ -1,10 +1,10 @@
1
1
  const { URLPattern } = require("urlpattern-polyfill");
2
2
  const require_rolldown_runtime = require("./rolldown-runtime-Dmm3t92W.cjs");
3
3
  const require_transformers = require("./transformers-BalCrSsr.cjs");
4
- const require_http = require("./http-Dvy_IwRa.cjs");
4
+ const require_http = require("./http-B5q1CHtL.cjs");
5
5
  const require_types = require("./types-Be2zbyeG.cjs");
6
- const require_owner = require("./owner-BpOxL5PG.cjs");
7
- const require_kv_cache = require("./kv-cache-BP69NV0P.cjs");
6
+ const require_owner = require("./owner-BQFZtFV4.cjs");
7
+ const require_kv_cache = require("./kv-cache-Dhhhrryv.cjs");
8
8
  let _logtape_logtape = require("@logtape/logtape");
9
9
  let _fedify_uri_template = require("@fedify/uri-template");
10
10
  let _fedify_vocab = require("@fedify/vocab");
@@ -5059,6 +5059,7 @@ function resolvePortableInboxRecipient(actor, { authority, canonicalInboxId, loc
5059
5059
  return {
5060
5060
  status: "accepted",
5061
5061
  recipient: {
5062
+ actorId: id,
5062
5063
  inboxId,
5063
5064
  canonicalInboxId,
5064
5065
  gateways
@@ -5092,8 +5093,11 @@ function canonicalize(iri) {
5092
5093
  * Forwarding is therefore best effort. Without {@link KvStore.cas}, nothing is
5093
5094
  * forwarded.
5094
5095
  *
5095
- * Forwarded requests are not signed with HTTP Signatures; the receiving
5096
- * gateways authenticate the activity by its own proof.
5096
+ * Forwarded requests are signed with HTTP Signatures by the RSA key among
5097
+ * {@link ForwardPortableInboxActivityParameters.getKeys}, if any, so that
5098
+ * servers requiring HTTP Signatures accept them, and are sent unsigned
5099
+ * otherwise. Either way, the receiving gateways authenticate the activity by
5100
+ * its own proof.
5097
5101
  * @param parameters The parameters.
5098
5102
  * @returns The target inbox URLs that the activity was handed off to.
5099
5103
  */
@@ -5165,20 +5169,59 @@ function getForwardingTargets(recipient, excludedOrigins) {
5165
5169
  }
5166
5170
  return targets;
5167
5171
  }
5168
- async function forwardImmediately({ activity, activityId, activityType, allowPrivateAddress, options, meterProvider, tracerProvider }, inboxes) {
5172
+ /**
5173
+ * Resolves the key that signs forwarded requests: the first RSASSA-PKCS1-v1_5
5174
+ * key, as {@link sendActivity} signs requests only with such a key. Since
5175
+ * the targets have already been claimed, the activity is forwarded unsigned
5176
+ * rather than not at all if the key cannot be resolved or used.
5177
+ */
5178
+ async function resolveForwardingKey({ getKeys, activityId }) {
5179
+ if (getKeys == null) return null;
5180
+ const logger = (0, _logtape_logtape.getLogger)([
5181
+ "fedify",
5182
+ "federation",
5183
+ "inbox"
5184
+ ]);
5185
+ let key;
5186
+ try {
5187
+ key = (await getKeys()).find((k) => k.privateKey.algorithm.name === "RSASSA-PKCS1-v1_5");
5188
+ } catch (error) {
5189
+ logger.error("Failed to get the gateway keys to sign forwarded requests with; forwarding activity {activityId} without HTTP Signatures:\n{error}", {
5190
+ activityId: activityId.href,
5191
+ error
5192
+ });
5193
+ return null;
5194
+ }
5195
+ if (key == null) return null;
5196
+ try {
5197
+ require_http.validateCryptoKey(key.privateKey, "private");
5198
+ } catch (error) {
5199
+ if (!(error instanceof TypeError)) throw error;
5200
+ logger.warn("The gateway key {keyId} cannot sign forwarded requests; forwarding activity {activityId} without HTTP Signatures:\n{error}", {
5201
+ keyId: key.keyId.href,
5202
+ activityId: activityId.href,
5203
+ error
5204
+ });
5205
+ return null;
5206
+ }
5207
+ return key;
5208
+ }
5209
+ async function forwardImmediately(parameters, inboxes) {
5210
+ const { activity, activityId, activityType, allowPrivateAddress, specDeterminer, options, meterProvider, tracerProvider } = parameters;
5169
5211
  const logger = (0, _logtape_logtape.getLogger)([
5170
5212
  "fedify",
5171
5213
  "federation",
5172
5214
  "inbox"
5173
5215
  ]);
5174
5216
  const deadline = (options ?? resolvePortableInboxForwardingOptions()).deadline.total("millisecond");
5175
- const sends = inboxes.map((inbox) => sendActivity({
5217
+ const forwarding = resolveForwardingKey(parameters).then((key) => Promise.all(inboxes.map((inbox) => sendActivity({
5176
5218
  activity,
5177
5219
  activityId: activityId.href,
5178
5220
  activityType,
5179
- keys: [],
5221
+ keys: key == null ? [] : [key],
5180
5222
  inbox,
5181
5223
  allowPrivateAddress,
5224
+ specDeterminer,
5182
5225
  meterProvider,
5183
5226
  tracerProvider
5184
5227
  }).catch((error) => {
@@ -5187,9 +5230,9 @@ async function forwardImmediately({ activity, activityId, activityType, allowPri
5187
5230
  inbox: inbox.href,
5188
5231
  error
5189
5232
  });
5190
- }));
5233
+ }))));
5191
5234
  let timer;
5192
- const timedOut = await Promise.race([Promise.all(sends).then(() => false), new Promise((resolve) => {
5235
+ const timedOut = await Promise.race([forwarding.then(() => false), new Promise((resolve) => {
5193
5236
  timer = setTimeout(() => resolve(true), deadline);
5194
5237
  })]);
5195
5238
  clearTimeout(timer);
@@ -5198,12 +5241,27 @@ async function forwardImmediately({ activity, activityId, activityType, allowPri
5198
5241
  deadline
5199
5242
  });
5200
5243
  }
5201
- async function enqueueForwarding({ activity, activityId, activityType, baseUrl, startQueue, meterProvider }, outboxQueue, inboxes) {
5244
+ async function enqueueForwarding(parameters, outboxQueue, inboxes) {
5245
+ const { activity, activityId, activityType, baseUrl, startQueue, meterProvider } = parameters;
5202
5246
  const logger = (0, _logtape_logtape.getLogger)([
5203
5247
  "fedify",
5204
5248
  "federation",
5205
5249
  "inbox"
5206
5250
  ]);
5251
+ const key = await resolveForwardingKey(parameters);
5252
+ const keys = [];
5253
+ if (key != null) try {
5254
+ keys.push({
5255
+ keyId: key.keyId.href,
5256
+ privateKey: await require_http.exportJwk(key.privateKey)
5257
+ });
5258
+ } catch (error) {
5259
+ logger.error("Failed to export the gateway key {keyId}; forwarding activity {activityId} without HTTP Signatures:\n{error}", {
5260
+ keyId: key.keyId.href,
5261
+ activityId: activityId.href,
5262
+ error
5263
+ });
5264
+ }
5207
5265
  startQueue?.();
5208
5266
  const started = (/* @__PURE__ */ new Date()).toISOString();
5209
5267
  const traceContext = {};
@@ -5213,7 +5271,7 @@ async function enqueueForwarding({ activity, activityId, activityType, baseUrl,
5213
5271
  type: "outbox",
5214
5272
  id: crypto.randomUUID(),
5215
5273
  baseUrl,
5216
- keys: [],
5274
+ keys,
5217
5275
  activity,
5218
5276
  activityId: activityId.href,
5219
5277
  activityType,
@@ -7896,6 +7954,8 @@ var FederationImpl = class extends FederationBuilderImpl {
7896
7954
  outboxQueue: this.outboxQueue,
7897
7955
  startQueue: this.manuallyStartQueue ? void 0 : () => this._startQueueInternal(contextData),
7898
7956
  allowPrivateAddress: this.allowPrivateAddress,
7957
+ getKeys: () => this.#getPortableGatewayKeyPairs(context, identifier, recipient),
7958
+ specDeterminer: new KvSpecDeterminer(this.kv, this.kvPrefixes.httpMessageSignaturesSpec, this.firstKnock, { specTtl: this.httpMessageSignaturesSpecTtl }),
7899
7959
  options: this.portableInboxForwarding,
7900
7960
  meterProvider: this.meterProvider,
7901
7961
  tracerProvider: this.tracerProvider
@@ -7904,6 +7964,49 @@ var FederationImpl = class extends FederationBuilderImpl {
7904
7964
  }
7905
7965
  });
7906
7966
  }
7967
+ /**
7968
+ * Gets this server's gateway key pairs for the portable actor that owns
7969
+ * a portable inbox, which sign the activities forwarded from the inbox.
7970
+ * @param context The context.
7971
+ * @param identifier The identifier of the portable actor.
7972
+ * @param recipient The portable inbox.
7973
+ * @returns The key pairs, or an empty array if the actor has no gateway
7974
+ * keys on this server.
7975
+ */
7976
+ async #getPortableGatewayKeyPairs(context, identifier, recipient) {
7977
+ const logger = (0, _logtape_logtape.getLogger)([
7978
+ "fedify",
7979
+ "federation",
7980
+ "inbox"
7981
+ ]);
7982
+ if (this.actorCallbacks?.keyPairsDispatcher == null || this.actorCallbacks.portableActorIdMapper == null) {
7983
+ logger.debug("Forwarding activities from the portable inbox {inbox} without HTTP Signatures, as gateway keys need both a key pairs dispatcher and a portable actor ID mapper.", { inbox: recipient.canonicalInboxId });
7984
+ return [];
7985
+ }
7986
+ const actorId = require_http.getCanonicalPortableId(recipient.actorId);
7987
+ if (actorId == null) return [];
7988
+ const keyPairs = await context.getActorKeyPairs(identifier);
7989
+ for (const { cryptographicKey } of keyPairs) {
7990
+ const owner = cryptographicKey.ownerId;
7991
+ const canonicalOwner = owner == null ? null : require_http.getCanonicalPortableId(owner);
7992
+ if (canonicalOwner === actorId) continue;
7993
+ if (canonicalOwner == null) logger.debug("Forwarding activities from the portable inbox {inbox} without HTTP Signatures, as the key pairs of the actor {identifier} are not the gateway keys of a portable actor.", {
7994
+ inbox: recipient.canonicalInboxId,
7995
+ identifier
7996
+ });
7997
+ else logger.warn("Forwarding activities from the portable inbox {inbox} without HTTP Signatures, as the key pairs of the actor {identifier} are the gateway keys of {owner}, not of the recipient {actorId}.", {
7998
+ inbox: recipient.canonicalInboxId,
7999
+ identifier,
8000
+ owner: canonicalOwner,
8001
+ actorId
8002
+ });
8003
+ return [];
8004
+ }
8005
+ return keyPairs.map(({ keyId, privateKey }) => ({
8006
+ keyId,
8007
+ privateKey
8008
+ }));
8009
+ }
7907
8010
  async #fetchHashlinkMedia(request, media, { onNotFound, contextData, span, metricState }) {
7908
8011
  metricState.endpoint = "hashlink_media";
7909
8012
  metricState.routeTemplate = HASHLINK_MEDIA_ROUTE_TEMPLATE;
@@ -13,7 +13,7 @@ import { decodeBase64, encodeBase64 } from "byte-encodings/base64";
13
13
  import { ATTR_HTTP_REQUEST_HEADER, ATTR_HTTP_REQUEST_METHOD, ATTR_URL_FULL } from "@opentelemetry/semantic-conventions";
14
14
  //#region deno.json
15
15
  var name = "@fedify/fedify";
16
- var version = "2.4.0-dev.2077+c29a03d4";
16
+ var version = "2.4.0-dev.2084+4b8bd325";
17
17
  //#endregion
18
18
  //#region src/sig/accept.ts
19
19
  /**
@@ -1,11 +1,11 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-Djkzd-G_.mjs";
5
- import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-D1p6hv4z.mjs";
4
+ import { n as version, t as name } from "./deno-Dn-py9Zy.mjs";
5
+ import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-Beod6tzO.mjs";
6
6
  import { i as validateAcceptSignature, n as fulfillAcceptSignature, r as parseAcceptSignature } from "./accept-CPVqki78.mjs";
7
- import { i as fetchKeyDetailed, s as validateCryptoKey } from "./key-IxTTC8Yf.mjs";
8
- import { n as verifyPortableGatewayKeyDocument } from "./portable-key-CCwJGSsU.mjs";
7
+ import { i as fetchKeyDetailed, s as validateCryptoKey } from "./key-BJrp524f.mjs";
8
+ import { n as verifyPortableGatewayKeyDocument } from "./portable-key-B4R-CiIV.mjs";
9
9
  import { getLogger } from "@logtape/logtape";
10
10
  import { CryptographicKey } from "@fedify/vocab";
11
11
  import { SpanStatusCode, trace } from "@opentelemetry/api";
@@ -15,7 +15,7 @@ let byte_encodings_base64 = require("byte-encodings/base64");
15
15
  let _opentelemetry_semantic_conventions = require("@opentelemetry/semantic-conventions");
16
16
  //#region deno.json
17
17
  var name = "@fedify/fedify";
18
- var version = "2.4.0-dev.2077+c29a03d4";
18
+ var version = "2.4.0-dev.2084+4b8bd325";
19
19
  //#endregion
20
20
  //#region ../../node_modules/.pnpm/temporal-polyfill@1.0.1/node_modules/temporal-polyfill/chunks/root.js
21
21
  const NativeTemporal = globalThis.Temporal;
@@ -1,8 +1,8 @@
1
1
  import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-Djkzd-G_.mjs";
5
- import { _ as recordKeyLookup, n as getDurationMs, t as classifyFetchError } from "./metrics-D1p6hv4z.mjs";
4
+ import { n as version, t as name } from "./deno-Dn-py9Zy.mjs";
5
+ import { _ as recordKeyLookup, n as getDurationMs, t as classifyFetchError } from "./metrics-Beod6tzO.mjs";
6
6
  import { getLogger } from "@logtape/logtape";
7
7
  import { CryptographicKey, Multikey, Object as Object$1, isActor } from "@fedify/vocab";
8
8
  import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
@@ -1,6 +1,6 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
- import { R as validateCryptoKey, nt as recordDocumentCache, t as doubleKnock } from "./http-aqh3yFdM.js";
3
+ import { R as validateCryptoKey, nt as recordDocumentCache, t as doubleKnock } from "./http-4PCCvhsU.js";
4
4
  import { getLogger } from "@logtape/logtape";
5
5
  import { UrlError, createActivityPubRequest, getRemoteDocument, logRequest, preloadedContexts, validatePublicUrl } from "@fedify/vocab-runtime";
6
6
  import { curry } from "es-toolkit";
@@ -1,7 +1,7 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { p as recordDocumentCache } from "./metrics-D1p6hv4z.mjs";
4
+ import { p as recordDocumentCache } from "./metrics-Beod6tzO.mjs";
5
5
  import { getLogger } from "@logtape/logtape";
6
6
  import { preloadedContexts } from "@fedify/vocab-runtime";
7
7
  //#region src/utils/kv-cache.ts
@@ -1,5 +1,5 @@
1
1
  const { URLPattern } = require("urlpattern-polyfill");
2
- const require_http = require("./http-Dvy_IwRa.cjs");
2
+ const require_http = require("./http-B5q1CHtL.cjs");
3
3
  let _logtape_logtape = require("@logtape/logtape");
4
4
  let _fedify_vocab_runtime = require("@fedify/vocab-runtime");
5
5
  let es_toolkit = require("es-toolkit");
@@ -1,9 +1,9 @@
1
1
  import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-Djkzd-G_.mjs";
5
- import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-D1p6hv4z.mjs";
6
- import { r as fetchKey, s as validateCryptoKey } from "./key-IxTTC8Yf.mjs";
4
+ import { n as version, t as name } from "./deno-Dn-py9Zy.mjs";
5
+ import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-Beod6tzO.mjs";
6
+ import { r as fetchKey, s as validateCryptoKey } from "./key-BJrp524f.mjs";
7
7
  import { getLogger } from "@logtape/logtape";
8
8
  import { Activity, CryptographicKey, Object as Object$1, getTypeId } from "@fedify/vocab";
9
9
  import { SpanStatusCode, trace } from "@opentelemetry/api";
@@ -1,7 +1,7 @@
1
1
  import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-Djkzd-G_.mjs";
4
+ import { n as version, t as name } from "./deno-Dn-py9Zy.mjs";
5
5
  import { metrics } from "@opentelemetry/api";
6
6
  import { FetchError } from "@fedify/vocab-runtime";
7
7
  //#region src/federation/metrics.ts
@@ -1,5 +1,5 @@
1
1
  import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as FederationImpl } from "./middleware-BMgzqBtr.mjs";
4
+ import { n as FederationImpl } from "./middleware-BKeQYPm0.mjs";
5
5
  export { FederationImpl };
@@ -1,27 +1,27 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-Djkzd-G_.mjs";
5
- import { 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, n as getDurationMs, o as isAbortError, r as getFederationMetrics, u as recordCollectionPageItems, v as recordOutboxActivity, x as registerQueueDepthGauge, y as recordOutboxEnqueue } from "./metrics-D1p6hv4z.mjs";
4
+ import { n as version, t as name } from "./deno-Dn-py9Zy.mjs";
5
+ import { 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, n as getDurationMs, o as isAbortError, r as getFederationMetrics, u as recordCollectionPageItems, v as recordOutboxActivity, x as registerQueueDepthGauge, y as recordOutboxEnqueue } from "./metrics-Beod6tzO.mjs";
6
6
  import { t as formatAcceptSignature } from "./accept-CPVqki78.mjs";
7
- import { d as hasPortableActor, f as isCompatibleKeyId, m as isPortableUri, o as importJwk, s as validateCryptoKey, t as exportJwk, u as getGatewayKeyBase } from "./key-IxTTC8Yf.mjs";
7
+ import { d as hasPortableActor, f as isCompatibleKeyId, l as getCanonicalPortableId, m as isPortableUri, o as importJwk, s as validateCryptoKey, t as exportJwk, u as getGatewayKeyBase } from "./key-BJrp524f.mjs";
8
8
  import { r as normalizeOutgoingActivityJsonLd } from "./outgoing-jsonld-4IJqPYos.mjs";
9
- 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-l0B9nREb.mjs";
10
- import { a as verifyObject, n as hasProofLike, o as verifyPortableObjectProof, r as signObject } from "./proof-CVL7JnZd.mjs";
11
- import { l as verifyRequest, o as parseRfc9421SignatureInput, u as verifyRequestDetailed } from "./http-CCJ97Jql.mjs";
12
- import { t as getAuthenticatedDocumentLoader } from "./docloader-dA0RswUV.mjs";
13
- import { n as kvCache } from "./kv-cache-Bwwbh-xR.mjs";
9
+ 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-BmFz5NLA.mjs";
10
+ import { a as verifyObject, n as hasProofLike, o as verifyPortableObjectProof, r as signObject } from "./proof-ozG4HfcQ.mjs";
11
+ import { l as verifyRequest, o as parseRfc9421SignatureInput, u as verifyRequestDetailed } from "./http-B0VRNipE.mjs";
12
+ import { t as getAuthenticatedDocumentLoader } from "./docloader-BUq5bYJr.mjs";
13
+ import { n as kvCache } from "./kv-cache-C4C3LyYW.mjs";
14
14
  import { d as readBoundedText, l as BodyTooLargeError, t as getNodeInfo, u as MAX_BODY_SIZE } from "./client-DpnpgjyX.mjs";
15
15
  import { t as nodeInfoToJson } from "./types-DE_LV1JB.mjs";
16
- import { n as getKeyOwner, t as doesActorOwnKey } from "./owner-DGbYmXqU.mjs";
17
- import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "./send-Ejy1jOz9.mjs";
18
- import { n as FederationBuilderImpl, t as ACTOR_ALIAS_PREFIX } from "./builder-i3jxVe9m.mjs";
16
+ import { n as getKeyOwner, t as doesActorOwnKey } from "./owner-CsKVgMzk.mjs";
17
+ import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "./send-ClPt8nYu.mjs";
18
+ import { n as FederationBuilderImpl, t as ACTOR_ALIAS_PREFIX } from "./builder-BGlpUocK.mjs";
19
19
  import { t as CircuitBreaker } from "./circuit-breaker-C5NqoLBJ.mjs";
20
20
  import { t as buildCollectionSynchronizationHeader } from "./collection-CnnP1UAv.mjs";
21
- import { a as verifyCompoundPortableObjectProofs, i as inspectCompoundPortableObjectApplicability, r as findUnsupportedCompoundProofShape, t as containsCompoundPortableObject } from "./compound-proof-Cayy-ECj.mjs";
21
+ import { a as verifyCompoundPortableObjectProofs, i as inspectCompoundPortableObjectApplicability, r as findUnsupportedCompoundProofShape, t as containsCompoundPortableObject } from "./compound-proof-B6Aj2kGY.mjs";
22
22
  import { t as KvKeyCache } from "./keycache-BUDHB5rY.mjs";
23
23
  import { t as acceptsJsonLd } from "./negotiation-CCocWT_N.mjs";
24
- import { t as hasMalformedKnownTemporalLiteral } from "./temporal-DNJ1cNpr.mjs";
24
+ import { t as hasMalformedKnownTemporalLiteral } from "./temporal-Ba0EAZAI.mjs";
25
25
  import { t as createExponentialBackoffPolicy } from "./retry-zX5xw5e0.mjs";
26
26
  import { t as TaskCodec } from "./codec-C6rTrpIp.mjs";
27
27
  import { getLogger, withContext } from "@logtape/logtape";
@@ -2670,6 +2670,7 @@ function resolvePortableInboxRecipient(actor, { authority, canonicalInboxId, loc
2670
2670
  return {
2671
2671
  status: "accepted",
2672
2672
  recipient: {
2673
+ actorId: id,
2673
2674
  inboxId,
2674
2675
  canonicalInboxId,
2675
2676
  gateways
@@ -2703,8 +2704,11 @@ function canonicalize(iri) {
2703
2704
  * Forwarding is therefore best effort. Without {@link KvStore.cas}, nothing is
2704
2705
  * forwarded.
2705
2706
  *
2706
- * Forwarded requests are not signed with HTTP Signatures; the receiving
2707
- * gateways authenticate the activity by its own proof.
2707
+ * Forwarded requests are signed with HTTP Signatures by the RSA key among
2708
+ * {@link ForwardPortableInboxActivityParameters.getKeys}, if any, so that
2709
+ * servers requiring HTTP Signatures accept them, and are sent unsigned
2710
+ * otherwise. Either way, the receiving gateways authenticate the activity by
2711
+ * its own proof.
2708
2712
  * @param parameters The parameters.
2709
2713
  * @returns The target inbox URLs that the activity was handed off to.
2710
2714
  */
@@ -2776,20 +2780,59 @@ function getForwardingTargets(recipient, excludedOrigins) {
2776
2780
  }
2777
2781
  return targets;
2778
2782
  }
2779
- async function forwardImmediately({ activity, activityId, activityType, allowPrivateAddress, options, meterProvider, tracerProvider }, inboxes) {
2783
+ /**
2784
+ * Resolves the key that signs forwarded requests: the first RSASSA-PKCS1-v1_5
2785
+ * key, as {@link sendActivity} signs requests only with such a key. Since
2786
+ * the targets have already been claimed, the activity is forwarded unsigned
2787
+ * rather than not at all if the key cannot be resolved or used.
2788
+ */
2789
+ async function resolveForwardingKey({ getKeys, activityId }) {
2790
+ if (getKeys == null) return null;
2791
+ const logger = getLogger([
2792
+ "fedify",
2793
+ "federation",
2794
+ "inbox"
2795
+ ]);
2796
+ let key;
2797
+ try {
2798
+ key = (await getKeys()).find((k) => k.privateKey.algorithm.name === "RSASSA-PKCS1-v1_5");
2799
+ } catch (error) {
2800
+ logger.error("Failed to get the gateway keys to sign forwarded requests with; forwarding activity {activityId} without HTTP Signatures:\n{error}", {
2801
+ activityId: activityId.href,
2802
+ error
2803
+ });
2804
+ return null;
2805
+ }
2806
+ if (key == null) return null;
2807
+ try {
2808
+ validateCryptoKey(key.privateKey, "private");
2809
+ } catch (error) {
2810
+ if (!(error instanceof TypeError)) throw error;
2811
+ logger.warn("The gateway key {keyId} cannot sign forwarded requests; forwarding activity {activityId} without HTTP Signatures:\n{error}", {
2812
+ keyId: key.keyId.href,
2813
+ activityId: activityId.href,
2814
+ error
2815
+ });
2816
+ return null;
2817
+ }
2818
+ return key;
2819
+ }
2820
+ async function forwardImmediately(parameters, inboxes) {
2821
+ const { activity, activityId, activityType, allowPrivateAddress, specDeterminer, options, meterProvider, tracerProvider } = parameters;
2780
2822
  const logger = getLogger([
2781
2823
  "fedify",
2782
2824
  "federation",
2783
2825
  "inbox"
2784
2826
  ]);
2785
2827
  const deadline = (options ?? resolvePortableInboxForwardingOptions()).deadline.total("millisecond");
2786
- const sends = inboxes.map((inbox) => sendActivity({
2828
+ const forwarding = resolveForwardingKey(parameters).then((key) => Promise.all(inboxes.map((inbox) => sendActivity({
2787
2829
  activity,
2788
2830
  activityId: activityId.href,
2789
2831
  activityType,
2790
- keys: [],
2832
+ keys: key == null ? [] : [key],
2791
2833
  inbox,
2792
2834
  allowPrivateAddress,
2835
+ specDeterminer,
2793
2836
  meterProvider,
2794
2837
  tracerProvider
2795
2838
  }).catch((error) => {
@@ -2798,9 +2841,9 @@ async function forwardImmediately({ activity, activityId, activityType, allowPri
2798
2841
  inbox: inbox.href,
2799
2842
  error
2800
2843
  });
2801
- }));
2844
+ }))));
2802
2845
  let timer;
2803
- const timedOut = await Promise.race([Promise.all(sends).then(() => false), new Promise((resolve) => {
2846
+ const timedOut = await Promise.race([forwarding.then(() => false), new Promise((resolve) => {
2804
2847
  timer = setTimeout(() => resolve(true), deadline);
2805
2848
  })]);
2806
2849
  clearTimeout(timer);
@@ -2809,12 +2852,27 @@ async function forwardImmediately({ activity, activityId, activityType, allowPri
2809
2852
  deadline
2810
2853
  });
2811
2854
  }
2812
- async function enqueueForwarding({ activity, activityId, activityType, baseUrl, startQueue, meterProvider }, outboxQueue, inboxes) {
2855
+ async function enqueueForwarding(parameters, outboxQueue, inboxes) {
2856
+ const { activity, activityId, activityType, baseUrl, startQueue, meterProvider } = parameters;
2813
2857
  const logger = getLogger([
2814
2858
  "fedify",
2815
2859
  "federation",
2816
2860
  "inbox"
2817
2861
  ]);
2862
+ const key = await resolveForwardingKey(parameters);
2863
+ const keys = [];
2864
+ if (key != null) try {
2865
+ keys.push({
2866
+ keyId: key.keyId.href,
2867
+ privateKey: await exportJwk(key.privateKey)
2868
+ });
2869
+ } catch (error) {
2870
+ logger.error("Failed to export the gateway key {keyId}; forwarding activity {activityId} without HTTP Signatures:\n{error}", {
2871
+ keyId: key.keyId.href,
2872
+ activityId: activityId.href,
2873
+ error
2874
+ });
2875
+ }
2818
2876
  startQueue?.();
2819
2877
  const started = (/* @__PURE__ */ new Date()).toISOString();
2820
2878
  const traceContext = {};
@@ -2824,7 +2882,7 @@ async function enqueueForwarding({ activity, activityId, activityType, baseUrl,
2824
2882
  type: "outbox",
2825
2883
  id: crypto.randomUUID(),
2826
2884
  baseUrl,
2827
- keys: [],
2885
+ keys,
2828
2886
  activity,
2829
2887
  activityId: activityId.href,
2830
2888
  activityType,
@@ -5325,6 +5383,8 @@ var FederationImpl = class extends FederationBuilderImpl {
5325
5383
  outboxQueue: this.outboxQueue,
5326
5384
  startQueue: this.manuallyStartQueue ? void 0 : () => this._startQueueInternal(contextData),
5327
5385
  allowPrivateAddress: this.allowPrivateAddress,
5386
+ getKeys: () => this.#getPortableGatewayKeyPairs(context, identifier, recipient),
5387
+ specDeterminer: new KvSpecDeterminer(this.kv, this.kvPrefixes.httpMessageSignaturesSpec, this.firstKnock, { specTtl: this.httpMessageSignaturesSpecTtl }),
5328
5388
  options: this.portableInboxForwarding,
5329
5389
  meterProvider: this.meterProvider,
5330
5390
  tracerProvider: this.tracerProvider
@@ -5333,6 +5393,49 @@ var FederationImpl = class extends FederationBuilderImpl {
5333
5393
  }
5334
5394
  });
5335
5395
  }
5396
+ /**
5397
+ * Gets this server's gateway key pairs for the portable actor that owns
5398
+ * a portable inbox, which sign the activities forwarded from the inbox.
5399
+ * @param context The context.
5400
+ * @param identifier The identifier of the portable actor.
5401
+ * @param recipient The portable inbox.
5402
+ * @returns The key pairs, or an empty array if the actor has no gateway
5403
+ * keys on this server.
5404
+ */
5405
+ async #getPortableGatewayKeyPairs(context, identifier, recipient) {
5406
+ const logger = getLogger([
5407
+ "fedify",
5408
+ "federation",
5409
+ "inbox"
5410
+ ]);
5411
+ if (this.actorCallbacks?.keyPairsDispatcher == null || this.actorCallbacks.portableActorIdMapper == null) {
5412
+ logger.debug("Forwarding activities from the portable inbox {inbox} without HTTP Signatures, as gateway keys need both a key pairs dispatcher and a portable actor ID mapper.", { inbox: recipient.canonicalInboxId });
5413
+ return [];
5414
+ }
5415
+ const actorId = getCanonicalPortableId(recipient.actorId);
5416
+ if (actorId == null) return [];
5417
+ const keyPairs = await context.getActorKeyPairs(identifier);
5418
+ for (const { cryptographicKey } of keyPairs) {
5419
+ const owner = cryptographicKey.ownerId;
5420
+ const canonicalOwner = owner == null ? null : getCanonicalPortableId(owner);
5421
+ if (canonicalOwner === actorId) continue;
5422
+ if (canonicalOwner == null) logger.debug("Forwarding activities from the portable inbox {inbox} without HTTP Signatures, as the key pairs of the actor {identifier} are not the gateway keys of a portable actor.", {
5423
+ inbox: recipient.canonicalInboxId,
5424
+ identifier
5425
+ });
5426
+ else logger.warn("Forwarding activities from the portable inbox {inbox} without HTTP Signatures, as the key pairs of the actor {identifier} are the gateway keys of {owner}, not of the recipient {actorId}.", {
5427
+ inbox: recipient.canonicalInboxId,
5428
+ identifier,
5429
+ owner: canonicalOwner,
5430
+ actorId
5431
+ });
5432
+ return [];
5433
+ }
5434
+ return keyPairs.map(({ keyId, privateKey }) => ({
5435
+ keyId,
5436
+ privateKey
5437
+ }));
5438
+ }
5336
5439
  async #fetchHashlinkMedia(request, media, { onNotFound, contextData, span, metricState }) {
5337
5440
  metricState.endpoint = "hashlink_media";
5338
5441
  metricState.routeTemplate = HASHLINK_MEDIA_ROUTE_TEMPLATE;