@fedify/fedify 2.1.0-dev.503 → 2.1.0-dev.523

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 (78) hide show
  1. package/dist/{builder-BHUnSQtB.js → builder-CJkMYxxc.js} +9 -3
  2. package/dist/compat/mod.d.cts +3 -3
  3. package/dist/compat/mod.d.ts +3 -3
  4. package/dist/compat/transformers.test.js +12 -12
  5. package/dist/{context-DZJhUmzF.d.ts → context--RwChtri.d.ts} +54 -2
  6. package/dist/{context-D3QkEtZd.d.cts → context-DL0cPpPV.d.cts} +54 -2
  7. package/dist/{deno-BYerLnry.js → deno-CQdJQjC5.js} +1 -1
  8. package/dist/{docloader-MSkogD2T.js → docloader-Cyl0-S8m.js} +2 -2
  9. package/dist/federation/builder.test.js +14 -3
  10. package/dist/federation/handler.test.js +97 -13
  11. package/dist/federation/idempotency.test.js +12 -12
  12. package/dist/federation/inbox.test.js +2 -2
  13. package/dist/federation/keycache.test.js +46 -2
  14. package/dist/federation/middleware.test.js +206 -12
  15. package/dist/federation/mod.cjs +4 -4
  16. package/dist/federation/mod.d.cts +4 -4
  17. package/dist/federation/mod.d.ts +4 -4
  18. package/dist/federation/mod.js +4 -4
  19. package/dist/federation/send.test.js +5 -5
  20. package/dist/federation/webfinger.test.js +12 -12
  21. package/dist/{http-DkHdFfrc.d.ts → http-BbfOqHGG.d.ts} +80 -8
  22. package/dist/{http-DJT6NciB.cjs → http-D6a6mMc0.cjs} +305 -99
  23. package/dist/{http-CSX1-Mgi.js → http-DJmytoC2.js} +295 -101
  24. package/dist/{http-S2U3qDwN.js → http-DK0CTomU.js} +153 -57
  25. package/dist/{http-Cz3MlXAZ.d.cts → http-DsqqmkXi.d.cts} +80 -8
  26. package/dist/{inbox-BaA0g5I_.js → inbox-CWa6sqsk.js} +1 -1
  27. package/dist/{key-DCdTVZiK.js → key-DRgvVevp.js} +145 -47
  28. package/dist/keycache-C7k8s1Bk.js +102 -0
  29. package/dist/{kv-cache-Vtxhbo1W.cjs → kv-cache-DPtsJ1sL.cjs} +1 -1
  30. package/dist/{kv-cache-CQPL_aGY.js → kv-cache-MPcS_mGG.js} +1 -1
  31. package/dist/{ld-CrX7pQda.js → ld-s9_8WfBc.js} +2 -2
  32. package/dist/{middleware-CfI9C9Xy.js → middleware-2XtoTBq0.js} +12 -12
  33. package/dist/{middleware-MlO5iUeZ.js → middleware-Ajnk9qHB.js} +158 -22
  34. package/dist/middleware-BgCIhb_C.cjs +12 -0
  35. package/dist/{middleware-D4S6i4A_.cjs → middleware-BoCzk7-G.cjs} +158 -22
  36. package/dist/{middleware-C8PKuPrm.js → middleware-DGUNDGCl.js} +4 -4
  37. package/dist/{middleware-BelSJK7m.js → middleware-Dn9UDJZP.js} +100 -24
  38. package/dist/{mod-CwZXZJ9d.d.ts → mod-BugwI0JN.d.ts} +1 -1
  39. package/dist/{mod-DPkRU3EK.d.cts → mod-CFBU2OT3.d.cts} +1 -1
  40. package/dist/{mod-DUWcVv49.d.ts → mod-CvxylbuV.d.ts} +1 -1
  41. package/dist/{mod-DVwHUI_x.d.cts → mod-DE8MYisy.d.cts} +1 -1
  42. package/dist/{mod-DXsQakeS.d.cts → mod-DKG0ovjR.d.cts} +1 -1
  43. package/dist/{mod-DnSsduJF.d.ts → mod-DcfFNgYf.d.ts} +1 -1
  44. package/dist/{mod-Di3W5OdP.d.cts → mod-Dp0kK0hO.d.cts} +1 -1
  45. package/dist/{mod-DosD6NsG.d.ts → mod-Z7lIaCfo.d.ts} +1 -1
  46. package/dist/mod.cjs +8 -4
  47. package/dist/mod.d.cts +8 -8
  48. package/dist/mod.d.ts +8 -8
  49. package/dist/mod.js +7 -5
  50. package/dist/nodeinfo/handler.test.js +12 -12
  51. package/dist/otel/exporter.test.js +43 -2
  52. package/dist/otel/mod.cjs +7 -1
  53. package/dist/otel/mod.d.cts +12 -0
  54. package/dist/otel/mod.d.ts +12 -0
  55. package/dist/otel/mod.js +7 -1
  56. package/dist/{owner-BAlnLKMO.js → owner-Cx8gV-j4.js} +1 -1
  57. package/dist/{proof-DMgHaXNJ.js → proof-CDr3NP3R.js} +2 -2
  58. package/dist/{proof-BgUVmaJz.js → proof-Le4DAkqb.js} +1 -1
  59. package/dist/{proof-CR5RUAmy.cjs → proof-qHcNgE5i.cjs} +1 -1
  60. package/dist/{send-B2aZYf9A.js → send-DreBSY1U.js} +2 -2
  61. package/dist/sig/http.test.js +85 -5
  62. package/dist/sig/key.test.js +70 -3
  63. package/dist/sig/ld.test.js +3 -3
  64. package/dist/sig/mod.cjs +4 -2
  65. package/dist/sig/mod.d.cts +3 -3
  66. package/dist/sig/mod.d.ts +3 -3
  67. package/dist/sig/mod.js +3 -3
  68. package/dist/sig/owner.test.js +3 -3
  69. package/dist/sig/proof.test.js +3 -3
  70. package/dist/testing/mod.d.ts +92 -0
  71. package/dist/utils/docloader.test.js +4 -4
  72. package/dist/utils/mod.cjs +2 -2
  73. package/dist/utils/mod.d.cts +2 -2
  74. package/dist/utils/mod.d.ts +2 -2
  75. package/dist/utils/mod.js +2 -2
  76. package/package.json +5 -5
  77. package/dist/keycache-DRxpZ5r9.js +0 -48
  78. package/dist/middleware-D4XcpSBG.cjs +0 -12
