@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
@@ -2,17 +2,18 @@ import { Temporal } from "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  import { t as __exportAll } from "./rolldown-runtime-C7fyD9f2.js";
4
4
  import { r as getDefaultActivityTransformers } from "./transformers-BV4OeK9o.js";
5
- import { d as name, f as version, i as getDocumentLoader, s as kvCache } from "./docloader-ivt5xNwJ.js";
6
- import { G as Multikey, Tt as getTypeId, V as Link, X as OrderedCollectionPage, Y as OrderedCollection, _ as CollectionPage, g as Collection, q as Object$1, s as Activity, y as CryptographicKey } from "./actor-MDInzRdc.js";
7
- import { t as lookupWebFinger } from "./lookup-BOFQeNi_.js";
8
- import { i as importJwk, o as validateCryptoKey, t as exportJwk } from "./key-C2e_Yz1s.js";
9
- import { r as verifyRequest, t as doubleKnock } from "./http-Dbj7ZT3b.js";
10
- import { a as doesActorOwnKey, d as signJsonLd, f as verifyJsonLd, l as detachSignature, n as signObject, o as getKeyOwner, r as verifyObject, u as hasSignature } from "./proof-C99DO0SE.js";
11
- import { n as getNodeInfo, t as nodeInfoToJson } from "./types-dHLDn_7H.js";
12
- import { t as getAuthenticatedDocumentLoader } from "./authdocloader-DRG9qnz7.js";
5
+ import { d as name, f as version, i as getDocumentLoader, s as kvCache } from "./docloader-XzYOPh6W.js";
6
+ import { G as Multikey, Tt as getTypeId, V as Link, X as OrderedCollectionPage, Y as OrderedCollection, _ as CollectionPage, g as Collection, q as Object$1, s as Activity, y as CryptographicKey } from "./actor-I6MTQt65.js";
7
+ import { t as lookupWebFinger } from "./lookup-DDOc-i0I.js";
8
+ import { i as importJwk, o as validateCryptoKey, t as exportJwk } from "./key-DtaAm2V1.js";
9
+ import { r as verifyRequest, t as doubleKnock } from "./http-CXpt61mo.js";
10
+ import { _ as signJsonLd, a as doesActorOwnKey, c as assertSafeJsonLd, f as detachSignature, g as isInvalidUrlTypeError, h as isClearlyMalformedContextReference, m as hasSignature, n as signObject, o as getKeyOwner, p as getNormalizationContextLoader, r as verifyObject, s as InvalidContextReferenceError, u as compactJsonLd, v as verifyCompactJsonLd, x as wrapContextLoaderForJsonLd } from "./proof-CjgdsMq_.js";
11
+ import { n as getNodeInfo, t as nodeInfoToJson } from "./types-CRRb96yX.js";
12
+ import { t as getAuthenticatedDocumentLoader } from "./authdocloader-DGVNFUsJ.js";
13
13
  import { lookupObject, traverseCollection } from "./vocab/mod.js";
14
14
  import { getLogger, withContext } from "@logtape/logtape";
15
15
  import { SpanKind, SpanStatusCode, context, propagation, trace } from "@opentelemetry/api";
16
+ import jsonld from "jsonld";
16
17
  import { encodeHex } from "byte-encodings/hex";
17
18
  import { cloneDeep } from "es-toolkit";
18
19
  import { Router } from "uri-template-router";
@@ -54,7 +55,7 @@ var InboxListenerSet = class InboxListenerSet {
54
55
  }
55
56
  };
56
57
  let warnedAboutDefaultIdempotency = false;
