@fedify/fedify 2.1.0-dev.565 → 2.1.0-dev.599

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 (96) hide show
  1. package/dist/accept-D7sAxyNa.js +143 -0
  2. package/dist/{assert_rejects-Ce45JcFg.js → assert_rejects-0h7I2Esa.js} +1 -1
  3. package/dist/{builder-Deoi2N2z.js → builder-rlJT9XsH.js} +3 -3
  4. package/dist/compat/mod.d.cts +3 -3
  5. package/dist/compat/mod.d.ts +3 -3
  6. package/dist/compat/transformers.test.js +17 -16
  7. package/dist/{context-DL0cPpPV.d.cts → context-BcqA-0BL.d.cts} +52 -2
  8. package/dist/{context--RwChtri.d.ts → context-DyJjQQ_H.d.ts} +52 -2
  9. package/dist/{deno-CEdy89j9.js → deno-BYv1FXyT.js} +1 -2
  10. package/dist/{docloader-CL1QPJzN.js → docloader-D3dGL8MN.js} +2 -2
  11. package/dist/federation/builder.test.js +7 -7
  12. package/dist/federation/collection.test.js +5 -5
  13. package/dist/federation/handler.test.js +806 -26
  14. package/dist/federation/idempotency.test.js +22 -21
  15. package/dist/federation/inbox.test.js +3 -3
  16. package/dist/federation/keycache.test.js +1 -1
  17. package/dist/federation/kv.test.js +4 -4
  18. package/dist/federation/middleware.test.js +22 -21
  19. package/dist/federation/mod.cjs +4 -4
  20. package/dist/federation/mod.d.cts +4 -4
  21. package/dist/federation/mod.d.ts +4 -4
  22. package/dist/federation/mod.js +4 -4
  23. package/dist/federation/mq.test.js +4 -4
  24. package/dist/federation/negotiation.test.js +5 -5
  25. package/dist/federation/retry.test.js +2 -2
  26. package/dist/federation/router.test.js +4 -4
  27. package/dist/federation/send.test.js +11 -10
  28. package/dist/federation/webfinger.test.js +22 -21
  29. package/dist/{http-DsqqmkXi.d.cts → http-BudnHZE2.d.cts} +229 -1
  30. package/dist/{http-iDlaLy8a.cjs → http-Cjdbgipx.cjs} +307 -50
  31. package/dist/{http-BbfOqHGG.d.ts → http-Dax_FIBo.d.ts} +229 -1
  32. package/dist/{http-Dm9n1mRe.js → http-SDJbghtm.js} +144 -49
  33. package/dist/{http-VpqmUjje.js → http-WrV4DdQ1.js} +278 -51
  34. package/dist/{inbox-CMtnW0RE.js → inbox-gPJ0RaKj.js} +1 -1
  35. package/dist/{key-B0yADkL8.js → key-BSOrewQw.js} +1 -1
  36. package/dist/{kv-cache-BSATpUtX.js → kv-cache-D0a-g8yG.js} +1 -1
  37. package/dist/{kv-cache-551Om14-.cjs → kv-cache-sn8V-LU_.cjs} +1 -1
  38. package/dist/{ld-BBmbv1nb.js → ld-8UNDFIO0.js} +3 -3
  39. package/dist/middleware-74Kx7iWO.cjs +12 -0
  40. package/dist/{middleware-DpdPMZII.js → middleware-Brge9sYu.js} +4 -4
  41. package/dist/{middleware-Cldp2YSv.js → middleware-CWItYNUL.js} +113 -34
  42. package/dist/{middleware-Cx0tTbX1.js → middleware-C_89nqvv.js} +95 -18
  43. package/dist/{middleware-D11GYoP-.cjs → middleware-PGDxr3nC.cjs} +94 -17
  44. package/dist/middleware-nfE7By0g.js +27 -0
  45. package/dist/{mod-DE8MYisy.d.cts → mod-B7QkWzrL.d.cts} +1 -1
  46. package/dist/{mod-DKG0ovjR.d.cts → mod-Bx9jcLB8.d.cts} +1 -1
  47. package/dist/{mod-CFBU2OT3.d.cts → mod-Coe7KEgX.d.cts} +1 -1
  48. package/dist/{mod-BugwI0JN.d.ts → mod-Cs2dYEwI.d.ts} +1 -1
  49. package/dist/{mod-DcfFNgYf.d.ts → mod-D6MdymW7.d.ts} +1 -1
  50. package/dist/{mod-CvxylbuV.d.ts → mod-D6dOd--H.d.ts} +1 -1
  51. package/dist/{mod-Z7lIaCfo.d.ts → mod-SMHOMNpZ.d.ts} +1 -1
  52. package/dist/{mod-Dp0kK0hO.d.cts → mod-em2Il1eD.d.cts} +1 -1
  53. package/dist/mod.cjs +12 -4
  54. package/dist/mod.d.cts +8 -8
  55. package/dist/mod.d.ts +8 -8
  56. package/dist/mod.js +9 -5
  57. package/dist/nodeinfo/client.test.js +4 -4
  58. package/dist/nodeinfo/handler.test.js +22 -21
  59. package/dist/nodeinfo/types.test.js +4 -4
  60. package/dist/otel/exporter.test.js +4 -4
  61. package/dist/{owner-C1ZyG4NL.js → owner-D1i3Gz1q.js} +1 -1
  62. package/dist/{proof-wclcUq0C.js → proof-Blm7rPHe.js} +2 -2
  63. package/dist/{proof-CgK60TcQ.cjs → proof-DFqGzNZi.cjs} +3 -3
  64. package/dist/{proof-DnRq8s8f.js → proof-DgU0YpXY.js} +2 -2
  65. package/dist/{send-DNJyYRVU.js → send-Ban_thmx.js} +2 -2
  66. package/dist/sig/accept.test.d.ts +3 -0
  67. package/dist/sig/accept.test.js +451 -0
  68. package/dist/sig/http.test.js +452 -27
  69. package/dist/sig/key.test.js +7 -7
  70. package/dist/sig/ld.test.js +6 -6
  71. package/dist/sig/mod.cjs +6 -2
  72. package/dist/sig/mod.d.cts +3 -3
  73. package/dist/sig/mod.d.ts +3 -3
  74. package/dist/sig/mod.js +3 -3
  75. package/dist/sig/owner.test.js +8 -8
  76. package/dist/sig/proof.test.js +8 -8
  77. package/dist/testing/mod.js +1 -1
  78. package/dist/utils/docloader.test.js +10 -9
  79. package/dist/utils/kv-cache.test.js +1 -1
  80. package/dist/utils/mod.cjs +2 -2
  81. package/dist/utils/mod.d.cts +2 -2
  82. package/dist/utils/mod.d.ts +2 -2
  83. package/dist/utils/mod.js +2 -2
  84. package/package.json +6 -7
  85. package/dist/middleware-BDr0P6dx.cjs +0 -12
  86. package/dist/middleware-BZ8WpBo6.js +0 -26
  87. /package/dist/{assert_not_equals-C80BG-_5.js → assert_not_equals-f3m3epl3.js} +0 -0
  88. /package/dist/{assert_throws-BNXdRGWP.js → assert_throws-rjdMBf31.js} +0 -0
  89. /package/dist/{collection-CcnIw1qY.js → collection-CSzG2j1P.js} +0 -0
  90. /package/dist/{context-pa9aIrwp.js → context-Aqenou7c.js} +0 -0
  91. /package/dist/{keycache-C7k8s1Bk.js → keycache-CpGWAUbj.js} +0 -0
  92. /package/dist/{keys-ZbcByPg9.js → keys-BFve7QQv.js} +0 -0
  93. /package/dist/{kv-cache-El7We5sy.js → kv-cache-Bw2F2ABq.js} +0 -0
  94. /package/dist/{negotiation-5NPJL6zp.js → negotiation-BlAuS_nr.js} +0 -0
  95. /package/dist/{retry-D4GJ670a.js → retry-mqLf4b-R.js} +0 -0
  96. /package/dist/{std__assert-DWivtrGR.js → std__assert-X-_kMxKM.js} +0 -0
