@fedify/fedify 1.9.9 → 1.9.11

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 (118) hide show
  1. package/dist/{actor-DILKT_HU.cjs → actor-BaIbAIQ4.cjs} +187 -187
  2. package/dist/{actor-DVET8NhO.mjs → actor-BgNoo4Eo.mjs} +1 -1
  3. package/dist/{actor-MDInzRdc.js → actor-I6MTQt65.js} +187 -187
  4. package/dist/{authdocloader-hwjRHw8X.cjs → authdocloader-BML29LEF.cjs} +3 -3
  5. package/dist/{authdocloader-DLvikTbR.mjs → authdocloader-CQRL6xBg.mjs} +3 -3
  6. package/dist/{authdocloader-DRG9qnz7.js → authdocloader-DGVNFUsJ.js} +3 -3
  7. package/dist/{builder-DfInnU0K.mjs → builder-B8VNNUzm.mjs} +4 -4
  8. package/dist/{client-DuRbuCIQ.mjs → client-Cjo9kYUV.mjs} +1 -1
  9. package/dist/compat/mod.cjs +1 -1
  10. package/dist/compat/mod.d.cts +1 -1
  11. package/dist/compat/mod.d.ts +1 -1
  12. package/dist/compat/transformers.test.mjs +3 -3
  13. package/dist/{context-Cf8s07xm.d.ts → context-CNkTuNIm.d.ts} +48 -6
  14. package/dist/{context-Crmwowsw.d.cts → context-jX79cwCB.d.cts} +48 -6
  15. package/dist/{docloader-BZfjD0Xt.cjs → docloader-CB5WT9yk.cjs} +12 -3
  16. package/dist/{docloader-ivt5xNwJ.js → docloader-XzYOPh6W.js} +12 -3
  17. package/dist/{esm-D0urVWqo.mjs → esm-Br6fmrGY.mjs} +1 -1
  18. package/dist/federation/builder.test.mjs +3 -3
  19. package/dist/federation/collection.test.mjs +1 -1
  20. package/dist/federation/handler.test.mjs +1361 -45
  21. package/dist/federation/idempotency.test.mjs +5 -5
  22. package/dist/federation/inbox.test.mjs +3 -3
  23. package/dist/federation/keycache.test.mjs +3 -3
  24. package/dist/federation/kv.test.mjs +1 -1
  25. package/dist/federation/middleware.test.mjs +1586 -82
  26. package/dist/federation/mod.cjs +2 -2
  27. package/dist/federation/mod.d.cts +1 -1
  28. package/dist/federation/mod.d.ts +1 -1
  29. package/dist/federation/mod.js +1 -1
  30. package/dist/federation/mq.test.mjs +3 -3
  31. package/dist/federation/retry.test.mjs +2 -2
  32. package/dist/federation/router.test.mjs +2 -2
  33. package/dist/federation/send.test.mjs +7 -7
  34. package/dist/federation/temporal.test.d.mts +2 -0
  35. package/dist/federation/temporal.test.mjs +71 -0
  36. package/dist/{http-Dbj7ZT3b.js → http-CXpt61mo.js} +3 -3
  37. package/dist/{http-CsSHezSz.mjs → http-GSUEBn2H.mjs} +2 -2
  38. package/dist/{http-8jvVkJos.cjs → http-L4148RPe.cjs} +3 -3
  39. package/dist/{inbox-D7U8Ysvc.mjs → inbox-DRfIo1Ox.mjs} +7 -4
  40. package/dist/{key-Bbuzt358.mjs → key-73aMUDcO.mjs} +2 -2
  41. package/dist/{key-Bg3Rr_bi.mjs → key-CqxBq3-S.mjs} +1 -1
  42. package/dist/{key-CUorXS6k.cjs → key-D3E68McA.cjs} +2 -2
  43. package/dist/{key-C2e_Yz1s.js → key-DtaAm2V1.js} +2 -2
  44. package/dist/{key-Bl8yte5W.cjs → key-Yfx6HgfZ.cjs} +1 -1
  45. package/dist/{keycache-Dpm1JlAw.mjs → keycache-tpX1dxwp.mjs} +1 -1
  46. package/dist/{keys-CZlvPl27.mjs → keys-B9pkL0lY.mjs} +1 -1
  47. package/dist/ld-DqEigU4F.mjs +548 -0
  48. package/dist/{lookup-BnvWOvmD.cjs → lookup-BK6z2RO5.cjs} +1 -1
  49. package/dist/{lookup-DhIULso8.mjs → lookup-DA92hMtK.mjs} +1 -1
  50. package/dist/{lookup-BOFQeNi_.js → lookup-DDOc-i0I.js} +1 -1
  51. package/dist/{middleware-BD8iV5GL.cjs → middleware-CcRfmHUw.cjs} +404 -78
  52. package/dist/{middleware-D0zludVl.js → middleware-D72Tt9sq.js} +401 -76
  53. package/dist/{middleware-BBjNkISH.mjs → middleware-DNRnMI46.mjs} +313 -80
  54. package/dist/{middleware-TO6qMFz0.cjs → middleware-DfCM9WFN.cjs} +1 -1
  55. package/dist/{middleware-DauQ1R8p.mjs → middleware-ufTj15Ba.mjs} +1 -1
  56. package/dist/mod.cjs +10 -10
  57. package/dist/mod.d.cts +1 -1
  58. package/dist/mod.d.ts +1 -1
  59. package/dist/mod.js +9 -9
  60. package/dist/nodeinfo/client.test.mjs +3 -3
  61. package/dist/nodeinfo/handler.test.mjs +2 -2
  62. package/dist/nodeinfo/mod.cjs +1 -1
  63. package/dist/nodeinfo/mod.js +1 -1
  64. package/dist/nodeinfo/semver.test.mjs +1 -1
  65. package/dist/nodeinfo/types.test.mjs +1 -1
  66. package/dist/{owner-CAvCysNj.mjs → owner-C7rjJog4.mjs} +3 -3
  67. package/dist/{proof-2V3jxAdE.cjs → proof-BZb-g5fh.cjs} +347 -5
  68. package/dist/{proof-C99DO0SE.js → proof-CjgdsMq_.js} +300 -6
  69. package/dist/{proof-DkUAJViu.mjs → proof-GWFCtXCY.mjs} +2 -2
  70. package/dist/runtime/authdocloader.test.mjs +6 -6
  71. package/dist/runtime/docloader.test.mjs +3 -3
  72. package/dist/runtime/key.test.mjs +3 -3
  73. package/dist/runtime/langstr.test.mjs +2 -2
  74. package/dist/runtime/link.test.mjs +2 -2
  75. package/dist/runtime/mod.cjs +3 -3
  76. package/dist/runtime/mod.js +3 -3
  77. package/dist/runtime/multibase/multibase.test.mjs +2 -2
  78. package/dist/runtime/url.test.mjs +5 -2
  79. package/dist/{send-BxjL4pnG.mjs → send-B1cXA43r.mjs} +2 -2
  80. package/dist/sig/http.test.mjs +6 -6
  81. package/dist/sig/key.test.mjs +4 -4
  82. package/dist/sig/ld.test.mjs +561 -5
  83. package/dist/sig/mod.cjs +3 -3
  84. package/dist/sig/mod.js +3 -3
  85. package/dist/sig/owner.test.mjs +5 -5
  86. package/dist/sig/proof.test.mjs +4 -4
  87. package/dist/temporal-DE-KwUkP.mjs +95 -0
  88. package/dist/testing/docloader.test.mjs +1 -1
  89. package/dist/testing/mod.d.mts +48 -6
  90. package/dist/testing/mod.mjs +1 -1
  91. package/dist/{testing-eYbMkj5P.mjs → testing-Brx3i-JJ.mjs} +2 -2
  92. package/dist/{transformers-DW7EyHh4.cjs → transformers-v2GoHgHn.cjs} +1 -1
  93. package/dist/{types-dHLDn_7H.js → types-CRRb96yX.js} +1 -1
  94. package/dist/{types-B0U2QC2Q.cjs → types-Ck12MwLI.cjs} +1 -1
  95. package/dist/vocab/actor.test.mjs +4 -4
  96. package/dist/vocab/cjs.test.mjs +1 -1
  97. package/dist/vocab/lookup.test.mjs +4 -4
  98. package/dist/vocab/mod.cjs +3 -3
  99. package/dist/vocab/mod.js +3 -3
  100. package/dist/vocab/type.test.mjs +2 -2
  101. package/dist/vocab/vocab.test.mjs +3 -3
  102. package/dist/{vocab-CG-IqKPs.mjs → vocab-D9ewcFHJ.mjs} +197 -188
  103. package/dist/webfinger/handler.test.mjs +3 -3
  104. package/dist/webfinger/lookup.test.mjs +3 -3
  105. package/dist/webfinger/mod.cjs +1 -1
  106. package/dist/webfinger/mod.js +1 -1
  107. package/dist/x/cfworkers.cjs +1 -1
  108. package/dist/x/cfworkers.test.mjs +1 -1
  109. package/dist/x/hono.cjs +1 -1
  110. package/dist/x/hono.d.cts +1 -1
  111. package/dist/x/hono.d.ts +1 -1
  112. package/dist/x/sveltekit.cjs +1 -1
  113. package/dist/x/sveltekit.d.cts +1 -1
  114. package/dist/x/sveltekit.d.ts +1 -1
  115. package/package.json +1 -1
  116. package/dist/ld-iU-MUtGB.mjs +0 -254
  117. /package/dist/{denokv-CoSB_Eps.mjs → denokv-NJPMXyK0.mjs} +0 -0
  118. /package/dist/{retry-Ddbq3AcK.mjs → retry-B_E3V_Dx.mjs} +0 -0