57
- 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 }) {
58
59
  const logger = getLogger([
59
60
  "fedify",
60
61
  "federation",
@@ -115,7 +116,9 @@ async function routeActivity({ context: ctx, json, activity, recipient, inboxLis
115
116
  type: "inbox",
116
117
  id: crypto.randomUUID(),
117
118
  baseUrl: ctx.origin,
118
- activity: json,
119
+ activity: originalJson ?? json,
120
+ ...normalizedActivity == null ? {} : { normalizedActivity },
121
+ ...ldSignatureVerified == null ? {} : { ldSignatureVerified },
119
122
  identifier: recipient,
120
123
  attempt: 0,
121
124
  started: (/* @__PURE__ */ new Date()).toISOString(),
@@ -159,7 +162,8 @@ async function routeActivity({ context: ctx, json, activity, recipient, inboxLis
159
162
  const { class: cls, listener } = dispatched;
160
163
  span.updateName(`activitypub.dispatch_inbox_listener ${cls.name}`);
161
164
  try {
162
- await listener(inboxContextFactory(recipient, json, activity?.id?.href, getTypeId(activity).href), activity);
165
+ const contextFactory = listenerInboxContextFactory ?? inboxContextFactory;
166
+ await listener(contextFactory(recipient, contextFactory === inboxContextFactory ? json : originalJson ?? json, activity?.id?.href, getTypeId(activity).href), activity);
163
167
  } catch (error) {
164
168
  try {
165
169
  await inboxErrorHandler?.(ctx, error);
@@ -999,7 +1003,123 @@ function preferredMediaTypes(accept) {
999
1003
  return parseAccept(accept === void 0 ? "*/*" : accept ?? "").filter(isQuality).sort(compareSpecs).map(getFullType);
1000
1004
  }
1001
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.expand(value, {
1087
+ documentLoader: getNormalizationContextLoader(contextLoader),
1088
+ keepFreeFloatingNodes: true
1089
+ }), /* @__PURE__ */ new Set());
1090
+ } catch {
1091
+ return false;
1092
+ }
1093
+ }
1094
+ //#endregion
1002
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) && 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 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) || 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
+ }
1003
1123
  function acceptsJsonLd(request) {
1004
1124
  const accept = request.headers.get("Accept");
1005
1125
  const types = accept ? preferredMediaTypes(accept) : ["*/*"];
@@ -1383,35 +1503,120 @@ async function handleInboxInternal(request, parameters, span) {
1383
1503
  });
1384
1504
  }
1385
1505
  const keyCache = new KvKeyCache(kv, kvPrefixes.publicKey, ctx);
1386
- let ldSigVerified;
1387
- try {
1388
- ldSigVerified = await verifyJsonLd(json, {
1389
- contextLoader: ctx.contextLoader,
1390
- documentLoader: ctx.documentLoader,
1391
- keyCache,
1392
- tracerProvider
1506
+ const jsonWithoutSig = detachSignature(json);
1507
+ const hasLdSignature = 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
1393
1515
  });
1394
- } catch (error) {
1395
- if (error instanceof Error && error.name === "jsonld.SyntaxError") {
1396
- 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: 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 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}", {
1397
1554
  recipient,
1398
1555
  error
1399
1556
  });
1400
- return new Response("Invalid JSON-LD.", {
1401
- status: 400,
1402
- headers: { "Content-Type": "text/plain; charset=utf-8" }
1403
- });
1404
1557
  }