@@ -3,25 +3,27 @@
3
3
  import { URLPattern } from "urlpattern-polyfill";
4
4
  globalThis.addEventListener = () => {};
5
5
 
6
- import { deno_default } from "./deno-CEdy89j9.js";
6
+ import { deno_default } from "./deno-BYv1FXyT.js";
7
7
  import { getNodeInfo } from "./client-CoCIaTNO.js";
8
8
  import { RouterError } from "./router-D9eI0s4b.js";
9
9
  import { nodeInfoToJson } from "./types-CPz01LGH.js";
10
- import { exportJwk, importJwk, validateCryptoKey } from "./key-B0yADkL8.js";
11
- import { verifyRequest, verifyRequestDetailed } from "./http-Dm9n1mRe.js";
12
- import { detachSignature, hasSignature, signJsonLd, verifyJsonLd } from "./ld-BBmbv1nb.js";
13
- import { doesActorOwnKey, getKeyOwner } from "./owner-C1ZyG4NL.js";
14
- import { signObject, verifyObject } from "./proof-wclcUq0C.js";
15
- import { getAuthenticatedDocumentLoader } from "./docloader-CL1QPJzN.js";
16
- import { kvCache } from "./kv-cache-El7We5sy.js";
17
- import { routeActivity } from "./inbox-CMtnW0RE.js";
18
- import { FederationBuilderImpl } from "./builder-Deoi2N2z.js";
19
- import { buildCollectionSynchronizationHeader } from "./collection-CcnIw1qY.js";
20
- import { KvKeyCache } from "./keycache-C7k8s1Bk.js";
21
- import { acceptsJsonLd } from "./negotiation-5NPJL6zp.js";
22
- import { createExponentialBackoffPolicy } from "./retry-D4GJ670a.js";
23
- import { SendActivityError, extractInboxes, sendActivity } from "./send-DNJyYRVU.js";
10
+ import { formatAcceptSignature } from "./accept-D7sAxyNa.js";
11
+ import { exportJwk, importJwk, validateCryptoKey } from "./key-BSOrewQw.js";
12
+ import { parseRfc9421SignatureInput, verifyRequest, verifyRequestDetailed } from "./http-SDJbghtm.js";
13
+ import { detachSignature, hasSignature, signJsonLd, verifyJsonLd } from "./ld-8UNDFIO0.js";
14
+ import { doesActorOwnKey, getKeyOwner } from "./owner-D1i3Gz1q.js";
15
+ import { signObject, verifyObject } from "./proof-Blm7rPHe.js";
16
+ import { getAuthenticatedDocumentLoader } from "./docloader-D3dGL8MN.js";
17
+ import { kvCache } from "./kv-cache-Bw2F2ABq.js";
18
+ import { routeActivity } from "./inbox-gPJ0RaKj.js";
19
+ import { FederationBuilderImpl } from "./builder-rlJT9XsH.js";
20
+ import { buildCollectionSynchronizationHeader } from "./collection-CSzG2j1P.js";
21
+ import { KvKeyCache } from "./keycache-CpGWAUbj.js";
22
+ import { acceptsJsonLd } from "./negotiation-BlAuS_nr.js";
23
+ import { createExponentialBackoffPolicy } from "./retry-mqLf4b-R.js";
24
+ import { SendActivityError, extractInboxes, sendActivity } from "./send-Ban_thmx.js";
24
25
  import { Activity, Collection, CollectionPage, CryptographicKey, Link, Multikey, Object as Object$1, OrderedCollection, OrderedCollectionPage, getTypeId, lookupObject, traverseCollection } from "@fedify/vocab";
26
+ import { uniq } from "es-toolkit";
25
27
  import { FetchError, getDocumentLoader } from "@fedify/vocab-runtime";
26
28
  import { lookupWebFinger } from "@fedify/webfinger";
27
29
  import { getLogger, withContext } from "@logtape/logtape";