@@ -3,10 +3,10 @@
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 } from "./http-CSX1-Mgi.js";
7
- import { detachSignature, doesActorOwnKey, getKeyOwner, hasSignature, signJsonLd, signObject, verifyJsonLd, verifyObject } from "./proof-BgUVmaJz.js";
6
+ import { deno_default, doubleKnock, exportJwk, importJwk, validateCryptoKey, verifyRequest, verifyRequestDetailed } from "./http-DJmytoC2.js";
7
+ import { detachSignature, doesActorOwnKey, getKeyOwner, hasSignature, signJsonLd, signObject, verifyJsonLd, verifyObject } from "./proof-Le4DAkqb.js";
8
8
  import { getNodeInfo, nodeInfoToJson } from "./types-C93Ob9cU.js";
9
- import { getAuthenticatedDocumentLoader, kvCache } from "./kv-cache-CQPL_aGY.js";
9
+ import { getAuthenticatedDocumentLoader, kvCache } from "./kv-cache-MPcS_mGG.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";
@@ -322,6 +322,7 @@ var FederationBuilderImpl = class {
322
322
  inboxListeners;
323
323
  inboxErrorHandler;
324
324
  sharedInboxKeyDispatcher;
325
+ unverifiedActivityHandler;
325
326
  outboxPermanentFailureHandler;
326
327
  idempotencyStrategy;
327
328
  collectionTypeIds;
@@ -338,7 +339,7 @@ var FederationBuilderImpl = class {
338
339
  this.collectionTypeIds = {};
339
340
  }
340
341
  async build(options) {
341
- const { FederationImpl: FederationImpl$1 } = await import("./middleware-C8PKuPrm.js");
342
+ const { FederationImpl: FederationImpl$1 } = await import("./middleware-DGUNDGCl.js");
342
343
  const f = new FederationImpl$1(options);
343
344
  const trailingSlashInsensitiveValue = f.router.trailingSlashInsensitive;
344
345
  f.router = this.router.clone();
@@ -360,6 +361,7 @@ var FederationBuilderImpl = class {
360
361
  f.inboxListeners = this.inboxListeners?.clone();
361
362
  f.inboxErrorHandler = this.inboxErrorHandler;
362
363
  f.sharedInboxKeyDispatcher = this.sharedInboxKeyDispatcher;
364
+ f.unverifiedActivityHandler = this.unverifiedActivityHandler;
363
365
  f.outboxPermanentFailureHandler = this.outboxPermanentFailureHandler;
364
366
  f.idempotencyStrategy = this.idempotencyStrategy;
365
367
  return f;
@@ -736,6 +738,10 @@ var FederationBuilderImpl = class {
736
738
  this.inboxErrorHandler = handler;
737
739
  return setters;
738
740
  },
741
+ onUnverifiedActivity: (handler) => {
742
+ this.unverifiedActivityHandler = handler;
743
+ return setters;
744
+ },
739
745
  setSharedKeyDispatcher: (dispatcher) => {
740
746
  this.sharedInboxKeyDispatcher = dispatcher;
741
747
  return setters;
@@ -869,17 +875,34 @@ var KvKeyCache = class {
869
875
  kv;
870
876
  prefix;
871
877
  options;
878
+ unavailableKeyTtl;
872
879
  nullKeys;
873
880
  constructor(kv, prefix, options = {}) {
874
881
  this.kv = kv;
875
882
  this.prefix = prefix;
876
- this.nullKeys = /* @__PURE__ */ new Set();
877
883
  this.options = options;
884
+ this.unavailableKeyTtl = options.unavailableKeyTtl ?? Temporal.Duration.from({ minutes: 10 });
885
+ this.nullKeys = /* @__PURE__ */ new Map();
886
+ }
887
+ #getFetchErrorKey(keyId) {
888
+ return [
889
+ ...this.prefix,
890
+ "__fetchError",
891
+ keyId.href
892
+ ];
878
893
  }
879
894
  async get(keyId) {
880
- if (this.nullKeys.has(keyId.href)) return null;
895
+ const negativeExpiration = this.nullKeys.get(keyId.href);
896
+ if (negativeExpiration != null) {
897
+ if (Temporal.Now.instant().until(negativeExpiration).sign >= 0) return null;
898
+ this.nullKeys.delete(keyId.href);
899
+ }
881
900
  const serialized = await this.kv.get([...this.prefix, keyId.href]);
882
- if (serialized == null) return void 0;
901
+ if (serialized === void 0) return void 0;
902
+ if (serialized === null) {
903
+ this.nullKeys.set(keyId.href, Temporal.Now.instant().add(this.unavailableKeyTtl));
904
+ return null;
905
+ }
883
906
  try {
884
907
  return await CryptographicKey.fromJsonLd(serialized, this.options);
885
908
  } catch {
@@ -893,14 +916,51 @@ var KvKeyCache = class {
893
916
  }
894
917
  async set(keyId, key) {
895
918
  if (key == null) {
896
- this.nullKeys.add(keyId.href);
897
- await this.kv.delete([...this.prefix, keyId.href]);
919
+ this.nullKeys.set(keyId.href, Temporal.Now.instant().add(this.unavailableKeyTtl));
920
+ await this.kv.set([...this.prefix, keyId.href], null, { ttl: this.unavailableKeyTtl });
898
921
  return;
899
922
  }
900
923
  this.nullKeys.delete(keyId.href);
901
924
  const serialized = await key.toJsonLd(this.options);
902
925
  await this.kv.set([...this.prefix, keyId.href], serialized);
903
926
  }
927
+ async getFetchError(keyId) {
928
+ const cached = await this.kv.get(this.#getFetchErrorKey(keyId));
929
+ if (cached == null || typeof cached !== "object") return void 0;
930
+ if ("status" in cached && typeof cached.status === "number" && "statusText" in cached && typeof cached.statusText === "string" && "headers" in cached && Array.isArray(cached.headers) && "body" in cached && typeof cached.body === "string") return {
931
+ status: cached.status,
932
+ response: new Response(cached.body, {
933
+ status: cached.status,
934
+ statusText: cached.statusText,
935
+ headers: cached.headers
936
+ })
937
+ };
938
+ else if ("errorName" in cached && typeof cached.errorName === "string" && "errorMessage" in cached && typeof cached.errorMessage === "string") {
939
+ const error = new Error(cached.errorMessage);
940
+ error.name = cached.errorName;
941
+ return { error };
942
+ }
943
+ return void 0;
944
+ }
945
+ async setFetchError(keyId, error) {
946
+ if (error == null) {
947
+ await this.kv.delete(this.#getFetchErrorKey(keyId));
948
+ return;
949
+ }
950
+ if ("status" in error) {
951
+ await this.kv.set(this.#getFetchErrorKey(keyId), {
952
+ status: error.status,
953
+ statusText: error.response.statusText,
954
+ headers: Array.from(error.response.headers.entries()),
955
+ body: await error.response.clone().text()
956
+ }, { ttl: this.unavailableKeyTtl });
957
+ return;
958
+ }
959
+ await this.kv.set(this.#getFetchErrorKey(keyId), {
960
+ errorName: error.error.name,
961
+ errorMessage: error.error.message
962
+ }, { ttl: this.unavailableKeyTtl });
963
+ }
904
964
  };
905
965
 
906
966
  //#endregion
@@ -1221,7 +1281,7 @@ async function handleInbox(request, options) {
1221
1281
  * @returns A promise that resolves to an HTTP response.
1222
1282
  */
1223
1283
  async function handleInboxInternal(request, parameters, span) {
1224
- const { recipient, context: ctx, inboxContextFactory, kv, kvPrefixes, queue, actorDispatcher, inboxListeners, inboxErrorHandler, onNotFound, signatureTimeWindow, skipSignatureVerification, tracerProvider } = parameters;
1284
+ const { recipient, context: ctx, inboxContextFactory, kv, kvPrefixes, queue, actorDispatcher, inboxListeners, inboxErrorHandler, unverifiedActivityHandler, onNotFound, signatureTimeWindow, skipSignatureVerification, tracerProvider } = parameters;
1225
1285
  const logger$1 = getLogger([
1226
1286
  "fedify",
1227
1287
  "federation",
@@ -1316,12 +1376,14 @@ async function handleInboxInternal(request, parameters, span) {
1316
1376
  }
1317
1377
  const jsonWithoutSig = detachSignature(json);
1318
1378
  let activity = null;
1379
+ let activityVerified = false;
1319
1380
  if (ldSigVerified) {
1320
1381
  logger$1.debug("Linked Data Signatures are verified.", {
1321
1382
  recipient,
1322
1383
  json
1323
1384
  });
1324
1385
  activity = await Activity.fromJsonLd(jsonWithoutSig, ctx);
1386
+ activityVerified = true;
1325
1387
  } else {
1326
1388
  logger$1.debug("Linked Data Signatures are not verified.", {
1327
1389
  recipient,
@@ -1362,34 +1424,107 @@ async function handleInboxInternal(request, parameters, span) {
1362
1424
  recipient,
1363
1425
  activity: json
1364
1426
  });
1365
- else logger$1.debug("Object Integrity Proofs are verified.", {
1366
- recipient,
1367
- activity: json
1368
- });
1427
+ else {
1428
+ logger$1.debug("Object Integrity Proofs are verified.", {
1429
+ recipient,
1430
+ activity: json
1431
+ });
1432
+ activityVerified = true;
1433
+ }
1369
1434
  }
1370
1435
  let httpSigKey = null;
1371
1436
  if (activity == null) {
1372
1437
  if (!skipSignatureVerification) {
1373
- const key = await verifyRequest(request, {
1438
+ const verification = await verifyRequestDetailed(request, {
1374
1439
  contextLoader: ctx.contextLoader,
1375
1440
  documentLoader: ctx.documentLoader,
1376
1441
  timeWindow: signatureTimeWindow,
1377
1442
  keyCache,
1378
1443
  tracerProvider
1379
1444
  });
1380
- if (key == null) {
1381
- logger$1.error("Failed to verify the request's HTTP Signatures.", { recipient });
1445
+ if (verification.verified === false) {
1446
+ const reason = verification.reason;
1447
+ logger$1.error("Failed to verify the request's HTTP Signatures.", {
1448
+ recipient,
1449
+ reason: reason.type,
1450
+ keyId: "keyId" in reason ? reason.keyId?.href : void 0
1451
+ });
1382
1452
  span.setStatus({
1383
1453
  code: SpanStatusCode.ERROR,
1384
1454
  message: `Failed to verify the request's HTTP Signatures.`
1385
1455
  });
1386
- const response = new Response("Failed to verify the request signature.", {
1456
+ if (unverifiedActivityHandler == null) return new Response("Failed to verify the request signature.", {
1387
1457
  status: 401,
1388
1458
  headers: { "Content-Type": "text/plain; charset=utf-8" }
1389
1459
  });
1390
- return response;
1391
- } else logger$1.debug("HTTP Signatures are verified.", { recipient });
1392
- httpSigKey = key;
1460
+ try {
1461
+ activity = await Activity.fromJsonLd(jsonWithoutSig, ctx);
1462
+ } catch (error) {
1463
+ logger$1.error("Failed to parse activity:\n{error}", {
1464
+ recipient,
1465
+ activity: json,
1466
+ error
1467
+ });
1468
+ try {
1469
+ await inboxErrorHandler?.(ctx, error);
1470
+ } catch (error$1) {
1471
+ logger$1.error("An unexpected error occurred in inbox error handler:\n{error}", {
1472
+ error: error$1,
1473
+ activity: json,
1474
+ recipient
1475
+ });
1476
+ }
1477
+ return new Response("Invalid activity.", {
1478
+ status: 400,
1479
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
1480
+ });
1481
+ }
1482
+ if (activity.id != null) span.setAttribute("activitypub.activity.id", activity.id.href);
1483
+ span.setAttribute("activitypub.activity.type", getTypeId(activity).href);
1484
+ const eventAttributes = {
1485
+ "activitypub.activity.json": JSON.stringify(json),
1486
+ "activitypub.activity.verified": false,
1487
+ "ld_signatures.verified": ldSigVerified,
1488
+ "http_signatures.verified": false,
1489
+ "http_signatures.key_id": "keyId" in reason ? reason.keyId?.href ?? "" : "",
1490
+ "http_signatures.failure_reason": reason.type
1491
+ };
1492
+ if (reason.type === "keyFetchError") if ("status" in reason.result) eventAttributes["http_signatures.key_fetch_status"] = reason.result.status;
1493
+ else eventAttributes["http_signatures.key_fetch_error"] = reason.result.error.name || reason.result.error.constructor.name || "Error";
1494
+ span.addEvent("activitypub.activity.received", eventAttributes);
1495
+ let response;
1496
+ try {
1497
+ response = await unverifiedActivityHandler(ctx, activity, reason);
1498
+ } catch (error) {
1499
+ logger$1.error("An unexpected error occurred in unverified activity handler:\n{error}", {
1500
+ error,
1501
+ activity: json,
1502
+ recipient
1503
+ });
1504
+ try {
1505
+ await inboxErrorHandler?.(ctx, error);
1506
+ } catch (error$1) {
1507
+ logger$1.error("An unexpected error occurred in inbox error handler:\n{error}", {
1508
+ error: error$1,
1509
+ activity: json,
1510
+ recipient
1511
+ });
1512
+ }
1513
+ return new Response("Failed to verify the request signature.", {
1514
+ status: 401,
1515
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
1516
+ });
1517
+ }
1518
+ 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
+ });
1523
+ } else {
1524
+ logger$1.debug("HTTP Signatures are verified.", { recipient });
1525
+ activityVerified = true;
1526
+ }
1527
+ httpSigKey = verification.key;
1393
1528
  }
1394
1529
  activity = await Activity.fromJsonLd(jsonWithoutSig, ctx);
1395
1530
  }
@@ -1397,7 +1532,7 @@ async function handleInboxInternal(request, parameters, span) {
1397
1532
  span.setAttribute("activitypub.activity.type", getTypeId(activity).href);
1398
1533
  span.addEvent("activitypub.activity.received", {
1399
1534
  "activitypub.activity.json": JSON.stringify(json),
1400
- "activitypub.activity.verified": activity != null,
1535
+ "activitypub.activity.verified": activityVerified,
1401
1536
  "ld_signatures.verified": ldSigVerified,
1402
1537
  "http_signatures.verified": httpSigKey != null,
1403
1538
  "http_signatures.key_id": httpSigKey?.id?.href ?? ""
@@ -3122,6 +3257,7 @@ var FederationImpl = class extends FederationBuilderImpl {
3122
3257
  actorDispatcher: this.actorCallbacks?.dispatcher,
3123
3258
  inboxListeners: this.inboxListeners,
3124
3259
  inboxErrorHandler: this.inboxErrorHandler,
3260
+ unverifiedActivityHandler: this.unverifiedActivityHandler,
3125
3261
  onNotFound,
3126
3262
  signatureTimeWindow: this.signatureTimeWindow,
3127
3263
  skipSignatureVerification: this.skipSignatureVerification,
@@ -0,0 +1,12 @@
1
+
2
+ const { Temporal } = require("@js-temporal/polyfill");
3
+ const { URLPattern } = require("urlpattern-polyfill");
4
+
5
+ require('./transformers-3g8GZwkZ.cjs');
6
+ require('./http-D6a6mMc0.cjs');
7
+ const require_middleware = require('./middleware-BoCzk7-G.cjs');
8
+ require('./proof-qHcNgE5i.cjs');
9
+ require('./types-Cd_hszr_.cjs');
10
+ require('./kv-cache-DPtsJ1sL.cjs');
11
+
12
+ exports.FederationImpl = require_middleware.FederationImpl;
@@ -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-DJT6NciB.cjs');
8
- const require_proof = require('./proof-CR5RUAmy.cjs');
7
+ const require_http = require('./http-D6a6mMc0.cjs');
8
+ const require_proof = require('./proof-qHcNgE5i.cjs');
9
9
  const require_types = require('./types-Cd_hszr_.cjs');
10
- const require_kv_cache = require('./kv-cache-Vtxhbo1W.cjs');
10
+ const require_kv_cache = require('./kv-cache-DPtsJ1sL.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"));
@@ -323,6 +323,7 @@ var FederationBuilderImpl = class {
323
323
  inboxListeners;
324
324
  inboxErrorHandler;
325
325
  sharedInboxKeyDispatcher;
326
+ unverifiedActivityHandler;
326
327
  outboxPermanentFailureHandler;
327
328
  idempotencyStrategy;
328
329
  collectionTypeIds;
@@ -339,7 +340,7 @@ var FederationBuilderImpl = class {
339
340
  this.collectionTypeIds = {};
340
341
  }
341
342
  async build(options) {
342
- const { FederationImpl: FederationImpl$1 } = await Promise.resolve().then(() => require("./middleware-D4XcpSBG.cjs"));
343
+ const { FederationImpl: FederationImpl$1 } = await Promise.resolve().then(() => require("./middleware-BgCIhb_C.cjs"));
343
344
  const f = new FederationImpl$1(options);
344
345
  const trailingSlashInsensitiveValue = f.router.trailingSlashInsensitive;
345
346
  f.router = this.router.clone();
@@ -361,6 +362,7 @@ var FederationBuilderImpl = class {
361
362
  f.inboxListeners = this.inboxListeners?.clone();
362
363
  f.inboxErrorHandler = this.inboxErrorHandler;
363
364
  f.sharedInboxKeyDispatcher = this.sharedInboxKeyDispatcher;
365
+ f.unverifiedActivityHandler = this.unverifiedActivityHandler;
364
366
  f.outboxPermanentFailureHandler = this.outboxPermanentFailureHandler;
365
367
  f.idempotencyStrategy = this.idempotencyStrategy;
366
368
  return f;
@@ -737,6 +739,10 @@ var FederationBuilderImpl = class {
737
739
  this.inboxErrorHandler = handler;
738
740
  return setters;
739
741
  },
742
+ onUnverifiedActivity: (handler) => {
743
+ this.unverifiedActivityHandler = handler;
744
+ return setters;
745
+ },
740
746
  setSharedKeyDispatcher: (dispatcher) => {
741
747
  this.sharedInboxKeyDispatcher = dispatcher;
742
748
  return setters;
@@ -870,17 +876,34 @@ var KvKeyCache = class {
870
876
  kv;
871
877
  prefix;
872
878
  options;
879
+ unavailableKeyTtl;
873
880
  nullKeys;
874
881
  constructor(kv, prefix, options = {}) {
875
882
  this.kv = kv;
876
883
  this.prefix = prefix;
877
- this.nullKeys = /* @__PURE__ */ new Set();
878
884
  this.options = options;
885
+ this.unavailableKeyTtl = options.unavailableKeyTtl ?? Temporal.Duration.from({ minutes: 10 });
886
+ this.nullKeys = /* @__PURE__ */ new Map();
887
+ }
888
+ #getFetchErrorKey(keyId) {
889
+ return [
890
+ ...this.prefix,
891
+ "__fetchError",
892
+ keyId.href
893
+ ];
879
894
  }
880
895
  async get(keyId) {
881
- if (this.nullKeys.has(keyId.href)) return null;
896
+ const negativeExpiration = this.nullKeys.get(keyId.href);
897
+ if (negativeExpiration != null) {
898
+ if (Temporal.Now.instant().until(negativeExpiration).sign >= 0) return null;
899
+ this.nullKeys.delete(keyId.href);
900
+ }
882
901
  const serialized = await this.kv.get([...this.prefix, keyId.href]);
883
- if (serialized == null) return void 0;
902
+ if (serialized === void 0) return void 0;
903
+ if (serialized === null) {
904
+ this.nullKeys.set(keyId.href, Temporal.Now.instant().add(this.unavailableKeyTtl));
905
+ return null;
906
+ }
884
907
  try {
885
908
  return await __fedify_vocab.CryptographicKey.fromJsonLd(serialized, this.options);
886
909
  } catch {
@@ -894,14 +917,51 @@ var KvKeyCache = class {
894
917
  }
895
918
  async set(keyId, key) {
896
919
  if (key == null) {
897
- this.nullKeys.add(keyId.href);
898
- await this.kv.delete([...this.prefix, keyId.href]);
920
+ this.nullKeys.set(keyId.href, Temporal.Now.instant().add(this.unavailableKeyTtl));
921
+ await this.kv.set([...this.prefix, keyId.href], null, { ttl: this.unavailableKeyTtl });
899
922
  return;
900
923
  }
901
924
  this.nullKeys.delete(keyId.href);
902
925
  const serialized = await key.toJsonLd(this.options);
903
926
  await this.kv.set([...this.prefix, keyId.href], serialized);
904
927
  }
928
+ async getFetchError(keyId) {
929
+ const cached = await this.kv.get(this.#getFetchErrorKey(keyId));
930
+ if (cached == null || typeof cached !== "object") return void 0;
931
+ if ("status" in cached && typeof cached.status === "number" && "statusText" in cached && typeof cached.statusText === "string" && "headers" in cached && Array.isArray(cached.headers) && "body" in cached && typeof cached.body === "string") return {
932
+ status: cached.status,
933
+ response: new Response(cached.body, {
934
+ status: cached.status,
935
+ statusText: cached.statusText,
936
+ headers: cached.headers
937
+ })
938
+ };
939
+ else if ("errorName" in cached && typeof cached.errorName === "string" && "errorMessage" in cached && typeof cached.errorMessage === "string") {
940
+ const error = new Error(cached.errorMessage);
941
+ error.name = cached.errorName;
942
+ return { error };
943
+ }
944
+ return void 0;
945
+ }
946
+ async setFetchError(keyId, error) {
947
+ if (error == null) {
948
+ await this.kv.delete(this.#getFetchErrorKey(keyId));
949
+ return;
950
+ }
951
+ if ("status" in error) {
952
+ await this.kv.set(this.#getFetchErrorKey(keyId), {
953
+ status: error.status,
954
+ statusText: error.response.statusText,
955
+ headers: Array.from(error.response.headers.entries()),
956
+ body: await error.response.clone().text()
957
+ }, { ttl: this.unavailableKeyTtl });
958
+ return;
959
+ }
960
+ await this.kv.set(this.#getFetchErrorKey(keyId), {
961
+ errorName: error.error.name,
962
+ errorMessage: error.error.message
963
+ }, { ttl: this.unavailableKeyTtl });
964
+ }
905
965
  };
906
966
 
907
967
  //#endregion
@@ -1222,7 +1282,7 @@ async function handleInbox(request, options) {
1222
1282
  * @returns A promise that resolves to an HTTP response.
1223
1283
  */
1224
1284
  async function handleInboxInternal(request, parameters, span) {
1225
- const { recipient, context: ctx, inboxContextFactory, kv, kvPrefixes, queue, actorDispatcher, inboxListeners, inboxErrorHandler, onNotFound, signatureTimeWindow, skipSignatureVerification, tracerProvider } = parameters;
1285
+ const { recipient, context: ctx, inboxContextFactory, kv, kvPrefixes, queue, actorDispatcher, inboxListeners, inboxErrorHandler, unverifiedActivityHandler, onNotFound, signatureTimeWindow, skipSignatureVerification, tracerProvider } = parameters;
1226
1286
  const logger$1 = (0, __logtape_logtape.getLogger)([
1227
1287
  "fedify",
1228
1288
  "federation",
@@ -1317,12 +1377,14 @@ async function handleInboxInternal(request, parameters, span) {
1317
1377
  }
1318
1378
  const jsonWithoutSig = require_proof.detachSignature(json);
1319
1379
  let activity = null;
1380
+ let activityVerified = false;
1320
1381
  if (ldSigVerified) {
1321
1382
  logger$1.debug("Linked Data Signatures are verified.", {
1322
1383
  recipient,
1323
1384
  json
1324
1385
  });
1325
1386
  activity = await __fedify_vocab.Activity.fromJsonLd(jsonWithoutSig, ctx);
1387
+ activityVerified = true;
1326
1388
  } else {
1327
1389
  logger$1.debug("Linked Data Signatures are not verified.", {
1328
1390
  recipient,
@@ -1363,34 +1425,107 @@ async function handleInboxInternal(request, parameters, span) {
1363
1425
  recipient,
1364
1426
  activity: json
1365
1427
  });
1366
- else logger$1.debug("Object Integrity Proofs are verified.", {
1367
- recipient,
1368
- activity: json
1369
- });
1428
+ else {
1429
+ logger$1.debug("Object Integrity Proofs are verified.", {
1430
+ recipient,
1431
+ activity: json
1432
+ });
1433
+ activityVerified = true;
1434
+ }
1370
1435
  }
1371
1436
  let httpSigKey = null;
1372
1437
  if (activity == null) {
1373
1438
  if (!skipSignatureVerification) {
1374
- const key = await require_http.verifyRequest(request, {
1439
+ const verification = await require_http.verifyRequestDetailed(request, {
1375
1440
  contextLoader: ctx.contextLoader,
1376
1441
  documentLoader: ctx.documentLoader,
1377
1442
  timeWindow: signatureTimeWindow,
1378
1443
  keyCache,
1379
1444
  tracerProvider
1380
1445
  });
1381
- if (key == null) {
1382
- logger$1.error("Failed to verify the request's HTTP Signatures.", { recipient });
1446
+ if (verification.verified === false) {
1447
+ const reason = verification.reason;
1448
+ logger$1.error("Failed to verify the request's HTTP Signatures.", {
1449
+ recipient,
1450
+ reason: reason.type,
1451
+ keyId: "keyId" in reason ? reason.keyId?.href : void 0
1452
+ });
1383
1453
  span.setStatus({
1384
1454
  code: __opentelemetry_api.SpanStatusCode.ERROR,
1385
1455
  message: `Failed to verify the request's HTTP Signatures.`
1386
1456
  });
1387
- const response = new Response("Failed to verify the request signature.", {
1457
+ if (unverifiedActivityHandler == null) return new Response("Failed to verify the request signature.", {
1388
1458
  status: 401,
1389
1459
  headers: { "Content-Type": "text/plain; charset=utf-8" }
1390
1460
  });
1391
- return response;
1392
- } else logger$1.debug("HTTP Signatures are verified.", { recipient });
1393
- httpSigKey = key;
1461
+ try {
1462
+ activity = await __fedify_vocab.Activity.fromJsonLd(jsonWithoutSig, ctx);
1463
+ } catch (error) {
1464
+ logger$1.error("Failed to parse activity:\n{error}", {
1465
+ recipient,
1466
+ activity: json,
1467
+ error
1468
+ });
1469
+ try {
1470
+ await inboxErrorHandler?.(ctx, error);
1471
+ } catch (error$1) {
1472
+ logger$1.error("An unexpected error occurred in inbox error handler:\n{error}", {
1473
+ error: error$1,
1474
+ activity: json,
1475
+ recipient
1476
+ });
1477
+ }
1478
+ return new Response("Invalid activity.", {
1479
+ status: 400,
1480
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
1481
+ });
1482
+ }
1483
+ if (activity.id != null) span.setAttribute("activitypub.activity.id", activity.id.href);
1484
+ span.setAttribute("activitypub.activity.type", (0, __fedify_vocab.getTypeId)(activity).href);
1485
+ const eventAttributes = {
1486
+ "activitypub.activity.json": JSON.stringify(json),
1487
+ "activitypub.activity.verified": false,
1488
+ "ld_signatures.verified": ldSigVerified,
1489
+ "http_signatures.verified": false,
1490
+ "http_signatures.key_id": "keyId" in reason ? reason.keyId?.href ?? "" : "",
1491
+ "http_signatures.failure_reason": reason.type
1492
+ };
1493
+ if (reason.type === "keyFetchError") if ("status" in reason.result) eventAttributes["http_signatures.key_fetch_status"] = reason.result.status;
1494
+ else eventAttributes["http_signatures.key_fetch_error"] = reason.result.error.name || reason.result.error.constructor.name || "Error";
1495
+ span.addEvent("activitypub.activity.received", eventAttributes);
1496
+ let response;
1497
+ try {
1498
+ response = await unverifiedActivityHandler(ctx, activity, reason);
1499
+ } catch (error) {
1500
+ logger$1.error("An unexpected error occurred in unverified activity handler:\n{error}", {
1501
+ error,
1502
+ activity: json,
1503
+ recipient
1504
+ });
1505
+ try {
1506
+ await inboxErrorHandler?.(ctx, error);
1507
+ } catch (error$1) {
1508
+ logger$1.error("An unexpected error occurred in inbox error handler:\n{error}", {
1509
+ error: error$1,
1510
+ activity: json,
1511
+ recipient
1512
+ });
1513
+ }
1514
+ return new Response("Failed to verify the request signature.", {
1515
+ status: 401,
1516
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
1517
+ });
1518
+ }
1519
+ 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
+ });
1524
+ } else {
1525
+ logger$1.debug("HTTP Signatures are verified.", { recipient });
1526
+ activityVerified = true;
1527
+ }
1528
+ httpSigKey = verification.key;
1394
1529
  }
1395
1530
  activity = await __fedify_vocab.Activity.fromJsonLd(jsonWithoutSig, ctx);
1396
1531
  }
@@ -1398,7 +1533,7 @@ async function handleInboxInternal(request, parameters, span) {
1398
1533
  span.setAttribute("activitypub.activity.type", (0, __fedify_vocab.getTypeId)(activity).href);
1399
1534
  span.addEvent("activitypub.activity.received", {
1400
1535
  "activitypub.activity.json": JSON.stringify(json),
1401
- "activitypub.activity.verified": activity != null,
1536
+ "activitypub.activity.verified": activityVerified,
1402
1537
  "ld_signatures.verified": ldSigVerified,
1403
1538
  "http_signatures.verified": httpSigKey != null,
1404
1539
  "http_signatures.key_id": httpSigKey?.id?.href ?? ""
@@ -3123,6 +3258,7 @@ var FederationImpl = class extends FederationBuilderImpl {
3123
3258
  actorDispatcher: this.actorCallbacks?.dispatcher,
3124
3259
  inboxListeners: this.inboxListeners,
3125
3260
  inboxErrorHandler: this.inboxErrorHandler,
3261
+ unverifiedActivityHandler: this.unverifiedActivityHandler,
3126
3262
  onNotFound,
3127
3263
  signatureTimeWindow: this.signatureTimeWindow,
3128
3264
  skipSignatureVerification: this.skipSignatureVerification,
@@ -3,10 +3,10 @@
3
3
  import { URLPattern } from "urlpattern-polyfill";
4
4
 
5
5
  import "./transformers-C3FLHUd6.js";
6
- import "./http-CSX1-Mgi.js";
7
- import { ContextImpl, FederationImpl, InboxContextImpl, KvSpecDeterminer, createFederation } from "./middleware-MlO5iUeZ.js";
8
- import "./proof-BgUVmaJz.js";
6
+ import "./http-DJmytoC2.js";
7
+ import { ContextImpl, FederationImpl, InboxContextImpl, KvSpecDeterminer, createFederation } from "./middleware-Ajnk9qHB.js";
8
+ import "./proof-Le4DAkqb.js";
9
9
  import "./types-C93Ob9cU.js";
10
- import "./kv-cache-CQPL_aGY.js";
10
+ import "./kv-cache-MPcS_mGG.js";
11
11
 
12
12
  export { FederationImpl };