@@ -1,17 +1,19 @@
1
1
  const { Temporal } = require("@js-temporal/polyfill");
2
2
  const { URLPattern } = require("urlpattern-polyfill");
3
- const require_docloader = require("./docloader-BZfjD0Xt.cjs");
4
- const require_transformers = require("./transformers-DW7EyHh4.cjs");
5
- const require_actor = require("./actor-DILKT_HU.cjs");
6
- const require_lookup = require("./lookup-BnvWOvmD.cjs");
7
- const require_key = require("./key-CUorXS6k.cjs");
8
- const require_http = require("./http-8jvVkJos.cjs");
9
- const require_proof = require("./proof-2V3jxAdE.cjs");
10
- const require_types = require("./types-B0U2QC2Q.cjs");
11
- const require_authdocloader = require("./authdocloader-hwjRHw8X.cjs");
3
+ const require_docloader = require("./docloader-CB5WT9yk.cjs");
4
+ const require_transformers = require("./transformers-v2GoHgHn.cjs");
5
+ const require_actor = require("./actor-BaIbAIQ4.cjs");
6
+ const require_lookup = require("./lookup-BK6z2RO5.cjs");
7
+ const require_key = require("./key-D3E68McA.cjs");
8
+ const require_http = require("./http-L4148RPe.cjs");
9
+ const require_proof = require("./proof-BZb-g5fh.cjs");
10
+ const require_types = require("./types-Ck12MwLI.cjs");
11
+ const require_authdocloader = require("./authdocloader-BML29LEF.cjs");
12
12
  const require_vocab_mod = require("./vocab/mod.cjs");
13
13
  let _logtape_logtape = require("@logtape/logtape");
14
14
  let _opentelemetry_api = require("@opentelemetry/api");
15
+ let jsonld = require("jsonld");
16
+ jsonld = require_docloader.__toESM(jsonld);
15
17
  let byte_encodings_hex = require("byte-encodings/hex");
16
18
  let es_toolkit = require("es-toolkit");