@@ -410,7 +412,7 @@ async function handleInbox(request, options) {
410
412
  * @returns A promise that resolves to an HTTP response.
411
413
  */
412
414
  async function handleInboxInternal(request, parameters, span) {
413
- const { recipient, context: ctx, inboxContextFactory, kv, kvPrefixes, queue, actorDispatcher, inboxListeners, inboxErrorHandler, unverifiedActivityHandler, onNotFound, signatureTimeWindow, skipSignatureVerification, tracerProvider } = parameters;
415
+ const { recipient, context: ctx, inboxContextFactory, kv, kvPrefixes, queue, actorDispatcher, inboxListeners, inboxErrorHandler, unverifiedActivityHandler, onNotFound, signatureTimeWindow, skipSignatureVerification, inboxChallengePolicy, tracerProvider } = parameters;
414
416
  const logger$2 = getLogger([
415
417
  "fedify",
416
418
  "federation",
@@ -562,6 +564,7 @@ async function handleInboxInternal(request, parameters, span) {
562
564
  }
563
565
  }
564
566
  let httpSigKey = null;
567
+ let pendingNonceLabel;
565
568
  if (activity == null) {
566
569
  if (!skipSignatureVerification) {
567
570
  const verification = await verifyRequestDetailed(request, {
@@ -582,10 +585,7 @@ async function handleInboxInternal(request, parameters, span) {
582
585
  code: SpanStatusCode.ERROR,
583
586
  message: `Failed to verify the request's HTTP Signatures.`
584
587
  });
585
- if (unverifiedActivityHandler == null) return new Response("Failed to verify the request signature.", {
586
- status: 401,
587
- headers: { "Content-Type": "text/plain; charset=utf-8" }
588
- });
588
+ if (unverifiedActivityHandler == null) return await getFailedSignatureResponse(inboxChallengePolicy, kv, kvPrefixes);
589
589
  try {
590
590
  activity = await Activity.fromJsonLd(jsonWithoutSig, ctx);
591
591
  } catch (error) {
@@ -639,17 +639,12 @@ async function handleInboxInternal(request, parameters, span) {
639
639
  recipient
640
640
  });
641
641
  }
642
- return new Response("Failed to verify the request signature.", {
643
- status: 401,
644
- headers: { "Content-Type": "text/plain; charset=utf-8" }
645
- });
642
+ return await getFailedSignatureResponse(inboxChallengePolicy, kv, kvPrefixes);
646
643
  }
647
644
  if (response instanceof Response) return response;
648
- return new Response("Failed to verify the request signature.", {
649
- status: 401,
650
- headers: { "Content-Type": "text/plain; charset=utf-8" }
651
- });
645
+ return await getFailedSignatureResponse(inboxChallengePolicy, kv, kvPrefixes);
652
646
  } else {
647
+ if (inboxChallengePolicy?.enabled && inboxChallengePolicy.requestNonce) pendingNonceLabel = verification.signatureLabel;
653
648
  logger$2.debug("HTTP Signatures are verified.", { recipient });
654
649
  activityVerified = true;
655
650
  }
@@ -682,6 +677,13 @@ async function handleInboxInternal(request, parameters, span) {
682
677
  headers: { "Content-Type": "text/plain; charset=utf-8" }
683
678
  });
684
679
  }
680
+ if (pendingNonceLabel != null) {
681
+ const nonceValid = await verifySignatureNonce(request, kv, kvPrefixes.acceptSignatureNonce, pendingNonceLabel);
682
+ if (!nonceValid) {
683
+ logger$2.error("Signature nonce verification failed (missing, expired, or replayed).", { recipient });
684
+ return await getFailedSignatureResponse(inboxChallengePolicy, kv, kvPrefixes);
685
+ }
686
+ }
685
687
  const routeResult = await routeActivity({
686
688
  context: ctx,
687
689
  json,
@@ -1146,6 +1148,79 @@ async function respondWithObjectIfAcceptable(object, request, options) {
1146
1148
  response.headers.set("Vary", "Accept");
1147
1149
  return response;
1148
1150
  }
1151
+ function generateNonce() {
1152
+ const bytes = new Uint8Array(16);
1153
+ crypto.getRandomValues(bytes);
1154
+ return btoa(String.fromCharCode(...bytes)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
1155
+ }
1156
+ async function verifySignatureNonce(request, kv, noncePrefix, verifiedLabel) {
1157
+ const signatureInput = request.headers.get("Signature-Input");
1158
+ if (signatureInput == null) return false;
1159
+ const parsed = parseRfc9421SignatureInput(signatureInput);
1160
+ if (verifiedLabel == null) return false;
1161
+ const sig = parsed[verifiedLabel];
1162
+ if (sig == null) return false;
1163
+ const nonce = sig.nonce;
1164
+ if (nonce == null) return false;
1165
+ const key = [...noncePrefix, nonce];
1166
+ if (kv.cas != null) return await kv.cas(key, true, void 0);
1167
+ const stored = await kv.get(key);
1168
+ if (stored != null) {
1169
+ await kv.delete(key);
1170
+ return true;
1171
+ }
1172
+ return false;
1173
+ }
1174
+ const getFailedSignatureResponse = async (policy, kv, kvPrefixes) => {
1175
+ const headers = await getFailedSignatureHeaders(policy, kv, kvPrefixes);
1176
+ return new Response("Failed to verify the request signature.", {
1177
+ status: 401,
1178
+ headers
1179
+ });
1180
+ };
1181
+ const getFailedSignatureHeaders = async (policy, kv, kvPrefixes) => ({
1182
+ "Content-Type": "text/plain; charset=utf-8",
1183
+ ...policy?.enabled && {
1184
+ "Accept-Signature": await buildAcceptSignatureHeader(policy, kv, kvPrefixes.acceptSignatureNonce),
1185
+ "Cache-Control": "no-store",
1186
+ "Vary": "Accept, Signature"
1187
+ }
1188
+ });
1189
+ async function buildAcceptSignatureHeader(policy, kv, noncePrefix) {
1190
+ const parameters = { created: true };
1191
+ if (policy.requestNonce) {
1192
+ const nonce = generateNonce();
1193
+ const key = [...noncePrefix, nonce];
1194
+ await setKey(kv, key, policy);
1195
+ parameters.nonce = nonce;
1196
+ }
1197
+ const baseComponents = policy.components ?? DEF_COMPONENTS;
1198
+ const components = uniq(MIN_COMPONENTS.concat(baseComponents)).filter((c) => c !== "@status").map((v) => ({
1199
+ value: v,
1200
+ params: {}
1201
+ }));
1202
+ return formatAcceptSignature([{
1203
+ label: "sig1",
1204
+ components,
1205
+ parameters
1206
+ }]);
1207
+ }
1208
+ async function setKey(kv, key, policy) {
1209
+ const seconds = policy.nonceTtlSeconds ?? 300;
1210
+ const ttl = Temporal.Duration.from({ seconds });
1211
+ await kv.set(key, true, { ttl });
1212
+ }
1213
+ const DEF_COMPONENTS = [
1214
+ "@method",
1215
+ "@target-uri",
1216
+ "@authority",
1217
+ "content-digest"
1218
+ ];
1219
+ const MIN_COMPONENTS = [
1220
+ "@method",
1221
+ "@target-uri",
1222
+ "@authority"
1223
+ ];
1149
1224
 
1150
1225
  //#endregion
1151
1226
  //#region src/federation/webfinger.ts
@@ -1315,6 +1390,7 @@ var FederationImpl = class extends FederationBuilderImpl {
1315
1390
  activityTransformers;
1316
1391
  _tracerProvider;
1317
1392
  firstKnock;
1393
+ inboxChallengePolicy;
1318
1394
  constructor(options) {
1319
1395
  super();
1320
1396
  this.kv = options.kv;
@@ -1323,6 +1399,7 @@ var FederationImpl = class extends FederationBuilderImpl {
1323
1399
  remoteDocument: ["_fedify", "remoteDocument"],
1324
1400
  publicKey: ["_fedify", "publicKey"],
1325
1401
  httpMessageSignaturesSpec: ["_fedify", "httpMessageSignaturesSpec"],
1402
+ acceptSignatureNonce: ["_fedify", "acceptSignatureNonce"],
1326
1403
  ...options.kvPrefixes ?? {}
1327
1404
  };
1328
1405
  if (options.queue == null) {
@@ -1392,6 +1469,7 @@ var FederationImpl = class extends FederationBuilderImpl {
1392
1469
  this.permanentFailureStatusCodes = options.permanentFailureStatusCodes ?? [404, 410];
1393
1470
  this.signatureTimeWindow = options.signatureTimeWindow ?? { hours: 1 };
1394
1471
  this.skipSignatureVerification = options.skipSignatureVerification ?? false;
1472
+ this.inboxChallengePolicy = options.inboxChallengePolicy;
1395
1473
  this.outboxRetryPolicy = options.outboxRetryPolicy ?? createExponentialBackoffPolicy();
1396
1474
  this.inboxRetryPolicy = options.inboxRetryPolicy ?? createExponentialBackoffPolicy();
1397
1475
  this.activityTransformers = options.activityTransformers ?? getDefaultActivityTransformers();
@@ -1638,11 +1716,11 @@ var FederationImpl = class extends FederationBuilderImpl {
1638
1716
  });
1639
1717
  throw error;
1640
1718
  }
1641
- const delay = this.outboxRetryPolicy({
1719
+ const delay$1 = this.outboxRetryPolicy({
1642
1720
  elapsedTime: Temporal.Instant.from(message.started).until(Temporal.Now.instant()),
1643
1721
  attempts: message.attempt
1644
1722
  });
1645
- if (delay != null) {
1723
+ if (delay$1 != null) {
1646
1724
  logger$2.error("Failed to send activity {activityId} to {inbox} (attempt #{attempt}); retry...:\n{error}", {
1647
1725
  ...logData,
1648
1726
  error
@@ -1650,7 +1728,7 @@ var FederationImpl = class extends FederationBuilderImpl {
1650
1728
  await this.outboxQueue?.enqueue({
1651
1729
  ...message,
1652
1730
  attempt: message.attempt + 1
1653
- }, { delay: Temporal.Duration.compare(delay, { seconds: 0 }) < 0 ? Temporal.Duration.from({ seconds: 0 }) : delay });
1731
+ }, { delay: Temporal.Duration.compare(delay$1, { seconds: 0 }) < 0 ? Temporal.Duration.from({ seconds: 0 }) : delay$1 });
1654
1732
  } else logger$2.error("Failed to send activity {activityId} to {inbox} after {attempt} attempts; giving up:\n{error}", {
1655
1733
  ...logData,
1656
1734
  error
@@ -1737,11 +1815,11 @@ var FederationImpl = class extends FederationBuilderImpl {
1737
1815
  span$1.end();
1738
1816
  throw error;
1739
1817
  }
1740
- const delay = this.inboxRetryPolicy({
1818
+ const delay$1 = this.inboxRetryPolicy({
1741
1819
  elapsedTime: Temporal.Instant.from(message.started).until(Temporal.Now.instant()),
1742
1820
  attempts: message.attempt
1743
1821
  });
1744
- if (delay != null) {
1822
+ if (delay$1 != null) {
1745
1823
  logger$2.error("Failed to process the incoming activity {activityId} (attempt #{attempt}); retry...:\n{error}", {
1746
1824
  error,
1747
1825
  attempt: message.attempt,
@@ -1752,7 +1830,7 @@ var FederationImpl = class extends FederationBuilderImpl {
1752
1830
  await this.inboxQueue?.enqueue({
1753
1831
  ...message,
1754
1832
  attempt: message.attempt + 1
1755
- }, { delay: Temporal.Duration.compare(delay, { seconds: 0 }) < 0 ? Temporal.Duration.from({ seconds: 0 }) : delay });
1833
+ }, { delay: Temporal.Duration.compare(delay$1, { seconds: 0 }) < 0 ? Temporal.Duration.from({ seconds: 0 }) : delay$1 });
1756
1834
  } else logger$2.error("Failed to process the incoming activity {activityId} after {trial} attempts; giving up:\n{error}", {
1757
1835
  error,
1758
1836
  activityId: activity.id?.href,
@@ -2134,6 +2212,7 @@ var FederationImpl = class extends FederationBuilderImpl {
2134
2212
  onNotFound,
2135
2213
  signatureTimeWindow: this.signatureTimeWindow,
2136
2214
  skipSignatureVerification: this.skipSignatureVerification,
2215
+ inboxChallengePolicy: this.inboxChallengePolicy,
2137
2216
  tracerProvider: this.tracerProvider,
2138
2217
  idempotencyStrategy: this.idempotencyStrategy
2139
2218
  });
@@ -3,14 +3,14 @@
3
3
  import { URLPattern } from "urlpattern-polyfill";
4
4
 
5
5
  import { getDefaultActivityTransformers } from "./transformers-C3FLHUd6.js";
6
- import { deno_default, doubleKnock, exportJwk, importJwk, validateCryptoKey, verifyRequest, verifyRequestDetailed } from "./http-VpqmUjje.js";
7
- import { detachSignature, doesActorOwnKey, getKeyOwner, hasSignature, signJsonLd, signObject, verifyJsonLd, verifyObject } from "./proof-DnRq8s8f.js";
6
+ import { deno_default, doubleKnock, exportJwk, formatAcceptSignature, importJwk, parseRfc9421SignatureInput, validateCryptoKey, verifyRequest, verifyRequestDetailed } from "./http-WrV4DdQ1.js";
7
+ import { detachSignature, doesActorOwnKey, getKeyOwner, hasSignature, signJsonLd, signObject, verifyJsonLd, verifyObject } from "./proof-DgU0YpXY.js";
8
8
  import { getNodeInfo, nodeInfoToJson } from "./types-C93Ob9cU.js";
9
- import { getAuthenticatedDocumentLoader, kvCache } from "./kv-cache-BSATpUtX.js";
9
+ import { getAuthenticatedDocumentLoader, kvCache } from "./kv-cache-D0a-g8yG.js";
10
10
  import { getLogger, withContext } from "@logtape/logtape";
11
11
  import { Activity, Collection, CollectionPage, CryptographicKey, Link, Multikey, Object as Object$1, OrderedCollection, OrderedCollectionPage, getTypeId, lookupObject, traverseCollection } from "@fedify/vocab";
12
12
  import { SpanKind, SpanStatusCode, context, propagation, trace } from "@opentelemetry/api";
13
- import { cloneDeep } from "es-toolkit";
13
+ import { cloneDeep, uniq } from "es-toolkit";
14
14
  import { Router } from "uri-template-router";
15
15
  import { parseTemplate } from "url-template";
16
16
  import { encodeHex } from "byte-encodings/hex";
@@ -339,7 +339,7 @@ var FederationBuilderImpl = class {
339
339
  this.collectionTypeIds = {};
340
340
  }
341
341
  async build(options) {
342
- const { FederationImpl: FederationImpl$1 } = await import("./middleware-DpdPMZII.js");
342
+ const { FederationImpl: FederationImpl$1 } = await import("./middleware-Brge9sYu.js");
343
343
  const f = new FederationImpl$1(options);
344
344
  const trailingSlashInsensitiveValue = f.router.trailingSlashInsensitive;
345
345
  f.router = this.router.clone();
@@ -1281,7 +1281,7 @@ async function handleInbox(request, options) {
1281
1281
  * @returns A promise that resolves to an HTTP response.
1282
1282
  */
1283
1283
  async function handleInboxInternal(request, parameters, span) {
1284
- const { recipient, context: ctx, inboxContextFactory, kv, kvPrefixes, queue, actorDispatcher, inboxListeners, inboxErrorHandler, unverifiedActivityHandler, onNotFound, signatureTimeWindow, skipSignatureVerification, tracerProvider } = parameters;
1284
+ const { recipient, context: ctx, inboxContextFactory, kv, kvPrefixes, queue, actorDispatcher, inboxListeners, inboxErrorHandler, unverifiedActivityHandler, onNotFound, signatureTimeWindow, skipSignatureVerification, inboxChallengePolicy, tracerProvider } = parameters;
1285
1285
  const logger$1 = getLogger([
1286
1286
  "fedify",
1287
1287
  "federation",
@@ -1433,6 +1433,7 @@ async function handleInboxInternal(request, parameters, span) {
1433
1433
  }
1434
1434
  }
1435
1435
  let httpSigKey = null;
1436
+ let pendingNonceLabel;
1436
1437
  if (activity == null) {
1437
1438
  if (!skipSignatureVerification) {
1438
1439
  const verification = await verifyRequestDetailed(request, {
@@ -1453,10 +1454,7 @@ async function handleInboxInternal(request, parameters, span) {
1453
1454
  code: SpanStatusCode.ERROR,
1454
1455
  message: `Failed to verify the request's HTTP Signatures.`
1455
1456
  });
1456
- if (unverifiedActivityHandler == null) return new Response("Failed to verify the request signature.", {
1457
- status: 401,
1458
- headers: { "Content-Type": "text/plain; charset=utf-8" }
1459
- });
1457
+ if (unverifiedActivityHandler == null) return await getFailedSignatureResponse(inboxChallengePolicy, kv, kvPrefixes);
1460
1458
  try {
1461
1459
  activity = await Activity.fromJsonLd(jsonWithoutSig, ctx);
1462
1460
  } catch (error) {
@@ -1510,17 +1508,12 @@ async function handleInboxInternal(request, parameters, span) {
1510
1508
  recipient
1511
1509
  });
1512
1510
  }
1513
- return new Response("Failed to verify the request signature.", {
1514
- status: 401,
1515
- headers: { "Content-Type": "text/plain; charset=utf-8" }
1516
- });
1511
+ return await getFailedSignatureResponse(inboxChallengePolicy, kv, kvPrefixes);
1517
1512
  }
1518
1513
  if (response instanceof Response) return response;
1519
- return new Response("Failed to verify the request signature.", {
1520
- status: 401,
1521
- headers: { "Content-Type": "text/plain; charset=utf-8" }
1522
- });
1514
+ return await getFailedSignatureResponse(inboxChallengePolicy, kv, kvPrefixes);
1523
1515
  } else {
1516
+ if (inboxChallengePolicy?.enabled && inboxChallengePolicy.requestNonce) pendingNonceLabel = verification.signatureLabel;
1524
1517
  logger$1.debug("HTTP Signatures are verified.", { recipient });
1525
1518
  activityVerified = true;
1526
1519
  }
@@ -1553,6 +1546,13 @@ async function handleInboxInternal(request, parameters, span) {
1553
1546
  headers: { "Content-Type": "text/plain; charset=utf-8" }
1554
1547
  });
1555
1548
  }
1549
+ if (pendingNonceLabel != null) {
1550
+ const nonceValid = await verifySignatureNonce(request, kv, kvPrefixes.acceptSignatureNonce, pendingNonceLabel);
1551
+ if (!nonceValid) {
1552
+ logger$1.error("Signature nonce verification failed (missing, expired, or replayed).", { recipient });
1553
+ return await getFailedSignatureResponse(inboxChallengePolicy, kv, kvPrefixes);
1554
+ }
1555
+ }
1556
1556
  const routeResult = await routeActivity({
1557
1557
  context: ctx,
1558
1558
  json,
@@ -2017,6 +2017,79 @@ async function respondWithObjectIfAcceptable(object, request, options) {
2017
2017
  response.headers.set("Vary", "Accept");
2018
2018
  return response;
2019
2019
  }
2020
+ function generateNonce() {
2021
+ const bytes = new Uint8Array(16);
2022
+ crypto.getRandomValues(bytes);
2023
+ return btoa(String.fromCharCode(...bytes)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
2024
+ }
2025
+ async function verifySignatureNonce(request, kv, noncePrefix, verifiedLabel) {
2026
+ const signatureInput = request.headers.get("Signature-Input");
2027
+ if (signatureInput == null) return false;
2028
+ const parsed = parseRfc9421SignatureInput(signatureInput);
2029
+ if (verifiedLabel == null) return false;
2030
+ const sig = parsed[verifiedLabel];
2031
+ if (sig == null) return false;
2032
+ const nonce = sig.nonce;
2033
+ if (nonce == null) return false;
2034
+ const key = [...noncePrefix, nonce];
2035
+ if (kv.cas != null) return await kv.cas(key, true, void 0);
2036
+ const stored = await kv.get(key);
2037
+ if (stored != null) {
2038
+ await kv.delete(key);
2039
+ return true;
2040
+ }
2041
+ return false;
2042
+ }
2043
+ const getFailedSignatureResponse = async (policy, kv, kvPrefixes) => {
2044
+ const headers = await getFailedSignatureHeaders(policy, kv, kvPrefixes);
2045
+ return new Response("Failed to verify the request signature.", {
2046
+ status: 401,
2047
+ headers
2048
+ });
2049
+ };
2050
+ const getFailedSignatureHeaders = async (policy, kv, kvPrefixes) => ({
2051
+ "Content-Type": "text/plain; charset=utf-8",
2052
+ ...policy?.enabled && {
2053
+ "Accept-Signature": await buildAcceptSignatureHeader(policy, kv, kvPrefixes.acceptSignatureNonce),
2054
+ "Cache-Control": "no-store",
2055
+ "Vary": "Accept, Signature"
2056
+ }
2057
+ });
2058
+ async function buildAcceptSignatureHeader(policy, kv, noncePrefix) {
2059
+ const parameters = { created: true };
2060
+ if (policy.requestNonce) {
2061
+ const nonce = generateNonce();
2062
+ const key = [...noncePrefix, nonce];
2063
+ await setKey(kv, key, policy);
2064
+ parameters.nonce = nonce;
2065
+ }
2066
+ const baseComponents = policy.components ?? DEF_COMPONENTS;
2067
+ const components = uniq(MIN_COMPONENTS.concat(baseComponents)).filter((c) => c !== "@status").map((v) => ({
2068
+ value: v,
2069
+ params: {}
2070
+ }));
2071
+ return formatAcceptSignature([{
2072
+ label: "sig1",
2073
+ components,
2074
+ parameters
2075
+ }]);
2076
+ }
2077
+ async function setKey(kv, key, policy) {
2078
+ const seconds = policy.nonceTtlSeconds ?? 300;
2079
+ const ttl = Temporal.Duration.from({ seconds });
2080
+ await kv.set(key, true, { ttl });
2081
+ }
2082
+ const DEF_COMPONENTS = [
2083
+ "@method",
2084
+ "@target-uri",
2085
+ "@authority",
2086
+ "content-digest"
2087
+ ];
2088
+ const MIN_COMPONENTS = [
2089
+ "@method",
2090
+ "@target-uri",
2091
+ "@authority"
2092
+ ];
2020
2093
 
2021
2094
  //#endregion
2022
2095
  //#region src/nodeinfo/handler.ts
@@ -2442,6 +2515,7 @@ var FederationImpl = class extends FederationBuilderImpl {
2442
2515
  activityTransformers;
2443
2516
  _tracerProvider;
2444
2517
  firstKnock;
2518
+ inboxChallengePolicy;
2445
2519
  constructor(options) {
2446
2520
  super();
2447
2521
  this.kv = options.kv;
@@ -2450,6 +2524,7 @@ var FederationImpl = class extends FederationBuilderImpl {
2450
2524
  remoteDocument: ["_fedify", "remoteDocument"],
2451
2525
  publicKey: ["_fedify", "publicKey"],
2452
2526
  httpMessageSignaturesSpec: ["_fedify", "httpMessageSignaturesSpec"],
2527
+ acceptSignatureNonce: ["_fedify", "acceptSignatureNonce"],
2453
2528
  ...options.kvPrefixes ?? {}
2454
2529
  };
2455
2530
  if (options.queue == null) {
@@ -2519,6 +2594,7 @@ var FederationImpl = class extends FederationBuilderImpl {
2519
2594
  this.permanentFailureStatusCodes = options.permanentFailureStatusCodes ?? [404, 410];
2520
2595
  this.signatureTimeWindow = options.signatureTimeWindow ?? { hours: 1 };
2521
2596
  this.skipSignatureVerification = options.skipSignatureVerification ?? false;
2597
+ this.inboxChallengePolicy = options.inboxChallengePolicy;
2522
2598
  this.outboxRetryPolicy = options.outboxRetryPolicy ?? createExponentialBackoffPolicy();
2523
2599
  this.inboxRetryPolicy = options.inboxRetryPolicy ?? createExponentialBackoffPolicy();
2524
2600
  this.activityTransformers = options.activityTransformers ?? getDefaultActivityTransformers();
@@ -3261,6 +3337,7 @@ var FederationImpl = class extends FederationBuilderImpl {
3261
3337
  onNotFound,
3262
3338
  signatureTimeWindow: this.signatureTimeWindow,
3263
3339
  skipSignatureVerification: this.skipSignatureVerification,
3340
+ inboxChallengePolicy: this.inboxChallengePolicy,
3264
3341
  tracerProvider: this.tracerProvider,
3265
3342
  idempotencyStrategy: this.idempotencyStrategy
3266
3343
  });
@@ -4,10 +4,10 @@
4
4
 
5
5
  const require_chunk = require('./chunk-CGaQZ11T.cjs');
6
6
  const require_transformers = require('./transformers-3g8GZwkZ.cjs');
7
- const require_http = require('./http-iDlaLy8a.cjs');
8
- const require_proof = require('./proof-CgK60TcQ.cjs');
7
+ const require_http = require('./http-Cjdbgipx.cjs');
8
+ const require_proof = require('./proof-DFqGzNZi.cjs');
9
9
  const require_types = require('./types-Cd_hszr_.cjs');
10
- const require_kv_cache = require('./kv-cache-551Om14-.cjs');
10
+ const require_kv_cache = require('./kv-cache-sn8V-LU_.cjs');
11
11
  const __logtape_logtape = require_chunk.__toESM(require("@logtape/logtape"));
12
12
  const __fedify_vocab = require_chunk.__toESM(require("@fedify/vocab"));
13
13
  const __opentelemetry_api = require_chunk.__toESM(require("@opentelemetry/api"));
@@ -340,7 +340,7 @@ var FederationBuilderImpl = class {
340
340
  this.collectionTypeIds = {};
341
341
  }
342
342
  async build(options) {
343
- const { FederationImpl: FederationImpl$1 } = await Promise.resolve().then(() => require("./middleware-BDr0P6dx.cjs"));
343
+ const { FederationImpl: FederationImpl$1 } = await Promise.resolve().then(() => require("./middleware-74Kx7iWO.cjs"));
344
344
  const f = new FederationImpl$1(options);
345
345
  const trailingSlashInsensitiveValue = f.router.trailingSlashInsensitive;
346
346
  f.router = this.router.clone();
@@ -1282,7 +1282,7 @@ async function handleInbox(request, options) {
1282
1282
  * @returns A promise that resolves to an HTTP response.
1283
1283
  */
1284
1284
  async function handleInboxInternal(request, parameters, span) {
1285
- const { recipient, context: ctx, inboxContextFactory, kv, kvPrefixes, queue, actorDispatcher, inboxListeners, inboxErrorHandler, unverifiedActivityHandler, onNotFound, signatureTimeWindow, skipSignatureVerification, tracerProvider } = parameters;
1285
+ const { recipient, context: ctx, inboxContextFactory, kv, kvPrefixes, queue, actorDispatcher, inboxListeners, inboxErrorHandler, unverifiedActivityHandler, onNotFound, signatureTimeWindow, skipSignatureVerification, inboxChallengePolicy, tracerProvider } = parameters;
1286
1286
  const logger$1 = (0, __logtape_logtape.getLogger)([
1287
1287
  "fedify",
1288
1288
  "federation",
@@ -1434,6 +1434,7 @@ async function handleInboxInternal(request, parameters, span) {
1434
1434
  }
1435
1435
  }
1436
1436
  let httpSigKey = null;
1437
+ let pendingNonceLabel;
1437
1438
  if (activity == null) {
1438
1439
  if (!skipSignatureVerification) {
1439
1440
  const verification = await require_http.verifyRequestDetailed(request, {
@@ -1454,10 +1455,7 @@ async function handleInboxInternal(request, parameters, span) {
1454
1455
  code: __opentelemetry_api.SpanStatusCode.ERROR,
1455
1456
  message: `Failed to verify the request's HTTP Signatures.`
1456
1457
  });
1457
- if (unverifiedActivityHandler == null) return new Response("Failed to verify the request signature.", {
1458
- status: 401,
1459
- headers: { "Content-Type": "text/plain; charset=utf-8" }
1460
- });
1458
+ if (unverifiedActivityHandler == null) return await getFailedSignatureResponse(inboxChallengePolicy, kv, kvPrefixes);
1461
1459
  try {
1462
1460
  activity = await __fedify_vocab.Activity.fromJsonLd(jsonWithoutSig, ctx);
1463
1461
  } catch (error) {
@@ -1511,17 +1509,12 @@ async function handleInboxInternal(request, parameters, span) {
1511
1509
  recipient
1512
1510
  });
1513
1511
  }
1514
- return new Response("Failed to verify the request signature.", {
1515
- status: 401,
1516
- headers: { "Content-Type": "text/plain; charset=utf-8" }
1517
- });
1512
+ return await getFailedSignatureResponse(inboxChallengePolicy, kv, kvPrefixes);
1518
1513
  }
1519
1514
  if (response instanceof Response) return response;
1520
- return new Response("Failed to verify the request signature.", {
1521
- status: 401,
1522
- headers: { "Content-Type": "text/plain; charset=utf-8" }
1523
- });
1515
+ return await getFailedSignatureResponse(inboxChallengePolicy, kv, kvPrefixes);
1524
1516
  } else {
1517
+ if (inboxChallengePolicy?.enabled && inboxChallengePolicy.requestNonce) pendingNonceLabel = verification.signatureLabel;
1525
1518
  logger$1.debug("HTTP Signatures are verified.", { recipient });
1526
1519
  activityVerified = true;
1527
1520
  }
@@ -1554,6 +1547,13 @@ async function handleInboxInternal(request, parameters, span) {
1554
1547
  headers: { "Content-Type": "text/plain; charset=utf-8" }
1555
1548
  });
1556
1549
  }
1550
+ if (pendingNonceLabel != null) {
1551
+ const nonceValid = await verifySignatureNonce(request, kv, kvPrefixes.acceptSignatureNonce, pendingNonceLabel);
1552
+ if (!nonceValid) {
1553
+ logger$1.error("Signature nonce verification failed (missing, expired, or replayed).", { recipient });
1554
+ return await getFailedSignatureResponse(inboxChallengePolicy, kv, kvPrefixes);
1555
+ }
1556
+ }
1557
1557
  const routeResult = await routeActivity({
1558
1558
  context: ctx,
1559
1559
  json,
@@ -2018,6 +2018,79 @@ async function respondWithObjectIfAcceptable(object, request, options) {
2018
2018
  response.headers.set("Vary", "Accept");
2019
2019
  return response;
2020
2020
  }
2021
+ function generateNonce() {
2022
+ const bytes = new Uint8Array(16);
2023
+ crypto.getRandomValues(bytes);
2024
+ return btoa(String.fromCharCode(...bytes)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
2025
+ }
2026
+ async function verifySignatureNonce(request, kv, noncePrefix, verifiedLabel) {
2027
+ const signatureInput = request.headers.get("Signature-Input");
2028
+ if (signatureInput == null) return false;
2029
+ const parsed = require_http.parseRfc9421SignatureInput(signatureInput);
2030
+ if (verifiedLabel == null) return false;
2031
+ const sig = parsed[verifiedLabel];
2032
+ if (sig == null) return false;
2033
+ const nonce = sig.nonce;
2034
+ if (nonce == null) return false;
2035
+ const key = [...noncePrefix, nonce];
2036
+ if (kv.cas != null) return await kv.cas(key, true, void 0);
2037
+ const stored = await kv.get(key);
2038
+ if (stored != null) {
2039
+ await kv.delete(key);
2040
+ return true;
2041
+ }
2042
+ return false;
2043
+ }
2044
+ const getFailedSignatureResponse = async (policy, kv, kvPrefixes) => {
2045
+ const headers = await getFailedSignatureHeaders(policy, kv, kvPrefixes);
2046
+ return new Response("Failed to verify the request signature.", {
2047
+ status: 401,
2048
+ headers
2049
+ });
2050
+ };
2051
+ const getFailedSignatureHeaders = async (policy, kv, kvPrefixes) => ({
2052
+ "Content-Type": "text/plain; charset=utf-8",
2053
+ ...policy?.enabled && {
2054
+ "Accept-Signature": await buildAcceptSignatureHeader(policy, kv, kvPrefixes.acceptSignatureNonce),
2055
+ "Cache-Control": "no-store",
2056
+ "Vary": "Accept, Signature"
2057
+ }
2058
+ });
2059
+ async function buildAcceptSignatureHeader(policy, kv, noncePrefix) {
2060
+ const parameters = { created: true };
2061
+ if (policy.requestNonce) {
2062
+ const nonce = generateNonce();
2063
+ const key = [...noncePrefix, nonce];
2064
+ await setKey(kv, key, policy);
2065
+ parameters.nonce = nonce;
2066
+ }
2067
+ const baseComponents = policy.components ?? DEF_COMPONENTS;
2068
+ const components = (0, es_toolkit.uniq)(MIN_COMPONENTS.concat(baseComponents)).filter((c) => c !== "@status").map((v) => ({
2069
+ value: v,
2070
+ params: {}
2071
+ }));
2072
+ return require_http.formatAcceptSignature([{
2073
+ label: "sig1",
2074
+ components,
2075
+ parameters
2076
+ }]);
2077
+ }
2078
+ async function setKey(kv, key, policy) {
2079
+ const seconds = policy.nonceTtlSeconds ?? 300;
2080
+ const ttl = Temporal.Duration.from({ seconds });
2081
+ await kv.set(key, true, { ttl });
2082
+ }
2083
+ const DEF_COMPONENTS = [
2084
+ "@method",
2085
+ "@target-uri",
2086
+ "@authority",
2087
+ "content-digest"
2088
+ ];
2089
+ const MIN_COMPONENTS = [
2090
+ "@method",
2091
+ "@target-uri",
2092
+ "@authority"
2093
+ ];
2021
2094
 
2022
2095
  //#endregion
2023
2096
  //#region src/nodeinfo/handler.ts
@@ -2443,6 +2516,7 @@ var FederationImpl = class extends FederationBuilderImpl {
2443
2516
  activityTransformers;
2444
2517
  _tracerProvider;
2445
2518
  firstKnock;
2519
+ inboxChallengePolicy;
2446
2520
  constructor(options) {
2447
2521
  super();
2448
2522
  this.kv = options.kv;
@@ -2451,6 +2525,7 @@ var FederationImpl = class extends FederationBuilderImpl {
2451
2525
  remoteDocument: ["_fedify", "remoteDocument"],
2452
2526
  publicKey: ["_fedify", "publicKey"],
2453
2527
  httpMessageSignaturesSpec: ["_fedify", "httpMessageSignaturesSpec"],
2528
+ acceptSignatureNonce: ["_fedify", "acceptSignatureNonce"],
2454
2529
  ...options.kvPrefixes ?? {}
2455
2530
  };
2456
2531
  if (options.queue == null) {
@@ -2520,6 +2595,7 @@ var FederationImpl = class extends FederationBuilderImpl {
2520
2595
  this.permanentFailureStatusCodes = options.permanentFailureStatusCodes ?? [404, 410];
2521
2596
  this.signatureTimeWindow = options.signatureTimeWindow ?? { hours: 1 };
2522
2597
  this.skipSignatureVerification = options.skipSignatureVerification ?? false;
2598
+ this.inboxChallengePolicy = options.inboxChallengePolicy;
2523
2599
  this.outboxRetryPolicy = options.outboxRetryPolicy ?? createExponentialBackoffPolicy();
2524
2600
  this.inboxRetryPolicy = options.inboxRetryPolicy ?? createExponentialBackoffPolicy();
2525
2601
  this.activityTransformers = options.activityTransformers ?? require_transformers.getDefaultActivityTransformers();
@@ -3262,6 +3338,7 @@ var FederationImpl = class extends FederationBuilderImpl {
3262
3338
  onNotFound,
3263
3339
  signatureTimeWindow: this.signatureTimeWindow,
3264
3340
  skipSignatureVerification: this.skipSignatureVerification,
3341
+ inboxChallengePolicy: this.inboxChallengePolicy,
3265
3342
  tracerProvider: this.tracerProvider,
3266
3343
  idempotencyStrategy: this.idempotencyStrategy
3267
3344
  });