@fedify/fedify 2.2.3-dev.1098 → 2.2.3

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 (60) hide show
  1. package/dist/{builder-mqtih91o.mjs → builder-CaVN56-q.mjs} +2 -2
  2. package/dist/compat/mod.d.cts +1 -1
  3. package/dist/compat/mod.d.ts +1 -1
  4. package/dist/compat/transformers.test.mjs +1 -1
  5. package/dist/{context-BPMgyX7m.d.ts → context-BU-1O90h.d.ts} +48 -6
  6. package/dist/{context-DwkhwUX9.d.cts → context-DVA8wHZ0.d.cts} +48 -6
  7. package/dist/{deno-CziVFvS6.mjs → deno-DMg4SgCb.mjs} +1 -1
  8. package/dist/{docloader-fI9DeYyB.mjs → docloader-Da15YRxG.mjs} +2 -2
  9. package/dist/federation/builder.test.mjs +1 -1
  10. package/dist/federation/handler.test.mjs +1363 -43
  11. package/dist/federation/idempotency.test.mjs +2 -2
  12. package/dist/federation/middleware.test.mjs +1584 -80
  13. package/dist/federation/mod.cjs +1 -1
  14. package/dist/federation/mod.d.cts +2 -2
  15. package/dist/federation/mod.d.ts +2 -2
  16. package/dist/federation/mod.js +1 -1
  17. package/dist/federation/retry.test.mjs +1 -1
  18. package/dist/federation/send.test.mjs +3 -3
  19. package/dist/federation/temporal.test.d.mts +2 -0
  20. package/dist/federation/temporal.test.mjs +71 -0
  21. package/dist/federation/webfinger.test.mjs +1 -1
  22. package/dist/{http-D8qsXrUS.js → http-BPPaA2uz.js} +1 -1
  23. package/dist/{http-BDCGf4Ac.mjs → http-C_edJspG.mjs} +2 -2
  24. package/dist/{http-kPc328Pc.cjs → http-Cl0Q2bUO.cjs} +1 -1
  25. package/dist/{key-D3TgMhcs.mjs → key-BAQuZEU1.mjs} +1 -1
  26. package/dist/{kv-cache-D_eVhctK.js → kv-cache-C4DGZ_t4.js} +1 -1
  27. package/dist/{kv-cache-zxW74Wfd.cjs → kv-cache-DmGi6uC-.cjs} +1 -1
  28. package/dist/ld-tusP_XxG.mjs +573 -0
  29. package/dist/{middleware-xR9KxICq.cjs → middleware-0V-9qj7m.cjs} +399 -73
  30. package/dist/{middleware-gXlDLkok.js → middleware-Ar1QOOPG.js} +396 -71
  31. package/dist/{middleware-2gmMVy8b.mjs → middleware-D9k0Knum.mjs} +314 -78
  32. package/dist/{middleware-BuOXw_hM.cjs → middleware-OQPBzyvx.cjs} +1 -1
  33. package/dist/{middleware-CfaiRKQ9.mjs → middleware-madKLp2f.mjs} +1 -1
  34. package/dist/{mod-CNAHY39V.d.ts → mod-BVt6iTmH.d.ts} +1 -1
  35. package/dist/{mod-Bi6WOdti.d.cts → mod-q-NFLW6B.d.cts} +1 -1
  36. package/dist/mod.cjs +4 -4
  37. package/dist/mod.d.cts +2 -2
  38. package/dist/mod.d.ts +2 -2
  39. package/dist/mod.js +4 -4
  40. package/dist/nodeinfo/handler.test.mjs +1 -1
  41. package/dist/{owner-DBSV2TSl.mjs → owner-DRHNR5YO.mjs} +2 -2
  42. package/dist/{proof-tz91vdtN.mjs → proof-DLhLRv3m.mjs} +2 -2
  43. package/dist/{proof-CZDkoeWG.cjs → proof-DfrItHmh.cjs} +351 -3
  44. package/dist/{proof-z93OkIov.js → proof-SQ4cQs3A.js} +298 -4
  45. package/dist/{send-CNjG31rJ.mjs → send-C7tim5U9.mjs} +2 -2
  46. package/dist/sig/http.test.mjs +2 -2
  47. package/dist/sig/key.test.mjs +1 -1
  48. package/dist/sig/ld.test.mjs +558 -2
  49. package/dist/sig/mod.cjs +2 -2
  50. package/dist/sig/mod.js +2 -2
  51. package/dist/sig/owner.test.mjs +1 -1
  52. package/dist/sig/proof.test.mjs +1 -1
  53. package/dist/temporal-LL61Ddf2.mjs +95 -0
  54. package/dist/testing/mod.d.mts +48 -6
  55. package/dist/utils/docloader.test.mjs +2 -2
  56. package/dist/utils/mod.cjs +1 -1
  57. package/dist/utils/mod.js +1 -1
  58. package/package.json +5 -5
  59. package/dist/ld-D_u8mdpv.mjs +0 -279
  60. /package/dist/{retry-bMXBL97A.mjs → retry-v_sGLH1d.mjs} +0 -0
@@ -2,10 +2,10 @@ import { Temporal } from "@js-temporal/polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
3
  import { t as __exportAll } from "./chunk-CRNNMoPX.js";
4
4
  import { r as getDefaultActivityTransformers } from "./transformers-BGMIq1cs.js";