17
19
  let uri_template_router = require("uri-template-router");
@@ -53,7 +55,7 @@ var InboxListenerSet = class InboxListenerSet {
53
55
  }
54
56
  };
55
57
  let warnedAboutDefaultIdempotency = false;
56
- async function routeActivity({ context: ctx, json, activity, recipient, inboxListeners, inboxContextFactory, inboxErrorHandler, kv, kvPrefixes, queue, span, tracerProvider, idempotencyStrategy }) {
58
+ async function routeActivity({ context: ctx, json, originalJson, normalizedActivity, ldSignatureVerified, activity, recipient, inboxListeners, inboxContextFactory, listenerInboxContextFactory, inboxErrorHandler, kv, kvPrefixes, queue, span, tracerProvider, idempotencyStrategy }) {
57
59
  const logger = (0, _logtape_logtape.getLogger)([
58
60
  "fedify",
59
61
  "federation",
@@ -114,7 +116,9 @@ async function routeActivity({ context: ctx, json, activity, recipient, inboxLis
114
116
  type: "inbox",
115
117
  id: crypto.randomUUID(),
116
118
  baseUrl: ctx.origin,
117
- activity: json,
119
+ activity: originalJson ?? json,
120
+ ...normalizedActivity == null ? {} : { normalizedActivity },
121
+ ...ldSignatureVerified == null ? {} : { ldSignatureVerified },
118
122
  identifier: recipient,
119
123
  attempt: 0,
120
124
  started: (/* @__PURE__ */ new Date()).toISOString(),
@@ -158,7 +162,8 @@ async function routeActivity({ context: ctx, json, activity, recipient, inboxLis
158
162
  const { class: cls, listener } = dispatched;
159
163
  span.updateName(`activitypub.dispatch_inbox_listener ${cls.name}`);
160
164
  try {
161
- await listener(inboxContextFactory(recipient, json, activity?.id?.href, require_actor.getTypeId(activity).href), activity);
165
+ const contextFactory = listenerInboxContextFactory ?? inboxContextFactory;
166
+ await listener(contextFactory(recipient, contextFactory === inboxContextFactory ? json : originalJson ?? json, activity?.id?.href, require_actor.getTypeId(activity).href), activity);
162
167
  } catch (error) {
163
168
  try {
164
169
  await inboxErrorHandler?.(ctx, error);
@@ -335,7 +340,7 @@ var FederationBuilderImpl = class {
335
340
  this.collectionTypeIds = {};
336
341
  }
337
342
  async build(options) {
338
- const { FederationImpl } = await Promise.resolve().then(() => require("./middleware-TO6qMFz0.cjs"));
343
+ const { FederationImpl } = await Promise.resolve().then(() => require("./middleware-DfCM9WFN.cjs"));
339
344
  const f = new FederationImpl(options);
340
345
  const trailingSlashInsensitiveValue = f.router.trailingSlashInsensitive;
341
346
  f.router = this.router.clone();
@@ -998,7 +1003,123 @@ function preferredMediaTypes(accept) {
998
1003
  return parseAccept(accept === void 0 ? "*/*" : accept ?? "").filter(isQuality).sort(compareSpecs).map(getFullType);
999
1004
  }
1000
1005
  //#endregion
1006
+ //#region src/federation/temporal.ts
1007
+ function isPlainObject(value) {
1008
+ return typeof value === "object" && value != null && !Array.isArray(value);
1009
+ }
1010
+ function normalizeDateTimeLiteral(value) {
1011
+ return value.substring(19).match(/[Z+-]/) ? value : value + "Z";
1012
+ }
1013
+ function isMalformedDateTimeLiteral(value) {
1014
+ if (typeof value !== "string") return false;
1015
+ try {
1016
+ Temporal.Instant.from(normalizeDateTimeLiteral(value));
1017
+ return false;
1018
+ } catch {
1019
+ return true;
1020
+ }
1021
+ }
1022
+ function isMalformedDurationLiteral(value) {
1023
+ if (typeof value !== "string") return false;
1024
+ try {
1025
+ Temporal.Duration.from(value);
1026
+ return false;
1027
+ } catch {
1028
+ return true;
1029
+ }
1030
+ }
1031
+ const TEMPORAL_DATE_TIME_IRIS = new Set([
1032
+ "https://www.w3.org/ns/activitystreams#deleted",
1033
+ "https://www.w3.org/ns/activitystreams#endTime",
1034
+ "https://www.w3.org/ns/activitystreams#published",
1035
+ "https://www.w3.org/ns/activitystreams#startTime",
1036
+ "https://www.w3.org/ns/activitystreams#updated",
1037
+ "http://purl.org/dc/terms/created",
1038
+ "https://w3id.org/security#created"
1039
+ ]);
1040
+ const TEMPORAL_DURATION_IRIS = new Set(["https://www.w3.org/ns/activitystreams#duration"]);
1041
+ const QUESTION_CLOSED_IRI = "https://www.w3.org/ns/activitystreams#closed";
1042
+ const XSD_DATE_TIME_IRI = "http://www.w3.org/2001/XMLSchema#dateTime";
1043
+ function hasMalformedExpandedDateTimeLiteral(value) {
1044
+ if (Array.isArray(value)) return value.some(hasMalformedExpandedDateTimeLiteral);
1045
+ return isPlainObject(value) && "@value" in value && isMalformedDateTimeLiteral(value["@value"]);
1046
+ }
1047
+ function hasMalformedExpandedQuestionClosedLiteral(value) {
1048
+ if (Array.isArray(value)) return value.some(hasMalformedExpandedQuestionClosedLiteral);
1049
+ if (!isPlainObject(value) || !("@value" in value)) return false;
1050
+ const literal = value["@value"];
1051
+ if (typeof literal === "boolean") return false;
1052
+ if (typeof literal !== "string") return false;
1053
+ if (value["@type"] !== XSD_DATE_TIME_IRI) return false;
1054
+ if (new Date(literal).toString() === "Invalid Date") return false;
1055
+ return isMalformedDateTimeLiteral(literal);
1056
+ }
1057
+ function hasMalformedExpandedDurationLiteral(value) {
1058
+ if (Array.isArray(value)) return value.some(hasMalformedExpandedDurationLiteral);
1059
+ return isPlainObject(value) && "@value" in value && isMalformedDurationLiteral(value["@value"]);
1060
+ }
1061
+ function hasMalformedKnownTemporalLiteralInternal(value, visited) {
1062
+ if (Array.isArray(value)) return value.some((item) => hasMalformedKnownTemporalLiteralInternal(item, visited));
1063
+ if (!isPlainObject(value)) return false;
1064
+ if (visited.has(value)) return false;
1065
+ visited.add(value);
1066
+ if ("@value" in value) return false;
1067
+ for (const [key, child] of Object.entries(value)) {
1068
+ if (TEMPORAL_DATE_TIME_IRIS.has(key)) {
1069
+ if (hasMalformedExpandedDateTimeLiteral(child)) return true;
1070
+ continue;
1071
+ }
1072
+ if (key === QUESTION_CLOSED_IRI) {
1073
+ if (hasMalformedExpandedQuestionClosedLiteral(child)) return true;
1074
+ continue;
1075
+ }
1076
+ if (TEMPORAL_DURATION_IRIS.has(key)) {
1077
+ if (hasMalformedExpandedDurationLiteral(child)) return true;
1078
+ continue;
1079
+ }
1080
+ if (hasMalformedKnownTemporalLiteralInternal(child, visited)) return true;
1081
+ }
1082
+ return false;
1083
+ }
1084
+ async function hasMalformedKnownTemporalLiteral(value, contextLoader) {
1085
+ try {
1086
+ return hasMalformedKnownTemporalLiteralInternal(await jsonld.default.expand(value, {
1087
+ documentLoader: require_proof.getNormalizationContextLoader(contextLoader),
1088
+ keepFreeFloatingNodes: true
1089
+ }), /* @__PURE__ */ new Set());
1090
+ } catch {
1091
+ return false;
1092
+ }
1093
+ }
1094
+ //#endregion
1001
1095
  //#region src/federation/handler.ts
1096
+ const rawInboxContextFactorySymbol = Symbol("fedify.rawInboxContextFactory");
1097
+ function isRemoteContextLoadingFailure$1(error) {
1098
+ return error instanceof Error && typeof error.details === "object" && error.details != null && error.details.code === "loading remote context failed";
1099
+ }
1100
+ function isPermanentRemoteContextError$1(error) {
1101
+ if (!(error instanceof Error) || error.name !== "jsonld.InvalidUrl") return false;
1102
+ const details = error.details;
1103
+ if (details?.code === "invalid remote context") return true;
1104
+ return isRemoteContextLoadingFailure$1(error) && typeof details?.url === "string" && !URL.canParse(details.url) && require_proof.isClearlyMalformedContextReference(details.url);
1105
+ }
1106
+ function isInvalidJsonLdError(error) {
1107
+ if (!(error instanceof Error)) return false;
1108
+ const name = error.name;
1109
+ return name === "UnsafeJsonLdError" || error instanceof require_proof.InvalidContextReferenceError || isPermanentRemoteContextError$1(error) || name === "jsonld.SyntaxError" && !isRemoteContextLoadingFailure$1(error);
1110
+ }
1111
+ function isValidationTypeError(error) {
1112
+ return error instanceof TypeError && (/^(Invalid JSON-LD:|Invalid type:|Unexpected type:)/.test(error.message) || require_proof.isInvalidUrlTypeError(error));
1113
+ }
1114
+ function isPermanentActivityParseError(error) {
1115
+ return isInvalidJsonLdError(error) || isValidationTypeError(error);
1116
+ }
1117
+ function hasHttpSignatureHeaders(request) {
1118
+ return request.headers.has("Signature") || request.headers.has("Signature-Input");
1119
+ }
1120
+ function hasObjectIntegrityProof(json) {
1121
+ return typeof json === "object" && json != null && "proof" in json;
1122
+ }
1002
1123
  function acceptsJsonLd(request) {
1003
1124
  const accept = request.headers.get("Accept");
1004
1125
  const types = accept ? preferredMediaTypes(accept) : ["*/*"];
@@ -1382,35 +1503,120 @@ async function handleInboxInternal(request, parameters, span) {
1382
1503
  });
1383
1504
  }
1384
1505
  const keyCache = new KvKeyCache(kv, kvPrefixes.publicKey, ctx);
1385
- let ldSigVerified;
1386
- try {
1387
- ldSigVerified = await require_proof.verifyJsonLd(json, {
1388
- contextLoader: ctx.contextLoader,
1389
- documentLoader: ctx.documentLoader,
1390
- keyCache,
1391
- tracerProvider
1506
+ const jsonWithoutSig = require_proof.detachSignature(json);
1507
+ const hasLdSignature = require_proof.hasSignature(json);
1508
+ const canAttemptAlternateAuthAfterLdSignatureFailure = skipSignatureVerification || hasHttpSignatureHeaders(request) || hasObjectIntegrityProof(jsonWithoutSig);
1509
+ let deferredLdSignatureError = void 0;
1510
+ const respondInvalidActivity = async (error) => {
1511
+ logger.error("Failed to parse activity:\n{error}", {
1512
+ recipient,
1513
+ activity: json,
1514
+ error
1392
1515
  });
1393
- } catch (error) {
1394
- if (error instanceof Error && error.name === "jsonld.SyntaxError") {
1395
- logger.error("Failed to parse JSON-LD:\n{error}", {
1516
+ try {
1517
+ await inboxErrorHandler?.(ctx, error);
1518
+ } catch (error) {
1519
+ logger.error("An unexpected error occurred in inbox error handler:\n{error}", {
1520
+ error,
1521
+ activity: json,
1522
+ recipient
1523
+ });
1524
+ }
1525
+ span.setStatus({
1526
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
1527
+ message: `Failed to parse activity:\n${error}`
1528
+ });
1529
+ return new Response("Invalid activity.", {
1530
+ status: 400,
1531
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
1532
+ });
1533
+ };
1534
+ let compactedJson = json;
1535
+ let compactedJsonWithoutSig = jsonWithoutSig;
1536
+ let ldSigVerified = false;
1537
+ if (hasLdSignature) {
1538
+ try {
1539
+ compactedJson = await require_proof.compactJsonLd(json, ctx.contextLoader);
1540
+ } catch (error) {
1541
+ if (isInvalidJsonLdError(error)) {
1542
+ logger.error("Failed to parse JSON-LD:\n{error}", {
1543
+ recipient,
1544
+ error
1545
+ });
1546
+ return new Response("Invalid JSON-LD.", {
1547
+ status: 400,
1548
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
1549
+ });
1550
+ }
1551
+ if (!canAttemptAlternateAuthAfterLdSignatureFailure) throw error;
1552
+ if (!skipSignatureVerification) deferredLdSignatureError = error;
1553
+ logger.debug("Failed to normalize JSON-LD for Linked Data Signatures; deferring to another authentication path only if it verifies:\n{error}", {
1396
1554
  recipient,
1397
1555
  error
1398
1556
  });
1399
- return new Response("Invalid JSON-LD.", {
1400
- status: 400,
1401
- headers: { "Content-Type": "text/plain; charset=utf-8" }
1402
- });
1403
1557
  }
1404
- ldSigVerified = false;
1558
+ if (compactedJson !== json) {
1559
+ compactedJsonWithoutSig = require_proof.detachSignature(compactedJson);
1560
+ try {
1561
+ ldSigVerified = await require_proof.verifyCompactJsonLd(compactedJson, {
1562
+ contextLoader: ctx.contextLoader,
1563
+ documentLoader: ctx.documentLoader,
1564
+ keyCache,
1565
+ tracerProvider
1566
+ });
1567
+ } catch (error) {
1568
+ if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(compactedJsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(error);
1569
+ if (isInvalidJsonLdError(error)) {
1570
+ logger.error("Failed to parse JSON-LD:\n{error}", {
1571
+ recipient,
1572
+ error
1573
+ });
1574
+ return new Response("Invalid JSON-LD.", {
1575
+ status: 400,
1576
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
1577
+ });
1578
+ }
1579
+ if (!canAttemptAlternateAuthAfterLdSignatureFailure) throw error;
1580
+ if (!skipSignatureVerification) try {
1581
+ await require_actor.Object.fromJsonLd(compactedJson, {
1582
+ contextLoader: require_proof.getNormalizationContextLoader(ctx.contextLoader),
1583
+ documentLoader: ctx.documentLoader,
1584
+ tracerProvider
1585
+ });
1586
+ } catch (parseError) {
1587
+ if (parseError instanceof RangeError && await hasMalformedKnownTemporalLiteral(compactedJsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(parseError);
1588
+ if (isInvalidJsonLdError(parseError)) {
1589
+ logger.error("Failed to parse JSON-LD:\n{error}", {
1590
+ recipient,
1591
+ error: parseError
1592
+ });
1593
+ return new Response("Invalid JSON-LD.", {
1594
+ status: 400,
1595
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
1596
+ });
1597
+ }
1598
+ deferredLdSignatureError = parseError;
1599
+ }
1600
+ ldSigVerified = false;
1601
+ }
1602
+ }
1405
1603
  }
1406
- const jsonWithoutSig = require_proof.detachSignature(json);
1407
1604
  let activity = null;
1408
1605
  if (ldSigVerified) {
1409
1606
  logger.debug("Linked Data Signatures are verified.", {
1410
1607
  recipient,
1411
1608
  json
1412
1609
  });
1413
- activity = await require_actor.Activity.fromJsonLd(jsonWithoutSig, ctx);
1610
+ try {
1611
+ activity = await require_actor.Activity.fromJsonLd(compactedJsonWithoutSig, {
1612
+ ...ctx,
1613
+ contextLoader: require_proof.getNormalizationContextLoader(ctx.contextLoader)
1614
+ });
1615
+ } catch (error) {
1616
+ if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(compactedJsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(error);
1617
+ if (!isPermanentActivityParseError(error)) throw error;
1618
+ return await respondInvalidActivity(error);
1619
+ }
1414
1620
  } else {
1415
1621
  logger.debug("Linked Data Signatures are not verified.", {
1416
1622
  recipient,
@@ -1418,12 +1624,21 @@ async function handleInboxInternal(request, parameters, span) {
1418
1624
  });
1419
1625
  try {
1420
1626
  activity = await require_proof.verifyObject(require_actor.Activity, jsonWithoutSig, {
1421
- contextLoader: ctx.contextLoader,
1627
+ contextLoader: require_proof.wrapContextLoaderForJsonLd(ctx.contextLoader),
1422
1628
  documentLoader: ctx.documentLoader,
1423
1629
  keyCache,
1424
1630
  tracerProvider
1425
1631
  });
1426
1632
  } catch (error) {
1633
+ if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(jsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(error);
1634
+ if (deferredLdSignatureError != null) {
1635
+ logger.debug("Object Integrity Proof fallback did not supersede a deferred Linked Data Signature failure:\n{error}", {
1636
+ recipient,
1637
+ error
1638
+ });
1639
+ activity = null;
1640
+ }
1641
+ if (!isPermanentActivityParseError(error)) throw error;
1427
1642
  logger.error("Failed to parse activity:\n{error}", {
1428
1643
  recipient,
1429
1644
  activity: json,
@@ -1467,6 +1682,7 @@ async function handleInboxInternal(request, parameters, span) {
1467
1682
  tracerProvider
1468
1683
  });
1469
1684
  if (key == null) {
1685
+ if (deferredLdSignatureError != null) throw deferredLdSignatureError;
1470
1686
  logger.error("Failed to verify the request's HTTP Signatures.", { recipient });
1471
1687
  span.setStatus({
1472
1688
  code: _opentelemetry_api.SpanStatusCode.ERROR,
@@ -1479,11 +1695,20 @@ async function handleInboxInternal(request, parameters, span) {
1479
1695
  } else logger.debug("HTTP Signatures are verified.", { recipient });
1480
1696
  httpSigKey = key;
1481
1697
  }
1482
- activity = await require_actor.Activity.fromJsonLd(jsonWithoutSig, ctx);
1698
+ try {
1699
+ activity = await require_actor.Activity.fromJsonLd(jsonWithoutSig, {
1700
+ ...ctx,
1701
+ contextLoader: require_proof.wrapContextLoaderForJsonLd(ctx.contextLoader)
1702
+ });
1703
+ } catch (error) {
1704
+ if (!isPermanentActivityParseError(error)) throw error;
1705
+ return await respondInvalidActivity(error);
1706
+ }
1483
1707
  }
1484
1708
  if (activity.id != null) span.setAttribute("activitypub.activity.id", activity.id.href);
1485
1709
  span.setAttribute("activitypub.activity.type", require_actor.getTypeId(activity).href);
1486
1710
  if (httpSigKey != null && !await require_proof.doesActorOwnKey(activity, httpSigKey, ctx)) {
1711
+ if (deferredLdSignatureError != null) throw deferredLdSignatureError;
1487
1712
  logger.error("The signer ({keyId}) and the actor ({actorId}) do not match.", {
1488
1713
  activity: json,
1489
1714
  recipient,
@@ -1502,10 +1727,14 @@ async function handleInboxInternal(request, parameters, span) {
1502
1727
  const routeResult = await routeActivity({
1503
1728
  context: ctx,
1504
1729
  json,
1730
+ originalJson: json,
1731
+ normalizedActivity: hasLdSignature && compactedJson !== json ? compactedJson : void 0,
1732
+ ldSignatureVerified: hasLdSignature ? ldSigVerified : void 0,
1505
1733
  activity,
1506
1734
  recipient,
1507
1735
  inboxListeners,
1508
1736
  inboxContextFactory,
1737
+ listenerInboxContextFactory: ldSigVerified ? inboxContextFactory[rawInboxContextFactorySymbol] : void 0,
1509
1738
  inboxErrorHandler,
1510
1739
  kv,
1511
1740
  kvPrefixes,
@@ -2317,6 +2546,18 @@ async function sendActivityInternal({ activity, activityId, keys, inbox, headers
2317
2546
  }
2318
2547
  //#endregion
2319
2548
  //#region src/federation/middleware.ts
2549
+ function isRemoteContextLoadingFailure(error) {
2550
+ return error instanceof Error && typeof error.details === "object" && error.details != null && error.details.code === "loading remote context failed";
2551
+ }
2552
+ function isPermanentRemoteContextError(error) {
2553
+ if (!(error instanceof Error) || error.name !== "jsonld.InvalidUrl") return false;
2554
+ const details = error.details;
2555
+ if (details?.code === "invalid remote context") return true;
2556
+ return isRemoteContextLoadingFailure(error) && typeof details?.url === "string" && !URL.canParse(details.url) && require_proof.isClearlyMalformedContextReference(details.url);
2557
+ }
2558
+ function isPermanentInboxParseError(error) {
2559
+ return error instanceof Error && (error.name === "UnsafeJsonLdError" || error instanceof require_proof.InvalidContextReferenceError || isPermanentRemoteContextError(error) || error.name === "jsonld.SyntaxError" && !isRemoteContextLoadingFailure(error)) || error instanceof TypeError && (/^(Invalid JSON-LD:|Invalid type:|Unexpected type:)/.test(error.message) || require_proof.isInvalidUrlTypeError(error));
2560
+ }
2320
2561
  /**
2321
2562
  * Create a new {@link Federation} instance.
2322
2563
  * @param parameters Parameters for initializing the instance.
@@ -2648,7 +2889,7 @@ var FederationImpl = class extends FederationBuilderImpl {
2648
2889
  }
2649
2890
  logger.info("Successfully sent activity {activityId} to {inbox}.", { ...logData });
2650
2891
  }
2651
- async #listenInboxMessage(ctxData, message, span) {
2892
+ async #listenInboxMessage(ctxData, message, _span) {
2652
2893
  const logger = (0, _logtape_logtape.getLogger)([
2653
2894
  "fedify",
2654
2895
  "federation",
@@ -2661,60 +2902,28 @@ var FederationImpl = class extends FederationBuilderImpl {
2661
2902
  const identity = await this.sharedInboxKeyDispatcher(context);
2662
2903
  if (identity != null) context = this.#createContext(baseUrl, ctxData, { documentLoader: "identifier" in identity || "username" in identity || "handle" in identity ? await context.getDocumentLoader(identity) : context.getDocumentLoader(identity) });
2663
2904
  }
2664
- const activity = await require_actor.Activity.fromJsonLd(message.activity, context);
2665
- span.setAttribute("activitypub.activity.type", require_actor.getTypeId(activity).href);
2666
- if (activity.id != null) span.setAttribute("activitypub.activity.id", activity.id.href);
2667
- const cacheKey = activity.id == null ? null : [
2668
- ...this.kvPrefixes.activityIdempotence,
2669
- context.origin,
2670
- activity.id.href
2671
- ];
2672
- if (cacheKey != null) {
2673
- if (await this.kv.get(cacheKey) === true) {
2674
- logger.debug("Activity {activityId} has already been processed.", {
2675
- activityId: activity.id?.href,
2676
- activity: message.activity,
2677
- recipient: message.identifier
2678
- });
2679
- return;
2680
- }
2681
- }
2682
2905
  await this._getTracer().startActiveSpan("activitypub.dispatch_inbox_listener", { kind: _opentelemetry_api.SpanKind.INTERNAL }, async (span) => {
2683
- const dispatched = this.inboxListeners?.dispatchWithClass(activity);
2684
- if (dispatched == null) {
2685
- logger.error("Unsupported activity type:\n{activity}", {
2686
- activityId: activity.id?.href,
2687
- activity: message.activity,
2688
- recipient: message.identifier,
2689
- trial: message.attempt
2690
- });
2691
- span.setStatus({
2692
- code: _opentelemetry_api.SpanStatusCode.ERROR,
2693
- message: `Unsupported activity type: ${require_actor.getTypeId(activity).href}`
2694
- });
2695
- span.end();
2696
- return;
2697
- }
2698
- const { class: cls, listener } = dispatched;
2699
- span.updateName(`activitypub.dispatch_inbox_listener ${cls.name}`);
2700
- try {
2701
- await listener(context.toInboxContext(message.identifier, message.activity, activity.id?.href, require_actor.getTypeId(activity).href), activity);
2702
- } catch (error) {
2906
+ let activity = null;
2907
+ let cacheKey = null;
2908
+ const reportInboxError = async (error) => {
2703
2909
  try {
2704
2910
  await this.inboxErrorHandler?.(context, error);
2705
2911
  } catch (error) {
2706
2912
  logger.error("An unexpected error occurred in inbox error handler:\n{error}", {
2707
2913
  error,
2708
2914
  trial: message.attempt,
2709
- activityId: activity.id?.href,
2915
+ activityId: activity?.id?.href,
2710
2916
  activity: message.activity,
2711
2917
  recipient: message.identifier
2712
2918
  });
2713
2919
  }
2920
+ };
2921
+ const handleRetriableFailure = async (error) => {
2922
+ await reportInboxError(error);
2714
2923
  if (this.inboxQueue?.nativeRetrial) {
2715
2924
  logger.error("Failed to process the incoming activity {activityId}; backend will handle retry:\n{error}", {
2716
2925
  error,
2717
- activityId: activity.id?.href,
2926
+ activityId: activity?.id?.href,
2718
2927
  activity: message.activity,
2719
2928
  recipient: message.identifier
2720
2929
  });
@@ -2733,17 +2942,25 @@ var FederationImpl = class extends FederationBuilderImpl {
2733
2942
  logger.error("Failed to process the incoming activity {activityId} (attempt #{attempt}); retry...:\n{error}", {
2734
2943
  error,
2735
2944
  attempt: message.attempt,
2736
- activityId: activity.id?.href,
2945
+ activityId: activity?.id?.href,
2737
2946
  activity: message.activity,
2738
2947
  recipient: message.identifier
2739
2948
  });
2949
+ if (this.inboxQueue == null) {
2950
+ span.setStatus({
2951
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
2952
+ message: String(error)
2953
+ });
2954
+ span.end();
2955
+ throw error;
2956
+ }
2740
2957
  await this.inboxQueue?.enqueue({
2741
2958
  ...message,
2742
2959
  attempt: message.attempt + 1
2743
2960
  }, { delay: Temporal.Duration.compare(delay, { seconds: 0 }) < 0 ? Temporal.Duration.from({ seconds: 0 }) : delay });
2744
2961
  } else logger.error("Failed to process the incoming activity {activityId} after {trial} attempts; giving up:\n{error}", {
2745
2962
  error,
2746
- activityId: activity.id?.href,
2963
+ activityId: activity?.id?.href,
2747
2964
  activity: message.activity,
2748
2965
  recipient: message.identifier
2749
2966
  });
@@ -2752,11 +2969,108 @@ var FederationImpl = class extends FederationBuilderImpl {
2752
2969
  message: String(error)
2753
2970
  });
2754
2971
  span.end();
2972
+ };
2973
+ let dispatched;
2974
+ let parseInput = void 0;
2975
+ let parseContextLoader = context.contextLoader;
2976
+ try {
2977
+ const hasSignatureField = require_proof.hasSignature(message.activity);
2978
+ const shouldParseFromNormalizedSignedPayload = message.ldSignatureVerified === true || message.normalizedActivity != null || message.ldSignatureVerified == null && hasSignatureField;
2979
+ const parseContext = hasSignatureField ? {
2980
+ ...context,
2981
+ contextLoader: require_proof.getNormalizationContextLoader(context.contextLoader)
2982
+ } : {
2983
+ ...context,
2984
+ contextLoader: require_proof.wrapContextLoaderForJsonLd(context.contextLoader)
2985
+ };
2986
+ parseContextLoader = parseContext.contextLoader;
2987
+ let normalizedActivity;
2988
+ if (shouldParseFromNormalizedSignedPayload) {
2989
+ normalizedActivity = message.normalizedActivity ?? await require_proof.compactJsonLd(message.activity, context.contextLoader);
2990
+ require_proof.assertSafeJsonLd(normalizedActivity);
2991
+ }
2992
+ parseInput = shouldParseFromNormalizedSignedPayload ? require_proof.detachSignature(normalizedActivity) : hasSignatureField ? require_proof.detachSignature(message.activity) : message.activity;
2993
+ activity = await require_actor.Activity.fromJsonLd(parseInput, parseContext);
2994
+ span.setAttribute("activitypub.activity.type", require_actor.getTypeId(activity).href);
2995
+ if (activity.id != null) span.setAttribute("activitypub.activity.id", activity.id.href);
2996
+ cacheKey = activity.id == null ? null : [
2997
+ ...this.kvPrefixes.activityIdempotence,
2998
+ context.origin,
2999
+ activity.id.href
3000
+ ];
3001
+ if (cacheKey != null) {
3002
+ if (await this.kv.get(cacheKey) === true) {
3003
+ logger.debug("Activity {activityId} has already been processed.", {
3004
+ activityId: activity.id?.href,
3005
+ activity: message.activity,
3006
+ recipient: message.identifier
3007
+ });
3008
+ span.end();
3009
+ return;
3010
+ }
3011
+ }
3012
+ dispatched = this.inboxListeners?.dispatchWithClass(activity);
3013
+ } catch (error) {
3014
+ if (activity == null && error instanceof RangeError && await hasMalformedKnownTemporalLiteral(parseInput, parseContextLoader)) {
3015
+ await reportInboxError(error);
3016
+ logger.error("Failed to parse the queued incoming activity {activityId}:\n{error}", {
3017
+ error,
3018
+ trial: message.attempt,
3019
+ activityId: null,
3020
+ activity: message.activity,
3021
+ recipient: message.identifier
3022
+ });
3023
+ span.setStatus({
3024
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
3025
+ message: String(error)
3026
+ });
3027
+ span.end();
3028
+ return;
3029
+ }
3030
+ if (isPermanentInboxParseError(error)) {
3031
+ await reportInboxError(error);
3032
+ logger.error("Failed to parse the queued incoming activity {activityId}:\n{error}", {
3033
+ error,
3034
+ trial: message.attempt,
3035
+ activityId: activity?.id?.href,
3036
+ activity: message.activity,
3037
+ recipient: message.identifier
3038
+ });
3039
+ span.setStatus({
3040
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
3041
+ message: String(error)
3042
+ });
3043
+ span.end();
3044
+ return;
3045
+ }
3046
+ await handleRetriableFailure(error);
3047
+ return;
3048
+ }
3049
+ if (dispatched == null) {
3050
+ logger.error("Unsupported activity type:\n{activity}", {
3051
+ activityId: activity.id?.href,
3052
+ activity: message.activity,
3053
+ recipient: message.identifier,
3054
+ trial: message.attempt
3055
+ });
3056
+ span.setStatus({
3057
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
3058
+ message: `Unsupported activity type: ${require_actor.getTypeId(activity).href}`
3059
+ });
3060
+ span.end();
3061
+ return;
3062
+ }
3063
+ const { class: cls, listener } = dispatched;
3064
+ span.updateName(`activitypub.dispatch_inbox_listener ${cls.name}`);
3065
+ try {
3066
+ await listener(context.toInboxContext(message.identifier, message.activity, activity.id?.href, require_actor.getTypeId(activity).href), activity);
3067
+ } catch (error) {
3068
+ await handleRetriableFailure(error);
2755
3069
  return;
2756
3070
  }
2757
3071
  if (cacheKey != null) await this.kv.set(cacheKey, true, { ttl: Temporal.Duration.from({ days: 1 }) });
2758
3072
  logger.info("Activity {activityId} has been processed.", {
2759
- activityId: activity.id?.href,
3073
+ activityId: activity?.id?.href,
2760
3074
  activity: message.activity,
2761
3075
  recipient: message.identifier
2762
3076
  });
@@ -3078,16 +3392,18 @@ var FederationImpl = class extends FederationBuilderImpl {
3078
3392
  onNotAcceptable
3079
3393
  });
3080
3394
  context = this.#createContext(request, contextData, { documentLoader: await context.getDocumentLoader({ identifier: route.values.identifier ?? route.values.handle }) });
3081
- case "sharedInbox":
3395
+ case "sharedInbox": {
3082
3396
  if (routeName !== "inbox" && this.sharedInboxKeyDispatcher != null) {
3083
3397
  const identity = await this.sharedInboxKeyDispatcher(context);
3084
3398
  if (identity != null) context = this.#createContext(request, contextData, { documentLoader: "identifier" in identity || "username" in identity || "handle" in identity ? await context.getDocumentLoader(identity) : context.getDocumentLoader(identity) });
3085
3399
  }
3086
3400
  if (!this.manuallyStartQueue) this._startQueueInternal(contextData);
3401
+ const inboxContextFactory = context.toInboxContext.bind(context);
3402
+ inboxContextFactory[rawInboxContextFactorySymbol] = context.toInboxContext.bind(context);
3087
3403
  return await handleInbox(request, {
3088
3404
  recipient: route.values.identifier ?? route.values.handle ?? null,
3089
3405
  context,
3090
- inboxContextFactory: context.toInboxContext.bind(context),
3406
+ inboxContextFactory,
3091
3407
  kv: this.kv,
3092
3408
  kvPrefixes: this.kvPrefixes,
3093
3409
  queue: this.inboxQueue,
@@ -3100,6 +3416,7 @@ var FederationImpl = class extends FederationBuilderImpl {
3100
3416
  tracerProvider: this.tracerProvider,
3101
3417
  idempotencyStrategy: this.idempotencyStrategy
3102
3418
  });
3419
+ }
3103
3420
  case "following": return await handleCollection(request, {
3104
3421
  name: "following",
3105
3422
  identifier: route.values.identifier ?? route.values.handle,
@@ -3850,6 +4167,7 @@ var ContextImpl = class ContextImpl {
3850
4167
  const routeResult = await routeActivity({
3851
4168
  context: this,
3852
4169
  json,
4170
+ ldSignatureVerified: false,
3853
4171
  activity,
3854
4172
  recipient,
3855
4173
  inboxListeners: this.federation.inboxListeners,
@@ -3948,6 +4266,14 @@ var RequestContextImpl = class RequestContextImpl extends ContextImpl {
3948
4266
  };
3949
4267
  var InboxContextImpl = class InboxContextImpl extends ContextImpl {
3950
4268
  recipient;
4269
+ /**
4270
+ * The original received activity payload.
4271
+ *
4272
+ * Fedify may normalize a Linked Data Signature payload internally for safe
4273
+ * parsing, but forwarding must keep the sender's payload unchanged so
4274
+ * third-party signatures/proofs remain intact.
4275
+ * @internal
4276
+ */
3951
4277
  activity;
3952
4278
  activityId;
3953
4279
  activityType;