1405
- ldSigVerified = false;
1558
+ if (compactedJson !== json) {
1559
+ compactedJsonWithoutSig = detachSignature(compactedJson);
1560
+ try {
1561
+ ldSigVerified = await 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 Object$1.fromJsonLd(compactedJson, {
1582
+ contextLoader: 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
+ }
1406
1603
  }
1407
- const jsonWithoutSig = detachSignature(json);
1408
1604
  let activity = null;
1409
1605
  if (ldSigVerified) {
1410
1606
  logger.debug("Linked Data Signatures are verified.", {
1411
1607
  recipient,
1412
1608
  json
1413
1609
  });
1414
- activity = await Activity.fromJsonLd(jsonWithoutSig, ctx);
1610
+ try {
1611
+ activity = await Activity.fromJsonLd(compactedJsonWithoutSig, {
1612
+ ...ctx,
1613
+ contextLoader: 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
+ }
1415
1620
  } else {
1416
1621
  logger.debug("Linked Data Signatures are not verified.", {
1417
1622
  recipient,
@@ -1419,12 +1624,21 @@ async function handleInboxInternal(request, parameters, span) {
1419
1624
  });
1420
1625
  try {
1421
1626
  activity = await verifyObject(Activity, jsonWithoutSig, {
1422
- contextLoader: ctx.contextLoader,
1627
+ contextLoader: wrapContextLoaderForJsonLd(ctx.contextLoader),
1423
1628
  documentLoader: ctx.documentLoader,
1424
1629
  keyCache,
1425
1630
  tracerProvider
1426
1631
  });
1427
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;
1428
1642
  logger.error("Failed to parse activity:\n{error}", {
1429
1643
  recipient,
1430
1644
  activity: json,
@@ -1468,6 +1682,7 @@ async function handleInboxInternal(request, parameters, span) {
1468
1682
  tracerProvider
1469
1683
  });
1470
1684
  if (key == null) {
1685
+ if (deferredLdSignatureError != null) throw deferredLdSignatureError;
1471
1686
  logger.error("Failed to verify the request's HTTP Signatures.", { recipient });
1472
1687
  span.setStatus({
1473
1688
  code: SpanStatusCode.ERROR,
@@ -1480,11 +1695,20 @@ async function handleInboxInternal(request, parameters, span) {
1480
1695
  } else logger.debug("HTTP Signatures are verified.", { recipient });
1481
1696
  httpSigKey = key;
1482
1697
  }
1483
- activity = await Activity.fromJsonLd(jsonWithoutSig, ctx);
1698
+ try {
1699
+ activity = await Activity.fromJsonLd(jsonWithoutSig, {
1700
+ ...ctx,
1701
+ contextLoader: wrapContextLoaderForJsonLd(ctx.contextLoader)
1702
+ });
1703
+ } catch (error) {
1704
+ if (!isPermanentActivityParseError(error)) throw error;
1705
+ return await respondInvalidActivity(error);
1706
+ }
1484
1707
  }
1485
1708
  if (activity.id != null) span.setAttribute("activitypub.activity.id", activity.id.href);
1486
1709
  span.setAttribute("activitypub.activity.type", getTypeId(activity).href);
1487
1710
  if (httpSigKey != null && !await doesActorOwnKey(activity, httpSigKey, ctx)) {
1711
+ if (deferredLdSignatureError != null) throw deferredLdSignatureError;
1488
1712
  logger.error("The signer ({keyId}) and the actor ({actorId}) do not match.", {
1489
1713
  activity: json,
1490
1714
  recipient,
@@ -1503,10 +1727,14 @@ async function handleInboxInternal(request, parameters, span) {
1503
1727
  const routeResult = await routeActivity({
1504
1728
  context: ctx,
1505
1729
  json,
1730
+ originalJson: json,
1731
+ normalizedActivity: hasLdSignature && compactedJson !== json ? compactedJson : void 0,
1732
+ ldSignatureVerified: hasLdSignature ? ldSigVerified : void 0,
1506
1733
  activity,
1507
1734
  recipient,
1508
1735
  inboxListeners,
1509
1736
  inboxContextFactory,
1737
+ listenerInboxContextFactory: ldSigVerified ? inboxContextFactory[rawInboxContextFactorySymbol] : void 0,
1510
1738
  inboxErrorHandler,
1511
1739
  kv,
1512
1740
  kvPrefixes,
@@ -2325,6 +2553,18 @@ var middleware_exports = /* @__PURE__ */ __exportAll({
2325
2553
  KvSpecDeterminer: () => KvSpecDeterminer,
2326
2554
  createFederation: () => createFederation
2327
2555
  });
2556
+ function isRemoteContextLoadingFailure(error) {
2557
+ return error instanceof Error && typeof error.details === "object" && error.details != null && error.details.code === "loading remote context failed";
2558
+ }
2559
+ function isPermanentRemoteContextError(error) {
2560
+ if (!(error instanceof Error) || error.name !== "jsonld.InvalidUrl") return false;
2561
+ const details = error.details;
2562
+ if (details?.code === "invalid remote context") return true;
2563
+ return isRemoteContextLoadingFailure(error) && typeof details?.url === "string" && !URL.canParse(details.url) && isClearlyMalformedContextReference(details.url);
2564
+ }
2565
+ function isPermanentInboxParseError(error) {
2566
+ 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));
2567
+ }
2328
2568
  /**
2329
2569
  * Create a new {@link Federation} instance.
2330
2570
  * @param parameters Parameters for initializing the instance.
@@ -2656,7 +2896,7 @@ var FederationImpl = class extends FederationBuilderImpl {
2656
2896
  }
2657
2897
  logger.info("Successfully sent activity {activityId} to {inbox}.", { ...logData });
2658
2898
  }
2659
- async #listenInboxMessage(ctxData, message, span) {
2899
+ async #listenInboxMessage(ctxData, message, _span) {
2660
2900
  const logger = getLogger([
2661
2901
  "fedify",
2662
2902
  "federation",
@@ -2669,60 +2909,28 @@ var FederationImpl = class extends FederationBuilderImpl {
2669
2909
  const identity = await this.sharedInboxKeyDispatcher(context);
2670
2910
  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) });
2671
2911
  }
2672
- const activity = await Activity.fromJsonLd(message.activity, context);
2673
- span.setAttribute("activitypub.activity.type", getTypeId(activity).href);
2674
- if (activity.id != null) span.setAttribute("activitypub.activity.id", activity.id.href);
2675
- const cacheKey = activity.id == null ? null : [
2676
- ...this.kvPrefixes.activityIdempotence,
2677
- context.origin,
2678
- activity.id.href
2679
- ];
2680
- if (cacheKey != null) {
2681
- if (await this.kv.get(cacheKey) === true) {
2682
- logger.debug("Activity {activityId} has already been processed.", {
2683
- activityId: activity.id?.href,
2684
- activity: message.activity,
2685
- recipient: message.identifier
2686
- });
2687
- return;
2688
- }
2689
- }
2690
2912
  await this._getTracer().startActiveSpan("activitypub.dispatch_inbox_listener", { kind: SpanKind.INTERNAL }, async (span) => {
2691
- const dispatched = this.inboxListeners?.dispatchWithClass(activity);
2692
- if (dispatched == null) {
2693
- logger.error("Unsupported activity type:\n{activity}", {
2694
- activityId: activity.id?.href,
2695
- activity: message.activity,
2696
- recipient: message.identifier,
2697
- trial: message.attempt
2698
- });
2699
- span.setStatus({
2700
- code: SpanStatusCode.ERROR,
2701
- message: `Unsupported activity type: ${getTypeId(activity).href}`
2702
- });
2703
- span.end();
2704
- return;
2705
- }
2706
- const { class: cls, listener } = dispatched;
2707
- span.updateName(`activitypub.dispatch_inbox_listener ${cls.name}`);
2708
- try {
2709
- await listener(context.toInboxContext(message.identifier, message.activity, activity.id?.href, getTypeId(activity).href), activity);
2710
- } catch (error) {
2913
+ let activity = null;
2914
+ let cacheKey = null;
2915
+ const reportInboxError = async (error) => {
2711
2916
  try {
2712
2917
  await this.inboxErrorHandler?.(context, error);
2713
2918
  } catch (error) {
2714
2919
  logger.error("An unexpected error occurred in inbox error handler:\n{error}", {
2715
2920
  error,
2716
2921
  trial: message.attempt,
2717
- activityId: activity.id?.href,
2922
+ activityId: activity?.id?.href,
2718
2923
  activity: message.activity,
2719
2924
  recipient: message.identifier
2720
2925
  });
2721
2926
  }
2927
+ };
2928
+ const handleRetriableFailure = async (error) => {
2929
+ await reportInboxError(error);
2722
2930
  if (this.inboxQueue?.nativeRetrial) {
2723
2931
  logger.error("Failed to process the incoming activity {activityId}; backend will handle retry:\n{error}", {
2724
2932
  error,
2725
- activityId: activity.id?.href,
2933
+ activityId: activity?.id?.href,
2726
2934
  activity: message.activity,
2727
2935
  recipient: message.identifier
2728
2936
  });
@@ -2741,17 +2949,25 @@ var FederationImpl = class extends FederationBuilderImpl {
2741
2949
  logger.error("Failed to process the incoming activity {activityId} (attempt #{attempt}); retry...:\n{error}", {
2742
2950
  error,
2743
2951
  attempt: message.attempt,
2744
- activityId: activity.id?.href,
2952
+ activityId: activity?.id?.href,
2745
2953
  activity: message.activity,
2746
2954
  recipient: message.identifier
2747
2955
  });
2956
+ if (this.inboxQueue == null) {
2957
+ span.setStatus({
2958
+ code: SpanStatusCode.ERROR,
2959
+ message: String(error)
2960
+ });
2961
+ span.end();
2962
+ throw error;
2963
+ }
2748
2964
  await this.inboxQueue?.enqueue({
2749
2965
  ...message,
2750
2966
  attempt: message.attempt + 1
2751
2967
  }, { delay: Temporal.Duration.compare(delay, { seconds: 0 }) < 0 ? Temporal.Duration.from({ seconds: 0 }) : delay });
2752
2968
  } else logger.error("Failed to process the incoming activity {activityId} after {trial} attempts; giving up:\n{error}", {
2753
2969
  error,
2754
- activityId: activity.id?.href,
2970
+ activityId: activity?.id?.href,
2755
2971
  activity: message.activity,
2756
2972
  recipient: message.identifier
2757
2973
  });
@@ -2760,11 +2976,108 @@ var FederationImpl = class extends FederationBuilderImpl {
2760
2976
  message: String(error)
2761
2977
  });
2762
2978
  span.end();
2979
+ };
2980
+ let dispatched;
2981
+ let parseInput = void 0;
2982
+ let parseContextLoader = context.contextLoader;
2983
+ try {
2984
+ const hasSignatureField = hasSignature(message.activity);
2985
+ const shouldParseFromNormalizedSignedPayload = message.ldSignatureVerified === true || message.normalizedActivity != null || message.ldSignatureVerified == null && hasSignatureField;
2986
+ const parseContext = hasSignatureField ? {
2987
+ ...context,
2988
+ contextLoader: getNormalizationContextLoader(context.contextLoader)
2989
+ } : {
2990
+ ...context,
2991
+ contextLoader: wrapContextLoaderForJsonLd(context.contextLoader)
2992
+ };
2993
+ parseContextLoader = parseContext.contextLoader;
2994
+ let normalizedActivity;
2995
+ if (shouldParseFromNormalizedSignedPayload) {
2996
+ normalizedActivity = message.normalizedActivity ?? await compactJsonLd(message.activity, context.contextLoader);
2997
+ assertSafeJsonLd(normalizedActivity);
2998
+ }
2999
+ parseInput = shouldParseFromNormalizedSignedPayload ? detachSignature(normalizedActivity) : hasSignatureField ? detachSignature(message.activity) : message.activity;
3000
+ activity = await Activity.fromJsonLd(parseInput, parseContext);
3001
+ span.setAttribute("activitypub.activity.type", getTypeId(activity).href);
3002
+ if (activity.id != null) span.setAttribute("activitypub.activity.id", activity.id.href);
3003
+ cacheKey = activity.id == null ? null : [
3004
+ ...this.kvPrefixes.activityIdempotence,
3005
+ context.origin,
3006
+ activity.id.href
3007
+ ];
3008
+ if (cacheKey != null) {
3009
+ if (await this.kv.get(cacheKey) === true) {
3010
+ logger.debug("Activity {activityId} has already been processed.", {
3011
+ activityId: activity.id?.href,
3012
+ activity: message.activity,
3013
+ recipient: message.identifier
3014
+ });
3015
+ span.end();
3016
+ return;
3017
+ }
3018
+ }
3019
+ dispatched = this.inboxListeners?.dispatchWithClass(activity);
3020
+ } catch (error) {
3021
+ if (activity == null && error instanceof RangeError && await hasMalformedKnownTemporalLiteral(parseInput, parseContextLoader)) {
3022
+ await reportInboxError(error);
3023
+ logger.error("Failed to parse the queued incoming activity {activityId}:\n{error}", {
3024
+ error,
3025
+ trial: message.attempt,
3026
+ activityId: null,
3027
+ activity: message.activity,
3028
+ recipient: message.identifier
3029
+ });
3030
+ span.setStatus({
3031
+ code: SpanStatusCode.ERROR,
3032
+ message: String(error)
3033
+ });
3034
+ span.end();
3035
+ return;
3036
+ }
3037
+ if (isPermanentInboxParseError(error)) {
3038
+ await reportInboxError(error);
3039
+ logger.error("Failed to parse the queued incoming activity {activityId}:\n{error}", {
3040
+ error,
3041
+ trial: message.attempt,
3042
+ activityId: activity?.id?.href,
3043
+ activity: message.activity,
3044
+ recipient: message.identifier
3045
+ });
3046
+ span.setStatus({
3047
+ code: SpanStatusCode.ERROR,
3048
+ message: String(error)
3049
+ });
3050
+ span.end();
3051
+ return;
3052
+ }
3053
+ await handleRetriableFailure(error);
3054
+ return;
3055
+ }
3056
+ if (dispatched == null) {
3057
+ logger.error("Unsupported activity type:\n{activity}", {
3058
+ activityId: activity.id?.href,
3059
+ activity: message.activity,
3060
+ recipient: message.identifier,
3061
+ trial: message.attempt
3062
+ });
3063
+ span.setStatus({
3064
+ code: SpanStatusCode.ERROR,
3065
+ message: `Unsupported activity type: ${getTypeId(activity).href}`
3066
+ });
3067
+ span.end();
3068
+ return;
3069
+ }
3070
+ const { class: cls, listener } = dispatched;
3071
+ span.updateName(`activitypub.dispatch_inbox_listener ${cls.name}`);
3072
+ try {
3073
+ await listener(context.toInboxContext(message.identifier, message.activity, activity.id?.href, getTypeId(activity).href), activity);
3074
+ } catch (error) {
3075
+ await handleRetriableFailure(error);
2763
3076
  return;
2764
3077
  }
2765
3078
  if (cacheKey != null) await this.kv.set(cacheKey, true, { ttl: Temporal.Duration.from({ days: 1 }) });
2766
3079
  logger.info("Activity {activityId} has been processed.", {
2767
- activityId: activity.id?.href,
3080
+ activityId: activity?.id?.href,
2768
3081
  activity: message.activity,
2769
3082
  recipient: message.identifier
2770
3083
  });
@@ -3086,16 +3399,18 @@ var FederationImpl = class extends FederationBuilderImpl {
3086
3399
  onNotAcceptable
3087
3400
  });
3088
3401
  context = this.#createContext(request, contextData, { documentLoader: await context.getDocumentLoader({ identifier: route.values.identifier ?? route.values.handle }) });
3089
- case "sharedInbox":
3402
+ case "sharedInbox": {
3090
3403
  if (routeName !== "inbox" && this.sharedInboxKeyDispatcher != null) {
3091
3404
  const identity = await this.sharedInboxKeyDispatcher(context);
3092
3405
  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) });
3093
3406
  }
3094
3407
  if (!this.manuallyStartQueue) this._startQueueInternal(contextData);
3408
+ const inboxContextFactory = context.toInboxContext.bind(context);
3409
+ inboxContextFactory[rawInboxContextFactorySymbol] = context.toInboxContext.bind(context);
3095
3410
  return await handleInbox(request, {
3096
3411
  recipient: route.values.identifier ?? route.values.handle ?? null,
3097
3412
  context,
3098
- inboxContextFactory: context.toInboxContext.bind(context),
3413
+ inboxContextFactory,
3099
3414
  kv: this.kv,
3100
3415
  kvPrefixes: this.kvPrefixes,
3101
3416
  queue: this.inboxQueue,
@@ -3108,6 +3423,7 @@ var FederationImpl = class extends FederationBuilderImpl {
3108
3423
  tracerProvider: this.tracerProvider,
3109
3424
  idempotencyStrategy: this.idempotencyStrategy
3110
3425
  });
3426
+ }
3111
3427
  case "following": return await handleCollection(request, {
3112
3428
  name: "following",
3113
3429
  identifier: route.values.identifier ?? route.values.handle,
@@ -3858,6 +4174,7 @@ var ContextImpl = class ContextImpl {
3858
4174
  const routeResult = await routeActivity({
3859
4175
  context: this,
3860
4176
  json,
4177
+ ldSignatureVerified: false,
3861
4178
  activity,
3862
4179
  recipient,
3863
4180
  inboxListeners: this.federation.inboxListeners,
@@ -3956,6 +4273,14 @@ var RequestContextImpl = class RequestContextImpl extends ContextImpl {
3956
4273
  };
3957
4274
  var InboxContextImpl = class InboxContextImpl extends ContextImpl {
3958
4275
  recipient;
4276
+ /**
4277
+ * The original received activity payload.
4278
+ *
4279
+ * Fedify may normalize a Linked Data Signature payload internally for safe
4280
+ * parsing, but forwarding must keep the sender's payload unchanged so
4281
+ * third-party signatures/proofs remain intact.
4282
+ * @internal
4283
+ */
3959
4284
  activity;
3960
4285
  activityId;
3961
4286
  activityType;