5
- import { _ as version, a as verifyRequestDetailed, d as validateCryptoKey, f as formatAcceptSignature, g as name, i as verifyRequest, n as parseRfc9421SignatureInput, o as exportJwk, t as doubleKnock, u as importJwk } from "./http-D8qsXrUS.js";
6
- import { c as getKeyOwner, d as detachSignature, f as hasSignatureLike, i as verifyObject, m as verifyJsonLd, n as hasProofLike, o as normalizeOutgoingActivityJsonLd, p as signJsonLd, r as signObject, s as doesActorOwnKey } from "./proof-z93OkIov.js";
5
+ import { _ as version, a as verifyRequestDetailed, d as validateCryptoKey, f as formatAcceptSignature, g as name, i as verifyRequest, n as parseRfc9421SignatureInput, o as exportJwk, t as doubleKnock, u as importJwk } from "./http-BPPaA2uz.js";
6
+ import { _ as hasSignatureLike, b as signJsonLd, c as getKeyOwner, f as compactJsonLd, g as hasSignature, h as getNormalizationContextLoader, i as verifyObject, l as InvalidContextReferenceError, m as detachSignature, n as hasProofLike, o as normalizeOutgoingActivityJsonLd, r as signObject, s as doesActorOwnKey, u as assertSafeJsonLd, v as isClearlyMalformedContextReference, w as wrapContextLoaderForJsonLd, x as verifyCompactJsonLd, y as isInvalidUrlTypeError } from "./proof-SQ4cQs3A.js";
7
7
  import { n as getNodeInfo, t as nodeInfoToJson } from "./types-CAY3OdLq.js";
8
- import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-D_eVhctK.js";
8
+ import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-C4DGZ_t4.js";
9
9
  import { getLogger, withContext } from "@logtape/logtape";
10
10
  import { Activity, Collection, CollectionPage, CryptographicKey, Link, Multikey, Object as Object$1, OrderedCollection, OrderedCollectionPage, Tombstone, getTypeId, lookupObject, traverseCollection } from "@fedify/vocab";
11
11
  import { SpanKind, SpanStatusCode, context, propagation, trace } from "@opentelemetry/api";
@@ -15,6 +15,7 @@ import { parseTemplate } from "url-template";
15
15
  import { encodeHex } from "byte-encodings/hex";
16
16
  import { FetchError, getDocumentLoader } from "@fedify/vocab-runtime";
17
17
  import { ATTR_HTTP_REQUEST_HEADER, ATTR_HTTP_REQUEST_METHOD, ATTR_HTTP_RESPONSE_HEADER, ATTR_HTTP_RESPONSE_STATUS_CODE, ATTR_URL_FULL } from "@opentelemetry/semantic-conventions";
18
+ import jsonld from "@fedify/vocab-runtime/jsonld";
18
19
  import { lookupWebFinger } from "@fedify/webfinger";
19
20
  import { domainToASCII } from "node:url";
20
21
  //#region src/federation/activity-listener.ts
@@ -770,7 +771,7 @@ async function buildCollectionSynchronizationHeader(collectionId, actorIds) {
770
771
  }
771
772
  //#endregion
772
773
  //#region src/federation/inbox.ts
773
- async function routeActivity({ context: ctx, json, activity, recipient, inboxListeners, inboxContextFactory, inboxErrorHandler, kv, kvPrefixes, queue, span, tracerProvider, idempotencyStrategy }) {
774
+ async function routeActivity({ context: ctx, json, originalJson, normalizedActivity, ldSignatureVerified, activity, recipient, inboxListeners, inboxContextFactory, listenerInboxContextFactory, inboxErrorHandler, kv, kvPrefixes, queue, span, tracerProvider, idempotencyStrategy }) {
774
775
  const logger = getLogger([
775
776
  "fedify",
776
777
  "federation",
@@ -827,7 +828,9 @@ async function routeActivity({ context: ctx, json, activity, recipient, inboxLis
827
828
  type: "inbox",
828
829
  id: crypto.randomUUID(),
829
830
  baseUrl: ctx.origin,
830
- activity: json,
831
+ activity: originalJson ?? json,
832
+ ...normalizedActivity == null ? {} : { normalizedActivity },
833
+ ...ldSignatureVerified == null ? {} : { ldSignatureVerified },
831
834
  identifier: recipient,
832
835
  attempt: 0,
833
836
  started: (/* @__PURE__ */ new Date()).toISOString(),
@@ -871,7 +874,8 @@ async function routeActivity({ context: ctx, json, activity, recipient, inboxLis
871
874
  const { class: cls, listener } = dispatched;
872
875
  span.updateName(`activitypub.dispatch_inbox_listener ${cls.name}`);
873
876
  try {
874
- await listener(inboxContextFactory(recipient, json, activity?.id?.href, getTypeId(activity).href), activity);
877
+ const contextFactory = listenerInboxContextFactory ?? inboxContextFactory;
878
+ await listener(contextFactory(recipient, contextFactory === inboxContextFactory ? json : originalJson ?? json, activity?.id?.href, getTypeId(activity).href), activity);
875
879
  } catch (error) {
876
880
  try {
877
881
  await inboxErrorHandler?.(ctx, error);
@@ -1062,7 +1066,123 @@ function acceptsJsonLd(request) {
1062
1066
  return types.includes("application/activity+json") || types.includes("application/ld+json") || types.includes("application/json");
1063
1067
  }
1064
1068
  //#endregion
1069
+ //#region src/federation/temporal.ts
1070
+ function isPlainObject(value) {
1071
+ return typeof value === "object" && value != null && !Array.isArray(value);
1072
+ }
1073
+ function normalizeDateTimeLiteral(value) {
1074
+ return value.substring(19).match(/[Z+-]/) ? value : value + "Z";
1075
+ }
1076
+ function isMalformedDateTimeLiteral(value) {
1077
+ if (typeof value !== "string") return false;
1078
+ try {
1079
+ Temporal.Instant.from(normalizeDateTimeLiteral(value));
1080
+ return false;
1081
+ } catch {
1082
+ return true;
1083
+ }
1084
+ }
1085
+ function isMalformedDurationLiteral(value) {
1086
+ if (typeof value !== "string") return false;
1087
+ try {
1088
+ Temporal.Duration.from(value);
1089
+ return false;
1090
+ } catch {
1091
+ return true;
1092
+ }
1093
+ }
1094
+ const TEMPORAL_DATE_TIME_IRIS = new Set([
1095
+ "https://www.w3.org/ns/activitystreams#deleted",
1096
+ "https://www.w3.org/ns/activitystreams#endTime",
1097
+ "https://www.w3.org/ns/activitystreams#published",
1098
+ "https://www.w3.org/ns/activitystreams#startTime",
1099
+ "https://www.w3.org/ns/activitystreams#updated",
1100
+ "http://purl.org/dc/terms/created",
1101
+ "https://w3id.org/security#created"
1102
+ ]);
1103
+ const TEMPORAL_DURATION_IRIS = new Set(["https://www.w3.org/ns/activitystreams#duration"]);
1104
+ const QUESTION_CLOSED_IRI = "https://www.w3.org/ns/activitystreams#closed";
1105
+ const XSD_DATE_TIME_IRI = "http://www.w3.org/2001/XMLSchema#dateTime";
1106
+ function hasMalformedExpandedDateTimeLiteral(value) {
1107
+ if (Array.isArray(value)) return value.some(hasMalformedExpandedDateTimeLiteral);
1108
+ return isPlainObject(value) && "@value" in value && isMalformedDateTimeLiteral(value["@value"]);
1109
+ }
1110
+ function hasMalformedExpandedQuestionClosedLiteral(value) {
1111
+ if (Array.isArray(value)) return value.some(hasMalformedExpandedQuestionClosedLiteral);
1112
+ if (!isPlainObject(value) || !("@value" in value)) return false;
1113
+ const literal = value["@value"];
1114
+ if (typeof literal === "boolean") return false;
1115
+ if (typeof literal !== "string") return false;
1116
+ if (value["@type"] !== XSD_DATE_TIME_IRI) return false;
1117
+ if (new Date(literal).toString() === "Invalid Date") return false;
1118
+ return isMalformedDateTimeLiteral(literal);
1119
+ }
1120
+ function hasMalformedExpandedDurationLiteral(value) {
1121
+ if (Array.isArray(value)) return value.some(hasMalformedExpandedDurationLiteral);
1122
+ return isPlainObject(value) && "@value" in value && isMalformedDurationLiteral(value["@value"]);
1123
+ }
1124
+ function hasMalformedKnownTemporalLiteralInternal(value, visited) {
1125
+ if (Array.isArray(value)) return value.some((item) => hasMalformedKnownTemporalLiteralInternal(item, visited));
1126
+ if (!isPlainObject(value)) return false;
1127
+ if (visited.has(value)) return false;
1128
+ visited.add(value);
1129
+ if ("@value" in value) return false;
1130
+ for (const [key, child] of Object.entries(value)) {
1131
+ if (TEMPORAL_DATE_TIME_IRIS.has(key)) {
1132
+ if (hasMalformedExpandedDateTimeLiteral(child)) return true;
1133
+ continue;
1134
+ }
1135
+ if (key === QUESTION_CLOSED_IRI) {
1136
+ if (hasMalformedExpandedQuestionClosedLiteral(child)) return true;
1137
+ continue;
1138
+ }
1139
+ if (TEMPORAL_DURATION_IRIS.has(key)) {
1140
+ if (hasMalformedExpandedDurationLiteral(child)) return true;
1141
+ continue;
1142
+ }
1143
+ if (hasMalformedKnownTemporalLiteralInternal(child, visited)) return true;
1144
+ }
1145
+ return false;
1146
+ }
1147
+ async function hasMalformedKnownTemporalLiteral(value, contextLoader) {
1148
+ try {
1149
+ return hasMalformedKnownTemporalLiteralInternal(await jsonld.expand(value, {
1150
+ documentLoader: getNormalizationContextLoader(contextLoader),
1151
+ keepFreeFloatingNodes: true
1152
+ }), /* @__PURE__ */ new Set());
1153
+ } catch {
1154
+ return false;
1155
+ }
1156
+ }
1157
+ //#endregion
1065
1158
  //#region src/federation/handler.ts
1159
+ const rawInboxContextFactorySymbol = Symbol("fedify.rawInboxContextFactory");
1160
+ function isRemoteContextLoadingFailure$1(error) {
1161
+ return error instanceof Error && typeof error.details === "object" && error.details != null && error.details.code === "loading remote context failed";
1162
+ }
1163
+ function isPermanentRemoteContextError$1(error) {
1164
+ if (!(error instanceof Error) || error.name !== "jsonld.InvalidUrl") return false;
1165
+ const details = error.details;
1166
+ if (details?.code === "invalid remote context") return true;
1167
+ return isRemoteContextLoadingFailure$1(error) && typeof details?.url === "string" && !URL.canParse(details.url) && isClearlyMalformedContextReference(details.url);
1168
+ }
1169
+ function isInvalidJsonLdError(error) {
1170
+ if (!(error instanceof Error)) return false;
1171
+ const name = error.name;
1172
+ return name === "UnsafeJsonLdError" || error instanceof InvalidContextReferenceError || isPermanentRemoteContextError$1(error) || name === "jsonld.SyntaxError" && !isRemoteContextLoadingFailure$1(error);
1173
+ }
1174
+ function isValidationTypeError(error) {
1175
+ return error instanceof TypeError && (/^(Invalid JSON-LD:|Invalid type:|Unexpected type:)/.test(error.message) || isInvalidUrlTypeError(error));
1176
+ }
1177
+ function isPermanentActivityParseError(error) {
1178
+ return isInvalidJsonLdError(error) || isValidationTypeError(error);
1179
+ }
1180
+ function hasHttpSignatureHeaders(request) {
1181
+ return request.headers.has("Signature") || request.headers.has("Signature-Input");
1182
+ }
1183
+ function hasObjectIntegrityProof(json) {
1184
+ return typeof json === "object" && json != null && "proof" in json;
1185
+ }
1066
1186
  /**
1067
1187
  * Handles an actor request.
1068
1188
  * @template TContextData The context data to pass to the context.
@@ -1589,28 +1709,104 @@ async function handleInboxInternal(request, parameters, span) {
1589
1709
  });
1590
1710
  }
1591
1711
  const keyCache = new KvKeyCache(kv, kvPrefixes.publicKey, ctx);
1592
- let ldSigVerified;
1593
- try {
1594
- ldSigVerified = await verifyJsonLd(json, {
1595
- contextLoader: ctx.contextLoader,
1596
- documentLoader: ctx.documentLoader,
1597
- keyCache,
1598
- tracerProvider
1712
+ const jsonWithoutSig = detachSignature(json);
1713
+ const hasLdSignature = hasSignature(json);
1714
+ const canAttemptAlternateAuthAfterLdSignatureFailure = skipSignatureVerification || hasHttpSignatureHeaders(request) || hasObjectIntegrityProof(jsonWithoutSig);
1715
+ let deferredLdSignatureError = void 0;
1716
+ const respondInvalidActivity = async (error) => {
1717
+ logger.error("Failed to parse activity:\n{error}", {
1718
+ recipient,
1719
+ activity: json,
1720
+ error
1599
1721
  });
1600
- } catch (error) {
1601
- if (error instanceof Error && error.name === "jsonld.SyntaxError") {
1602
- logger.error("Failed to parse JSON-LD:\n{error}", {
1722
+ try {
1723
+ await inboxErrorHandler?.(ctx, error);
1724
+ } catch (error) {
1725
+ logger.error("An unexpected error occurred in inbox error handler:\n{error}", {
1726
+ error,
1727
+ activity: json,
1728
+ recipient
1729
+ });
1730
+ }
1731
+ span.setStatus({
1732
+ code: SpanStatusCode.ERROR,
1733
+ message: `Failed to parse activity:\n${error}`
1734
+ });
1735
+ return new Response("Invalid activity.", {
1736
+ status: 400,
1737
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
1738
+ });
1739
+ };
1740
+ let compactedJson = json;
1741
+ let compactedJsonWithoutSig = jsonWithoutSig;
1742
+ let ldSigVerified = false;
1743
+ if (hasLdSignature) {
1744
+ try {
1745
+ compactedJson = await compactJsonLd(json, ctx.contextLoader);
1746
+ } catch (error) {
1747
+ if (isInvalidJsonLdError(error)) {
1748
+ logger.error("Failed to parse JSON-LD:\n{error}", {
1749
+ recipient,
1750
+ error
1751
+ });
1752
+ return new Response("Invalid JSON-LD.", {
1753
+ status: 400,
1754
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
1755
+ });
1756
+ }
1757
+ if (!canAttemptAlternateAuthAfterLdSignatureFailure) throw error;
1758
+ if (!skipSignatureVerification) deferredLdSignatureError = error;
1759
+ logger.debug("Failed to normalize JSON-LD for Linked Data Signatures; deferring to another authentication path only if it verifies:\n{error}", {
1603
1760
  recipient,
1604
1761
  error
1605
1762
  });
1606
- return new Response("Invalid JSON-LD.", {
1607
- status: 400,
1608
- headers: { "Content-Type": "text/plain; charset=utf-8" }
1609
- });
1610
1763
  }
1611
- ldSigVerified = false;
1764
+ if (compactedJson !== json) {
1765
+ compactedJsonWithoutSig = detachSignature(compactedJson);
1766
+ try {
1767
+ ldSigVerified = await verifyCompactJsonLd(compactedJson, {
1768
+ contextLoader: ctx.contextLoader,
1769
+ documentLoader: ctx.documentLoader,
1770
+ keyCache,
1771
+ tracerProvider
1772
+ });
1773
+ } catch (error) {
1774
+ if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(compactedJsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(error);
1775
+ if (isInvalidJsonLdError(error)) {
1776
+ logger.error("Failed to parse JSON-LD:\n{error}", {
1777
+ recipient,
1778
+ error
1779
+ });
1780
+ return new Response("Invalid JSON-LD.", {
1781
+ status: 400,
1782
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
1783
+ });
1784
+ }
1785
+ if (!canAttemptAlternateAuthAfterLdSignatureFailure) throw error;
1786
+ if (!skipSignatureVerification) try {
1787
+ await Object$1.fromJsonLd(compactedJson, {
1788
+ contextLoader: getNormalizationContextLoader(ctx.contextLoader),
1789
+ documentLoader: ctx.documentLoader,
1790
+ tracerProvider
1791
+ });
1792
+ } catch (parseError) {
1793
+ if (parseError instanceof RangeError && await hasMalformedKnownTemporalLiteral(compactedJsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(parseError);
1794
+ if (isInvalidJsonLdError(parseError)) {
1795
+ logger.error("Failed to parse JSON-LD:\n{error}", {
1796
+ recipient,
1797
+ error: parseError
1798
+ });
1799
+ return new Response("Invalid JSON-LD.", {
1800
+ status: 400,
1801
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
1802
+ });
1803
+ }
1804
+ deferredLdSignatureError = parseError;
1805
+ }
1806
+ ldSigVerified = false;
1807
+ }
1808
+ }
1612
1809
  }
1613
- const jsonWithoutSig = detachSignature(json);
1614
1810
  let activity = null;
1615
1811
  let activityVerified = false;
1616
1812
  if (ldSigVerified) {
@@ -1618,7 +1814,16 @@ async function handleInboxInternal(request, parameters, span) {
1618
1814
  recipient,
1619
1815
  json
1620
1816
  });
1621
- activity = await Activity.fromJsonLd(jsonWithoutSig, ctx);
1817
+ try {
1818
+ activity = await Activity.fromJsonLd(compactedJsonWithoutSig, {
1819
+ ...ctx,
1820
+ contextLoader: getNormalizationContextLoader(ctx.contextLoader)
1821
+ });
1822
+ } catch (error) {
1823
+ if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(compactedJsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(error);
1824
+ if (!isPermanentActivityParseError(error)) throw error;
1825
+ return await respondInvalidActivity(error);
1826
+ }
1622
1827
  activityVerified = true;
1623
1828
  } else {
1624
1829
  logger.debug("Linked Data Signatures are not verified.", {
@@ -1627,12 +1832,21 @@ async function handleInboxInternal(request, parameters, span) {
1627
1832
  });
1628
1833
  try {
1629
1834
  activity = await verifyObject(Activity, jsonWithoutSig, {
1630
- contextLoader: ctx.contextLoader,
1835
+ contextLoader: wrapContextLoaderForJsonLd(ctx.contextLoader),
1631
1836
  documentLoader: ctx.documentLoader,
1632
1837
  keyCache,
1633
1838
  tracerProvider
1634
1839
  });
1635
1840
  } catch (error) {
1841
+ if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(jsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(error);
1842
+ if (deferredLdSignatureError != null) {
1843
+ logger.debug("Object Integrity Proof fallback did not supersede a deferred Linked Data Signature failure:\n{error}", {
1844
+ recipient,
1845
+ error
1846
+ });
1847
+ activity = null;
1848
+ }
1849
+ if (!isPermanentActivityParseError(error)) throw error;
1636
1850
  logger.error("Failed to parse activity:\n{error}", {
1637
1851
  recipient,
1638
1852
  activity: json,
@@ -1680,6 +1894,7 @@ async function handleInboxInternal(request, parameters, span) {
1680
1894
  tracerProvider
1681
1895
  });
1682
1896
  if (verification.verified === false) {
1897
+ if (deferredLdSignatureError != null) throw deferredLdSignatureError;
1683
1898
  const reason = verification.reason;
1684
1899
  logger.error("Failed to verify the request's HTTP Signatures.", {
1685
1900
  recipient,
@@ -1755,7 +1970,15 @@ async function handleInboxInternal(request, parameters, span) {
1755
1970
  }
1756
1971
  httpSigKey = verification.key;
1757
1972
  }
1758
- activity = await Activity.fromJsonLd(jsonWithoutSig, ctx);
1973
+ try {
1974
+ activity = await Activity.fromJsonLd(jsonWithoutSig, {
1975
+ ...ctx,
1976
+ contextLoader: wrapContextLoaderForJsonLd(ctx.contextLoader)
1977
+ });
1978
+ } catch (error) {
1979
+ if (!isPermanentActivityParseError(error)) throw error;
1980
+ return await respondInvalidActivity(error);
1981
+ }
1759
1982
  }
1760
1983
  if (activity.id != null) span.setAttribute("activitypub.activity.id", activity.id.href);
1761
1984
  span.setAttribute("activitypub.activity.type", getTypeId(activity).href);
@@ -1767,6 +1990,7 @@ async function handleInboxInternal(request, parameters, span) {
1767
1990
  "http_signatures.key_id": httpSigKey?.id?.href ?? ""
1768
1991
  });
1769
1992
  if (httpSigKey != null && !await doesActorOwnKey(activity, httpSigKey, ctx)) {
1993
+ if (deferredLdSignatureError != null) throw deferredLdSignatureError;
1770
1994
  logger.error("The signer ({keyId}) and the actor ({actorId}) do not match.", {
1771
1995
  activity: json,
1772
1996
  recipient,
@@ -1791,10 +2015,14 @@ async function handleInboxInternal(request, parameters, span) {
1791
2015
  const routeResult = await routeActivity({
1792
2016
  context: ctx,
1793
2017
  json,
2018
+ originalJson: json,
2019
+ normalizedActivity: hasLdSignature && compactedJson !== json ? compactedJson : void 0,
2020
+ ldSignatureVerified: hasLdSignature ? ldSigVerified : void 0,
1794
2021
  activity,
1795
2022
  recipient,
1796
2023
  inboxListeners,
1797
2024
  inboxContextFactory,
2025
+ listenerInboxContextFactory: ldSigVerified ? inboxContextFactory[rawInboxContextFactorySymbol] : void 0,
1798
2026
  inboxErrorHandler,
1799
2027
  kv,
1800
2028
  kvPrefixes,
@@ -2718,6 +2946,18 @@ var middleware_exports = /* @__PURE__ */ __exportAll({
2718
2946
  OutboxContextImpl: () => OutboxContextImpl,
2719
2947
  createFederation: () => createFederation
2720
2948
  });
2949
+ function isRemoteContextLoadingFailure(error) {
2950
+ return error instanceof Error && typeof error.details === "object" && error.details != null && error.details.code === "loading remote context failed";
2951
+ }
2952
+ function isPermanentRemoteContextError(error) {
2953
+ if (!(error instanceof Error) || error.name !== "jsonld.InvalidUrl") return false;
2954
+ const details = error.details;
2955
+ if (details?.code === "invalid remote context") return true;
2956
+ return isRemoteContextLoadingFailure(error) && typeof details?.url === "string" && !URL.canParse(details.url) && isClearlyMalformedContextReference(details.url);
2957
+ }
2958
+ function isPermanentInboxParseError(error) {
2959
+ return error instanceof Error && (error.name === "UnsafeJsonLdError" || error instanceof InvalidContextReferenceError || isPermanentRemoteContextError(error) || error.name === "jsonld.SyntaxError" && !isRemoteContextLoadingFailure(error)) || error instanceof TypeError && (/^(Invalid JSON-LD:|Invalid type:|Unexpected type:)/.test(error.message) || isInvalidUrlTypeError(error));
2960
+ }
2721
2961
  /**
2722
2962
  * Create a new {@link Federation} instance.
2723
2963
  * @param parameters Parameters for initializing the instance.
@@ -3099,7 +3339,7 @@ var FederationImpl = class extends FederationBuilderImpl {
3099
3339
  }
3100
3340
  logger.info("Successfully sent activity {activityId} to {inbox}.", { ...logData });
3101
3341
  }
3102
- async #listenInboxMessage(ctxData, message, span) {
3342
+ async #listenInboxMessage(ctxData, message, _span) {
3103
3343
  const logger = getLogger([
3104
3344
  "fedify",
3105
3345
  "federation",
@@ -3112,60 +3352,28 @@ var FederationImpl = class extends FederationBuilderImpl {
3112
3352
  const identity = await this.sharedInboxKeyDispatcher(context);
3113
3353
  if (identity != null) context = this.#createContext(baseUrl, ctxData, { documentLoader: "identifier" in identity || "username" in identity ? await context.getDocumentLoader(identity) : context.getDocumentLoader(identity) });
3114
3354
  }
3115
- const activity = await Activity.fromJsonLd(message.activity, context);
3116
- span.setAttribute("activitypub.activity.type", getTypeId(activity).href);
3117
- if (activity.id != null) span.setAttribute("activitypub.activity.id", activity.id.href);
3118
- const cacheKey = activity.id == null ? null : [
3119
- ...this.kvPrefixes.activityIdempotence,
3120
- context.origin,
3121
- activity.id.href
3122
- ];
3123
- if (cacheKey != null) {
3124
- if (await this.kv.get(cacheKey) === true) {
3125
- logger.debug("Activity {activityId} has already been processed.", {
3126
- activityId: activity.id?.href,
3127
- activity: message.activity,
3128
- recipient: message.identifier
3129
- });
3130
- return;
3131
- }
3132
- }
3133
3355
  await this._getTracer().startActiveSpan("activitypub.dispatch_inbox_listener", { kind: SpanKind.INTERNAL }, async (span) => {
3134
- const dispatched = this.inboxListeners?.dispatchWithClass(activity);
3135
- if (dispatched == null) {
3136
- logger.error("Unsupported activity type:\n{activity}", {
3137
- activityId: activity.id?.href,
3138
- activity: message.activity,
3139
- recipient: message.identifier,
3140
- trial: message.attempt
3141
- });
3142
- span.setStatus({
3143
- code: SpanStatusCode.ERROR,
3144
- message: `Unsupported activity type: ${getTypeId(activity).href}`
3145
- });
3146
- span.end();
3147
- return;
3148
- }
3149
- const { class: cls, listener } = dispatched;
3150
- span.updateName(`activitypub.dispatch_inbox_listener ${cls.name}`);
3151
- try {
3152
- await listener(context.toInboxContext(message.identifier, message.activity, activity.id?.href, getTypeId(activity).href), activity);
3153
- } catch (error) {
3356
+ let activity = null;
3357
+ let cacheKey = null;
3358
+ const reportInboxError = async (error) => {
3154
3359
  try {
3155
3360
  await this.inboxErrorHandler?.(context, error);
3156
3361
  } catch (error) {
3157
3362
  logger.error("An unexpected error occurred in inbox error handler:\n{error}", {
3158
3363
  error,
3159
3364
  trial: message.attempt,
3160
- activityId: activity.id?.href,
3365
+ activityId: activity?.id?.href,
3161
3366
  activity: message.activity,
3162
3367
  recipient: message.identifier
3163
3368
  });
3164
3369
  }
3370
+ };
3371
+ const handleRetriableFailure = async (error) => {
3372
+ await reportInboxError(error);
3165
3373
  if (this.inboxQueue?.nativeRetrial) {
3166
3374
  logger.error("Failed to process the incoming activity {activityId}; backend will handle retry:\n{error}", {
3167
3375
  error,
3168
- activityId: activity.id?.href,
3376
+ activityId: activity?.id?.href,
3169
3377
  activity: message.activity,
3170
3378
  recipient: message.identifier
3171
3379
  });
@@ -3184,17 +3392,25 @@ var FederationImpl = class extends FederationBuilderImpl {
3184
3392
  logger.error("Failed to process the incoming activity {activityId} (attempt #{attempt}); retry...:\n{error}", {
3185
3393
  error,
3186
3394
  attempt: message.attempt,
3187
- activityId: activity.id?.href,
3395
+ activityId: activity?.id?.href,
3188
3396
  activity: message.activity,
3189
3397
  recipient: message.identifier
3190
3398
  });
3399
+ if (this.inboxQueue == null) {
3400
+ span.setStatus({
3401
+ code: SpanStatusCode.ERROR,
3402
+ message: String(error)
3403
+ });
3404
+ span.end();
3405
+ throw error;
3406
+ }
3191
3407
  await this.inboxQueue?.enqueue({
3192
3408
  ...message,
3193
3409
  attempt: message.attempt + 1
3194
3410
  }, { delay: Temporal.Duration.compare(delay, { seconds: 0 }) < 0 ? Temporal.Duration.from({ seconds: 0 }) : delay });
3195
3411
  } else logger.error("Failed to process the incoming activity {activityId} after {trial} attempts; giving up:\n{error}", {
3196
3412
  error,
3197
- activityId: activity.id?.href,
3413
+ activityId: activity?.id?.href,
3198
3414
  activity: message.activity,
3199
3415
  recipient: message.identifier
3200
3416
  });
@@ -3203,11 +3419,108 @@ var FederationImpl = class extends FederationBuilderImpl {
3203
3419
  message: String(error)
3204
3420
  });
3205
3421
  span.end();
3422
+ };
3423
+ let dispatched;
3424
+ let parseInput = void 0;
3425
+ let parseContextLoader = context.contextLoader;
3426
+ try {
3427
+ const hasSignatureField = hasSignature(message.activity);
3428
+ const shouldParseFromNormalizedSignedPayload = message.ldSignatureVerified === true || message.normalizedActivity != null || message.ldSignatureVerified == null && hasSignatureField;
3429
+ const parseContext = hasSignatureField ? {
3430
+ ...context,
3431
+ contextLoader: getNormalizationContextLoader(context.contextLoader)
3432
+ } : {
3433
+ ...context,
3434
+ contextLoader: wrapContextLoaderForJsonLd(context.contextLoader)
3435
+ };
3436
+ parseContextLoader = parseContext.contextLoader;
3437
+ let normalizedActivity;
3438
+ if (shouldParseFromNormalizedSignedPayload) {
3439
+ normalizedActivity = message.normalizedActivity ?? await compactJsonLd(message.activity, context.contextLoader);
3440
+ assertSafeJsonLd(normalizedActivity);
3441
+ }
3442
+ parseInput = shouldParseFromNormalizedSignedPayload ? detachSignature(normalizedActivity) : hasSignatureField ? detachSignature(message.activity) : message.activity;
3443
+ activity = await Activity.fromJsonLd(parseInput, parseContext);
3444
+ span.setAttribute("activitypub.activity.type", getTypeId(activity).href);
3445
+ if (activity.id != null) span.setAttribute("activitypub.activity.id", activity.id.href);
3446
+ cacheKey = activity.id == null ? null : [
3447
+ ...this.kvPrefixes.activityIdempotence,
3448
+ context.origin,
3449
+ activity.id.href
3450
+ ];
3451
+ if (cacheKey != null) {
3452
+ if (await this.kv.get(cacheKey) === true) {
3453
+ logger.debug("Activity {activityId} has already been processed.", {
3454
+ activityId: activity.id?.href,
3455
+ activity: message.activity,
3456
+ recipient: message.identifier
3457
+ });
3458
+ span.end();
3459
+ return;
3460
+ }
3461
+ }
3462
+ dispatched = this.inboxListeners?.dispatchWithClass(activity);
3463
+ } catch (error) {
3464
+ if (activity == null && error instanceof RangeError && await hasMalformedKnownTemporalLiteral(parseInput, parseContextLoader)) {
3465
+ await reportInboxError(error);
3466
+ logger.error("Failed to parse the queued incoming activity {activityId}:\n{error}", {
3467
+ error,
3468
+ trial: message.attempt,
3469
+ activityId: null,
3470
+ activity: message.activity,
3471
+ recipient: message.identifier
3472
+ });
3473
+ span.setStatus({
3474
+ code: SpanStatusCode.ERROR,
3475
+ message: String(error)
3476
+ });
3477
+ span.end();
3478
+ return;
3479
+ }
3480
+ if (isPermanentInboxParseError(error)) {
3481
+ await reportInboxError(error);
3482
+ logger.error("Failed to parse the queued incoming activity {activityId}:\n{error}", {
3483
+ error,
3484
+ trial: message.attempt,
3485
+ activityId: activity?.id?.href,
3486
+ activity: message.activity,
3487
+ recipient: message.identifier
3488
+ });
3489
+ span.setStatus({
3490
+ code: SpanStatusCode.ERROR,
3491
+ message: String(error)
3492
+ });
3493
+ span.end();
3494
+ return;
3495
+ }
3496
+ await handleRetriableFailure(error);
3497
+ return;
3498
+ }
3499
+ if (dispatched == null) {
3500
+ logger.error("Unsupported activity type:\n{activity}", {
3501
+ activityId: activity.id?.href,
3502
+ activity: message.activity,
3503
+ recipient: message.identifier,
3504
+ trial: message.attempt
3505
+ });
3506
+ span.setStatus({
3507
+ code: SpanStatusCode.ERROR,
3508
+ message: `Unsupported activity type: ${getTypeId(activity).href}`
3509
+ });
3510
+ span.end();
3511
+ return;
3512
+ }
3513
+ const { class: cls, listener } = dispatched;
3514
+ span.updateName(`activitypub.dispatch_inbox_listener ${cls.name}`);
3515
+ try {
3516
+ await listener(context.toInboxContext(message.identifier, message.activity, activity.id?.href, getTypeId(activity).href), activity);
3517
+ } catch (error) {
3518
+ await handleRetriableFailure(error);
3206
3519
  return;
3207
3520
  }
3208
3521
  if (cacheKey != null) await this.kv.set(cacheKey, true, { ttl: Temporal.Duration.from({ days: 1 }) });
3209
3522
  logger.info("Activity {activityId} has been processed.", {
3210
- activityId: activity.id?.href,
3523
+ activityId: activity?.id?.href,
3211
3524
  activity: message.activity,
3212
3525
  recipient: message.identifier
3213
3526
  });
@@ -3577,16 +3890,18 @@ var FederationImpl = class extends FederationBuilderImpl {
3577
3890
  onNotFound
3578
3891
  });
3579
3892
  context = this.#createContext(request, contextData, { documentLoader: await context.getDocumentLoader({ identifier: route.values.identifier }) });
3580
- case "sharedInbox":
3893
+ case "sharedInbox": {
3581
3894
  if (routeName !== "inbox" && this.sharedInboxKeyDispatcher != null) {
3582
3895
  const identity = await this.sharedInboxKeyDispatcher(context);
3583
3896
  if (identity != null) context = this.#createContext(request, contextData, { documentLoader: "identifier" in identity || "username" in identity ? await context.getDocumentLoader(identity) : context.getDocumentLoader(identity) });
3584
3897
  }
3585
3898
  if (!this.manuallyStartQueue) this._startQueueInternal(contextData);
3899
+ const inboxContextFactory = context.toInboxContext.bind(context);
3900
+ inboxContextFactory[rawInboxContextFactorySymbol] = context.toInboxContext.bind(context);
3586
3901
  return await handleInbox(request, {
3587
3902
  recipient: route.values.identifier ?? null,
3588
3903
  context,
3589
- inboxContextFactory: context.toInboxContext.bind(context),
3904
+ inboxContextFactory,
3590
3905
  kv: this.kv,
3591
3906
  kvPrefixes: this.kvPrefixes,
3592
3907
  queue: this.inboxQueue,
@@ -3601,6 +3916,7 @@ var FederationImpl = class extends FederationBuilderImpl {
3601
3916
  tracerProvider: this.tracerProvider,
3602
3917
  idempotencyStrategy: this.idempotencyStrategy
3603
3918
  });
3919
+ }
3604
3920
  case "following": return await handleCollection(request, {
3605
3921
  name: "following",
3606
3922
  identifier: route.values.identifier,
@@ -4301,6 +4617,7 @@ var ContextImpl = class ContextImpl {
4301
4617
  const routeResult = await routeActivity({
4302
4618
  context: this,
4303
4619
  json,
4620
+ ldSignatureVerified: false,
4304
4621
  activity,
4305
4622
  recipient,
4306
4623
  inboxListeners: this.federation.inboxListeners,
@@ -4581,6 +4898,14 @@ async function forwardActivityInternal(ctx, loggerCategory, forwarder, recipient
4581
4898
  }
4582
4899
  var InboxContextImpl = class InboxContextImpl extends ContextImpl {
4583
4900
  recipient;
4901
+ /**
4902
+ * The original received activity payload.
4903
+ *
4904
+ * Fedify may normalize a Linked Data Signature payload internally for safe
4905
+ * parsing, but forwarding must keep the sender's payload unchanged so
4906
+ * third-party signatures/proofs remain intact.
4907
+ * @internal
4908
+ */
4584
4909
  activity;
4585
4910
  activityId;
4586
4911
  activityType;