@fedify/fedify 2.1.12 → 2.1.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/dist/{builder-D9hlyCx5.mjs → builder-BBifZZ5G.mjs} +3 -3
  2. package/dist/{chunk-nlSIicah.js → chunk-CRNNMoPX.js} +2 -2
  3. package/dist/{client-z-8dc-e1.d.cts → client-CAM_bQXx.d.cts} +1 -0
  4. package/dist/{client-AtlibPOU.d.ts → client-CSddvgWN.d.ts} +1 -2
  5. package/dist/compat/mod.d.cts +2 -1
  6. package/dist/compat/mod.d.ts +2 -3
  7. package/dist/compat/mod.js +3 -3
  8. package/dist/compat/transformers.test.mjs +1 -1
  9. package/dist/{context-C0fssarc.d.ts → context-_l9t359k.d.ts} +53 -12
  10. package/dist/{context-LmrZPwti.d.cts → context-o22vjzS2.d.cts} +53 -10
  11. package/dist/{deno-Ddb3P7G7.mjs → deno-bqnW1QlD.mjs} +1 -1
  12. package/dist/{docloader-CK8tewt1.mjs → docloader-D-Fu0kBF.mjs} +2 -2
  13. package/dist/federation/builder.test.mjs +1 -1
  14. package/dist/federation/handler.test.mjs +1361 -41
  15. package/dist/federation/idempotency.test.mjs +2 -2
  16. package/dist/federation/inbox.test.mjs +1 -1
  17. package/dist/federation/middleware.test.mjs +1588 -84
  18. package/dist/federation/mod.cjs +1 -1
  19. package/dist/federation/mod.d.cts +4 -3
  20. package/dist/federation/mod.d.ts +4 -5
  21. package/dist/federation/mod.js +2 -2
  22. package/dist/federation/retry.test.mjs +1 -1
  23. package/dist/federation/send.test.mjs +3 -3
  24. package/dist/federation/temporal.test.d.mts +2 -0
  25. package/dist/federation/temporal.test.mjs +71 -0
  26. package/dist/federation/webfinger.test.mjs +1 -1
  27. package/dist/{http-CrGuipxe.d.cts → http-BDZeS5om.d.ts} +2 -1
  28. package/dist/{http-aQzN9Ayi.d.ts → http-C87EWkO0.d.cts} +2 -3
  29. package/dist/{http-CmDErAnD.cjs → http-CEXtUHix.cjs} +57 -9
  30. package/dist/{http-qKyJKSnF.js → http-Cw1EorUA.js} +58 -10
  31. package/dist/{http-DRDpStn2.mjs → http-DvhFwy2E.mjs} +58 -10
  32. package/dist/{inbox-DTuPJEma.mjs → inbox-7b3VYnec.mjs} +7 -4
  33. package/dist/{key-WSedbAA-.mjs → key-B3OBz6A_.mjs} +1 -1
  34. package/dist/{kv-CbLNp3zQ.d.cts → kv-D6hNiMTK.d.ts} +1 -0
  35. package/dist/{kv-cache-9cdQDeT0.cjs → kv-cache-BIapFiSV.cjs} +2 -2
  36. package/dist/{kv-cache-DNNRFY8u.js → kv-cache-C6nynGhh.js} +2 -2
  37. package/dist/{kv-cache-B01V7s3h.mjs → kv-cache-FsT1ljhs.mjs} +1 -1
  38. package/dist/{kv-GFYnFoOl.d.ts → kv-gJ8LYbxX.d.cts} +1 -3
  39. package/dist/ld-DTxYKZTT.mjs +550 -0
  40. package/dist/{middleware-UThUK30x.js → middleware-0vceVN10.js} +407 -82
  41. package/dist/{middleware-DFcgN1cH.cjs → middleware-BcbU5lKS.cjs} +406 -80
  42. package/dist/{middleware-8P6Z15Wq.cjs → middleware-DUT--Qen.cjs} +1 -1
  43. package/dist/{middleware-DDsnRvFC.mjs → middleware-DmT9t0BJ.mjs} +317 -84
  44. package/dist/{middleware-C2c1rlmE.mjs → middleware-Li_g_vqz.mjs} +1 -1
  45. package/dist/{mod-CnwZoDzx.d.ts → mod-0dF0OeGw.d.cts} +2 -3
  46. package/dist/{mod-B505FZBC.d.cts → mod-BTWFtZwa.d.ts} +4 -3
  47. package/dist/{mod-Cgpc49fA.d.cts → mod-Bv1elC-b.d.ts} +2 -1
  48. package/dist/{mod-DoJBjjnO.d.cts → mod-CjiK2zrW.d.ts} +3 -2
  49. package/dist/{mod-DvxszxXC.d.ts → mod-D5YlzzP3.d.cts} +3 -4
  50. package/dist/{mod-D7PAuO6k.d.ts → mod-dxOnKp2Y.d.cts} +4 -5
  51. package/dist/mod.cjs +4 -4
  52. package/dist/mod.d.cts +9 -8
  53. package/dist/mod.d.ts +9 -10
  54. package/dist/mod.js +9 -9
  55. package/dist/nodeinfo/handler.test.mjs +1 -1
  56. package/dist/nodeinfo/mod.d.cts +2 -1
  57. package/dist/nodeinfo/mod.d.ts +2 -3
  58. package/dist/nodeinfo/mod.js +3 -3
  59. package/dist/otel/mod.d.cts +2 -1
  60. package/dist/otel/mod.d.ts +2 -3
  61. package/dist/otel/mod.js +2 -2
  62. package/dist/{owner-CptqhsOy.d.cts → owner-CnngXDNJ.d.ts} +2 -1
  63. package/dist/{owner-74ARJ5TL.d.ts → owner-DEvZuyOE.d.cts} +2 -3
  64. package/dist/{owner-BceqCVOs.mjs → owner-DzcnuWYo.mjs} +2 -2
  65. package/dist/{proof-4NUQlbQV.js → proof-CtTT8A7y.js} +299 -5
  66. package/dist/{proof-BojC8L94.mjs → proof-DmVGi7mI.mjs} +2 -2
  67. package/dist/{proof-kuTIVfyZ.cjs → proof-JEoW3ODq.cjs} +345 -3
  68. package/dist/runtime/mod.d.cts +1 -0
  69. package/dist/runtime/mod.d.ts +1 -2
  70. package/dist/runtime/mod.js +3 -3
  71. package/dist/{send-Buz5Gy8x.mjs → send-5ulOKVCW.mjs} +2 -2
  72. package/dist/sig/http.test.mjs +120 -2
  73. package/dist/sig/key.test.mjs +1 -1
  74. package/dist/sig/ld.test.mjs +558 -2
  75. package/dist/sig/mod.cjs +2 -2
  76. package/dist/sig/mod.d.cts +4 -3
  77. package/dist/sig/mod.d.ts +4 -5
  78. package/dist/sig/mod.js +4 -4
  79. package/dist/sig/owner.test.mjs +1 -1
  80. package/dist/sig/proof.test.mjs +1 -1
  81. package/dist/temporal-CmJkd-25.mjs +95 -0
  82. package/dist/testing/mod.d.mts +48 -6
  83. package/dist/{transformers-ve6e2xcg.js → transformers-BGMIq1cs.js} +2 -2
  84. package/dist/{types-hvL8ElAs.js → types-CAY3OdLq.js} +2 -2
  85. package/dist/utils/docloader.test.mjs +2 -2
  86. package/dist/utils/kv-cache.test.mjs +1 -1
  87. package/dist/utils/mod.cjs +1 -1
  88. package/dist/utils/mod.d.cts +2 -1
  89. package/dist/utils/mod.d.ts +2 -3
  90. package/dist/utils/mod.js +3 -3
  91. package/dist/vocab/mod.d.cts +1 -0
  92. package/dist/vocab/mod.d.ts +1 -2
  93. package/dist/vocab/mod.js +2 -2
  94. package/package.json +7 -7
  95. package/dist/ld-OBpaLfHR.mjs +0 -256
  96. /package/dist/{retry-B_E3V_Dx.mjs → retry-CJL0poaU.mjs} +0 -0
@@ -1,11 +1,11 @@
1
1
  import { Temporal } from "@js-temporal/polyfill";
2
- import "urlpattern-polyfill";
3
- import { t as __exportAll } from "./chunk-nlSIicah.js";
4
- import { r as getDefaultActivityTransformers } from "./transformers-ve6e2xcg.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-qKyJKSnF.js";
6
- 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-4NUQlbQV.js";
7
- import { n as getNodeInfo, t as nodeInfoToJson } from "./types-hvL8ElAs.js";
8
- import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-DNNRFY8u.js";
2
+ import { URLPattern } from "urlpattern-polyfill";
3
+ import { t as __exportAll } from "./chunk-CRNNMoPX.js";
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-Cw1EorUA.js";
6
+ 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-CtTT8A7y.js";
7
+ import { n as getNodeInfo, t as nodeInfoToJson } from "./types-CAY3OdLq.js";
8
+ import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-C6nynGhh.js";
9
9
  import { getLogger, withContext } from "@logtape/logtape";
10
10
  import { Activity, Collection, CollectionPage, CryptographicKey, Link, Multikey, Object as Object$1, OrderedCollection, OrderedCollectionPage, 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/inbox.ts
@@ -51,7 +52,7 @@ var InboxListenerSet = class InboxListenerSet {
51
52
  return this.dispatchWithClass(activity)?.listener ?? null;
52
53
  }
53
54
  };
54
- async function routeActivity({ context: ctx, json, activity, recipient, inboxListeners, inboxContextFactory, inboxErrorHandler, kv, kvPrefixes, queue, span, tracerProvider, idempotencyStrategy }) {
55
+ async function routeActivity({ context: ctx, json, originalJson, normalizedActivity, ldSignatureVerified, activity, recipient, inboxListeners, inboxContextFactory, listenerInboxContextFactory, inboxErrorHandler, kv, kvPrefixes, queue, span, tracerProvider, idempotencyStrategy }) {
55
56
  const logger = getLogger([
56
57
  "fedify",
57
58
  "federation",
@@ -108,7 +109,9 @@ async function routeActivity({ context: ctx, json, activity, recipient, inboxLis
108
109
  type: "inbox",
109
110
  id: crypto.randomUUID(),
110
111
  baseUrl: ctx.origin,
111
- activity: json,
112
+ activity: originalJson ?? json,
113
+ ...normalizedActivity == null ? {} : { normalizedActivity },
114
+ ...ldSignatureVerified == null ? {} : { ldSignatureVerified },
112
115
  identifier: recipient,
113
116
  attempt: 0,
114
117
  started: (/* @__PURE__ */ new Date()).toISOString(),
@@ -152,7 +155,8 @@ async function routeActivity({ context: ctx, json, activity, recipient, inboxLis
152
155
  const { class: cls, listener } = dispatched;
153
156
  span.updateName(`activitypub.dispatch_inbox_listener ${cls.name}`);
154
157
  try {
155
- await listener(inboxContextFactory(recipient, json, activity?.id?.href, getTypeId(activity).href), activity);
158
+ const contextFactory = listenerInboxContextFactory ?? inboxContextFactory;
159
+ await listener(contextFactory(recipient, contextFactory === inboxContextFactory ? json : originalJson ?? json, activity?.id?.href, getTypeId(activity).href), activity);
156
160
  } catch (error) {
157
161
  try {
158
162
  await inboxErrorHandler?.(ctx, error);
@@ -1009,7 +1013,123 @@ function acceptsJsonLd(request) {
1009
1013
  return types.includes("application/activity+json") || types.includes("application/ld+json") || types.includes("application/json");
1010
1014
  }
1011
1015
  //#endregion
1016
+ //#region src/federation/temporal.ts
1017
+ function isPlainObject(value) {
1018
+ return typeof value === "object" && value != null && !Array.isArray(value);
1019
+ }
1020
+ function normalizeDateTimeLiteral(value) {
1021
+ return value.substring(19).match(/[Z+-]/) ? value : value + "Z";
1022
+ }
1023
+ function isMalformedDateTimeLiteral(value) {
1024
+ if (typeof value !== "string") return false;
1025
+ try {
1026
+ Temporal.Instant.from(normalizeDateTimeLiteral(value));
1027
+ return false;
1028
+ } catch {
1029
+ return true;
1030
+ }
1031
+ }
1032
+ function isMalformedDurationLiteral(value) {
1033
+ if (typeof value !== "string") return false;
1034
+ try {
1035
+ Temporal.Duration.from(value);
1036
+ return false;
1037
+ } catch {
1038
+ return true;
1039
+ }
1040
+ }
1041
+ const TEMPORAL_DATE_TIME_IRIS = new Set([
1042
+ "https://www.w3.org/ns/activitystreams#deleted",
1043
+ "https://www.w3.org/ns/activitystreams#endTime",
1044
+ "https://www.w3.org/ns/activitystreams#published",
1045
+ "https://www.w3.org/ns/activitystreams#startTime",
1046
+ "https://www.w3.org/ns/activitystreams#updated",
1047
+ "http://purl.org/dc/terms/created",
1048
+ "https://w3id.org/security#created"
1049
+ ]);
1050
+ const TEMPORAL_DURATION_IRIS = new Set(["https://www.w3.org/ns/activitystreams#duration"]);
1051
+ const QUESTION_CLOSED_IRI = "https://www.w3.org/ns/activitystreams#closed";
1052
+ const XSD_DATE_TIME_IRI = "http://www.w3.org/2001/XMLSchema#dateTime";
1053
+ function hasMalformedExpandedDateTimeLiteral(value) {
1054
+ if (Array.isArray(value)) return value.some(hasMalformedExpandedDateTimeLiteral);
1055
+ return isPlainObject(value) && "@value" in value && isMalformedDateTimeLiteral(value["@value"]);
1056
+ }
1057
+ function hasMalformedExpandedQuestionClosedLiteral(value) {
1058
+ if (Array.isArray(value)) return value.some(hasMalformedExpandedQuestionClosedLiteral);
1059
+ if (!isPlainObject(value) || !("@value" in value)) return false;
1060
+ const literal = value["@value"];
1061
+ if (typeof literal === "boolean") return false;
1062
+ if (typeof literal !== "string") return false;
1063
+ if (value["@type"] !== XSD_DATE_TIME_IRI) return false;
1064
+ if (new Date(literal).toString() === "Invalid Date") return false;
1065
+ return isMalformedDateTimeLiteral(literal);
1066
+ }
1067
+ function hasMalformedExpandedDurationLiteral(value) {
1068
+ if (Array.isArray(value)) return value.some(hasMalformedExpandedDurationLiteral);
1069
+ return isPlainObject(value) && "@value" in value && isMalformedDurationLiteral(value["@value"]);
1070
+ }
1071
+ function hasMalformedKnownTemporalLiteralInternal(value, visited) {
1072
+ if (Array.isArray(value)) return value.some((item) => hasMalformedKnownTemporalLiteralInternal(item, visited));
1073
+ if (!isPlainObject(value)) return false;
1074
+ if (visited.has(value)) return false;
1075
+ visited.add(value);
1076
+ if ("@value" in value) return false;
1077
+ for (const [key, child] of Object.entries(value)) {
1078
+ if (TEMPORAL_DATE_TIME_IRIS.has(key)) {
1079
+ if (hasMalformedExpandedDateTimeLiteral(child)) return true;
1080
+ continue;
1081
+ }
1082
+ if (key === QUESTION_CLOSED_IRI) {
1083
+ if (hasMalformedExpandedQuestionClosedLiteral(child)) return true;
1084
+ continue;
1085
+ }
1086
+ if (TEMPORAL_DURATION_IRIS.has(key)) {
1087
+ if (hasMalformedExpandedDurationLiteral(child)) return true;
1088
+ continue;
1089
+ }
1090
+ if (hasMalformedKnownTemporalLiteralInternal(child, visited)) return true;
1091
+ }
1092
+ return false;
1093
+ }
1094
+ async function hasMalformedKnownTemporalLiteral(value, contextLoader) {
1095
+ try {
1096
+ return hasMalformedKnownTemporalLiteralInternal(await jsonld.expand(value, {
1097
+ documentLoader: getNormalizationContextLoader(contextLoader),
1098
+ keepFreeFloatingNodes: true
1099
+ }), /* @__PURE__ */ new Set());
1100
+ } catch {
1101
+ return false;
1102
+ }
1103
+ }
1104
+ //#endregion
1012
1105
  //#region src/federation/handler.ts
1106
+ const rawInboxContextFactorySymbol = Symbol("fedify.rawInboxContextFactory");
1107
+ function isRemoteContextLoadingFailure$1(error) {
1108
+ return error instanceof Error && typeof error.details === "object" && error.details != null && error.details.code === "loading remote context failed";
1109
+ }
1110
+ function isPermanentRemoteContextError$1(error) {
1111
+ if (!(error instanceof Error) || error.name !== "jsonld.InvalidUrl") return false;
1112
+ const details = error.details;
1113
+ if (details?.code === "invalid remote context") return true;
1114
+ return isRemoteContextLoadingFailure$1(error) && typeof details?.url === "string" && !URL.canParse(details.url) && isClearlyMalformedContextReference(details.url);
1115
+ }
1116
+ function isInvalidJsonLdError(error) {
1117
+ if (!(error instanceof Error)) return false;
1118
+ const name = error.name;
1119
+ return name === "UnsafeJsonLdError" || error instanceof InvalidContextReferenceError || isPermanentRemoteContextError$1(error) || name === "jsonld.SyntaxError" && !isRemoteContextLoadingFailure$1(error);
1120
+ }
1121
+ function isValidationTypeError(error) {
1122
+ return error instanceof TypeError && (/^(Invalid JSON-LD:|Invalid type:|Unexpected type:)/.test(error.message) || isInvalidUrlTypeError(error));
1123
+ }
1124
+ function isPermanentActivityParseError(error) {
1125
+ return isInvalidJsonLdError(error) || isValidationTypeError(error);
1126
+ }
1127
+ function hasHttpSignatureHeaders(request) {
1128
+ return request.headers.has("Signature") || request.headers.has("Signature-Input");
1129
+ }
1130
+ function hasObjectIntegrityProof(json) {
1131
+ return typeof json === "object" && json != null && "proof" in json;
1132
+ }
1013
1133
  /**
1014
1134
  * Handles an actor request.
1015
1135
  * @template TContextData The context data to pass to the context.
@@ -1071,8 +1191,8 @@ async function handleObject(request, { values, context, objectDispatcher, author
1071
1191
  * @param parameters The parameters for handling the collection.
1072
1192
  * @returns A promise that resolves to an HTTP response.
1073
1193
  */
1074
- async function handleCollection(request, { name: name$2, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, tracerProvider, onUnauthorized, onNotFound }) {
1075
- const spanName = name$2.trim().replace(/\s+/g, "_");
1194
+ async function handleCollection(request, { name: name$1, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, tracerProvider, onUnauthorized, onNotFound }) {
1195
+ const spanName = name$1.trim().replace(/\s+/g, "_");
1076
1196
  tracerProvider = tracerProvider ?? trace.getTracerProvider();
1077
1197
  const tracer = tracerProvider.getTracer(name, version);
1078
1198
  const cursor = new URL(request.url).searchParams.get("cursor");
@@ -1114,7 +1234,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
1114
1234
  collection = new OrderedCollection({
1115
1235
  id: baseUri,
1116
1236
  totalItems: totalItems == null ? null : Number(totalItems),
1117
- items: filterCollectionItems(itemsOrResponse, name$2, filterPredicate)
1237
+ items: filterCollectionItems(itemsOrResponse, name$1, filterPredicate)
1118
1238
  });
1119
1239
  } else {
1120
1240
  const lastCursor = await collectionCallbacks.lastCursor?.(context, identifier);
@@ -1135,7 +1255,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
1135
1255
  } else {
1136
1256
  const uri = new URL(baseUri);
1137
1257
  uri.searchParams.set("cursor", cursor);
1138
- const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$2}`, {
1258
+ const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$1}`, {
1139
1259
  kind: SpanKind.SERVER,
1140
1260
  attributes: {
1141
1261
  "activitypub.collection.id": uri.href,
@@ -1179,7 +1299,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
1179
1299
  id: uri,
1180
1300
  prev,
1181
1301
  next,
1182
- items: filterCollectionItems(items, name$2, filterPredicate),
1302
+ items: filterCollectionItems(items, name$1, filterPredicate),
1183
1303
  partOf
1184
1304
  });
1185
1305
  }
@@ -1329,28 +1449,104 @@ async function handleInboxInternal(request, parameters, span) {
1329
1449
  });
1330
1450
  }
1331
1451
  const keyCache = new KvKeyCache(kv, kvPrefixes.publicKey, ctx);
1332
- let ldSigVerified;
1333
- try {
1334
- ldSigVerified = await verifyJsonLd(json, {
1335
- contextLoader: ctx.contextLoader,
1336
- documentLoader: ctx.documentLoader,
1337
- keyCache,
1338
- tracerProvider
1452
+ const jsonWithoutSig = detachSignature(json);
1453
+ const hasLdSignature = hasSignature(json);
1454
+ const canAttemptAlternateAuthAfterLdSignatureFailure = skipSignatureVerification || hasHttpSignatureHeaders(request) || hasObjectIntegrityProof(jsonWithoutSig);
1455
+ let deferredLdSignatureError = void 0;
1456
+ const respondInvalidActivity = async (error) => {
1457
+ logger.error("Failed to parse activity:\n{error}", {
1458
+ recipient,
1459
+ activity: json,
1460
+ error
1339
1461
  });
1340
- } catch (error) {
1341
- if (error instanceof Error && error.name === "jsonld.SyntaxError") {
1342
- logger.error("Failed to parse JSON-LD:\n{error}", {
1462
+ try {
1463
+ await inboxErrorHandler?.(ctx, error);
1464
+ } catch (error) {
1465
+ logger.error("An unexpected error occurred in inbox error handler:\n{error}", {
1466
+ error,
1467
+ activity: json,
1468
+ recipient
1469
+ });
1470
+ }
1471
+ span.setStatus({
1472
+ code: SpanStatusCode.ERROR,
1473
+ message: `Failed to parse activity:\n${error}`
1474
+ });
1475
+ return new Response("Invalid activity.", {
1476
+ status: 400,
1477
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
1478
+ });
1479
+ };
1480
+ let compactedJson = json;
1481
+ let compactedJsonWithoutSig = jsonWithoutSig;
1482
+ let ldSigVerified = false;
1483
+ if (hasLdSignature) {
1484
+ try {
1485
+ compactedJson = await compactJsonLd(json, ctx.contextLoader);
1486
+ } catch (error) {
1487
+ if (isInvalidJsonLdError(error)) {
1488
+ logger.error("Failed to parse JSON-LD:\n{error}", {
1489
+ recipient,
1490
+ error
1491
+ });
1492
+ return new Response("Invalid JSON-LD.", {
1493
+ status: 400,
1494
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
1495
+ });
1496
+ }
1497
+ if (!canAttemptAlternateAuthAfterLdSignatureFailure) throw error;
1498
+ if (!skipSignatureVerification) deferredLdSignatureError = error;
1499
+ logger.debug("Failed to normalize JSON-LD for Linked Data Signatures; deferring to another authentication path only if it verifies:\n{error}", {
1343
1500
  recipient,
1344
1501
  error
1345
1502
  });
1346
- return new Response("Invalid JSON-LD.", {
1347
- status: 400,
1348
- headers: { "Content-Type": "text/plain; charset=utf-8" }
1349
- });
1350
1503
  }
1351
- ldSigVerified = false;
1504
+ if (compactedJson !== json) {
1505
+ compactedJsonWithoutSig = detachSignature(compactedJson);
1506
+ try {
1507
+ ldSigVerified = await verifyCompactJsonLd(compactedJson, {
1508
+ contextLoader: ctx.contextLoader,
1509
+ documentLoader: ctx.documentLoader,
1510
+ keyCache,
1511
+ tracerProvider
1512
+ });
1513
+ } catch (error) {
1514
+ if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(compactedJsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(error);
1515
+ if (isInvalidJsonLdError(error)) {
1516
+ logger.error("Failed to parse JSON-LD:\n{error}", {
1517
+ recipient,
1518
+ error
1519
+ });
1520
+ return new Response("Invalid JSON-LD.", {
1521
+ status: 400,
1522
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
1523
+ });
1524
+ }
1525
+ if (!canAttemptAlternateAuthAfterLdSignatureFailure) throw error;
1526
+ if (!skipSignatureVerification) try {
1527
+ await Object$1.fromJsonLd(compactedJson, {
1528
+ contextLoader: getNormalizationContextLoader(ctx.contextLoader),
1529
+ documentLoader: ctx.documentLoader,
1530
+ tracerProvider
1531
+ });
1532
+ } catch (parseError) {
1533
+ if (parseError instanceof RangeError && await hasMalformedKnownTemporalLiteral(compactedJsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(parseError);
1534
+ if (isInvalidJsonLdError(parseError)) {
1535
+ logger.error("Failed to parse JSON-LD:\n{error}", {
1536
+ recipient,
1537
+ error: parseError
1538
+ });
1539
+ return new Response("Invalid JSON-LD.", {
1540
+ status: 400,
1541
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
1542
+ });
1543
+ }
1544
+ deferredLdSignatureError = parseError;
1545
+ }
1546
+ ldSigVerified = false;
1547
+ }
1548
+ }
1352
1549
  }
1353
- const jsonWithoutSig = detachSignature(json);
1354
1550
  let activity = null;
1355
1551
  let activityVerified = false;
1356
1552
  if (ldSigVerified) {
@@ -1358,7 +1554,16 @@ async function handleInboxInternal(request, parameters, span) {
1358
1554
  recipient,
1359
1555
  json
1360
1556
  });
1361
- activity = await Activity.fromJsonLd(jsonWithoutSig, ctx);
1557
+ try {
1558
+ activity = await Activity.fromJsonLd(compactedJsonWithoutSig, {
1559
+ ...ctx,
1560
+ contextLoader: getNormalizationContextLoader(ctx.contextLoader)
1561
+ });
1562
+ } catch (error) {
1563
+ if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(compactedJsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(error);
1564
+ if (!isPermanentActivityParseError(error)) throw error;
1565
+ return await respondInvalidActivity(error);
1566
+ }
1362
1567
  activityVerified = true;
1363
1568
  } else {
1364
1569
  logger.debug("Linked Data Signatures are not verified.", {
@@ -1367,12 +1572,21 @@ async function handleInboxInternal(request, parameters, span) {
1367
1572
  });
1368
1573
  try {
1369
1574
  activity = await verifyObject(Activity, jsonWithoutSig, {
1370
- contextLoader: ctx.contextLoader,
1575
+ contextLoader: wrapContextLoaderForJsonLd(ctx.contextLoader),
1371
1576
  documentLoader: ctx.documentLoader,
1372
1577
  keyCache,
1373
1578
  tracerProvider
1374
1579
  });
1375
1580
  } catch (error) {
1581
+ if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(jsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(error);
1582
+ if (deferredLdSignatureError != null) {
1583
+ logger.debug("Object Integrity Proof fallback did not supersede a deferred Linked Data Signature failure:\n{error}", {
1584
+ recipient,
1585
+ error
1586
+ });
1587
+ activity = null;
1588
+ }
1589
+ if (!isPermanentActivityParseError(error)) throw error;
1376
1590
  logger.error("Failed to parse activity:\n{error}", {
1377
1591
  recipient,
1378
1592
  activity: json,
@@ -1420,6 +1634,7 @@ async function handleInboxInternal(request, parameters, span) {
1420
1634
  tracerProvider
1421
1635
  });
1422
1636
  if (verification.verified === false) {
1637
+ if (deferredLdSignatureError != null) throw deferredLdSignatureError;
1423
1638
  const reason = verification.reason;
1424
1639
  logger.error("Failed to verify the request's HTTP Signatures.", {
1425
1640
  recipient,
@@ -1495,7 +1710,15 @@ async function handleInboxInternal(request, parameters, span) {
1495
1710
  }
1496
1711
  httpSigKey = verification.key;
1497
1712
  }
1498
- activity = await Activity.fromJsonLd(jsonWithoutSig, ctx);
1713
+ try {
1714
+ activity = await Activity.fromJsonLd(jsonWithoutSig, {
1715
+ ...ctx,
1716
+ contextLoader: wrapContextLoaderForJsonLd(ctx.contextLoader)
1717
+ });
1718
+ } catch (error) {
1719
+ if (!isPermanentActivityParseError(error)) throw error;
1720
+ return await respondInvalidActivity(error);
1721
+ }
1499
1722
  }
1500
1723
  if (activity.id != null) span.setAttribute("activitypub.activity.id", activity.id.href);
1501
1724
  span.setAttribute("activitypub.activity.type", getTypeId(activity).href);
@@ -1507,6 +1730,7 @@ async function handleInboxInternal(request, parameters, span) {
1507
1730
  "http_signatures.key_id": httpSigKey?.id?.href ?? ""
1508
1731
  });
1509
1732
  if (httpSigKey != null && !await doesActorOwnKey(activity, httpSigKey, ctx)) {
1733
+ if (deferredLdSignatureError != null) throw deferredLdSignatureError;
1510
1734
  logger.error("The signer ({keyId}) and the actor ({actorId}) do not match.", {
1511
1735
  activity: json,
1512
1736
  recipient,
@@ -1531,10 +1755,14 @@ async function handleInboxInternal(request, parameters, span) {
1531
1755
  const routeResult = await routeActivity({
1532
1756
  context: ctx,
1533
1757
  json,
1758
+ originalJson: json,
1759
+ normalizedActivity: hasLdSignature && compactedJson !== json ? compactedJson : void 0,
1760
+ ldSignatureVerified: hasLdSignature ? ldSigVerified : void 0,
1534
1761
  activity,
1535
1762
  recipient,
1536
1763
  inboxListeners,
1537
1764
  inboxContextFactory,
1765
+ listenerInboxContextFactory: ldSigVerified ? inboxContextFactory[rawInboxContextFactorySymbol] : void 0,
1538
1766
  inboxErrorHandler,
1539
1767
  kv,
1540
1768
  kvPrefixes,
@@ -1652,8 +1880,8 @@ var CustomCollectionHandler = class {
1652
1880
  * @param CollectionPage The CollectionPage constructor.
1653
1881
  * @param filterPredicate Optional filter predicate for items.
1654
1882
  */
1655
- constructor(name$1, values, context, callbacks, tracerProvider = trace.getTracerProvider(), Collection, CollectionPage, filterPredicate) {
1656
- this.name = name$1;
1883
+ constructor(name$2, values, context, callbacks, tracerProvider = trace.getTracerProvider(), Collection, CollectionPage, filterPredicate) {
1884
+ this.name = name$2;
1657
1885
  this.values = values;
1658
1886
  this.context = context;
1659
1887
  this.callbacks = callbacks;
@@ -2452,6 +2680,18 @@ var middleware_exports = /* @__PURE__ */ __exportAll({
2452
2680
  KvSpecDeterminer: () => KvSpecDeterminer,
2453
2681
  createFederation: () => createFederation
2454
2682
  });
2683
+ function isRemoteContextLoadingFailure(error) {
2684
+ return error instanceof Error && typeof error.details === "object" && error.details != null && error.details.code === "loading remote context failed";
2685
+ }
2686
+ function isPermanentRemoteContextError(error) {
2687
+ if (!(error instanceof Error) || error.name !== "jsonld.InvalidUrl") return false;
2688
+ const details = error.details;
2689
+ if (details?.code === "invalid remote context") return true;
2690
+ return isRemoteContextLoadingFailure(error) && typeof details?.url === "string" && !URL.canParse(details.url) && isClearlyMalformedContextReference(details.url);
2691
+ }
2692
+ function isPermanentInboxParseError(error) {
2693
+ 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));
2694
+ }
2455
2695
  /**
2456
2696
  * Create a new {@link Federation} instance.
2457
2697
  * @param parameters Parameters for initializing the instance.
@@ -2832,7 +3072,7 @@ var FederationImpl = class extends FederationBuilderImpl {
2832
3072
  }
2833
3073
  logger.info("Successfully sent activity {activityId} to {inbox}.", { ...logData });
2834
3074
  }
2835
- async #listenInboxMessage(ctxData, message, span) {
3075
+ async #listenInboxMessage(ctxData, message, _span) {
2836
3076
  const logger = getLogger([
2837
3077
  "fedify",
2838
3078
  "federation",
@@ -2845,60 +3085,28 @@ var FederationImpl = class extends FederationBuilderImpl {
2845
3085
  const identity = await this.sharedInboxKeyDispatcher(context);
2846
3086
  if (identity != null) context = this.#createContext(baseUrl, ctxData, { documentLoader: "identifier" in identity || "username" in identity ? await context.getDocumentLoader(identity) : context.getDocumentLoader(identity) });
2847
3087
  }
2848
- const activity = await Activity.fromJsonLd(message.activity, context);
2849
- span.setAttribute("activitypub.activity.type", getTypeId(activity).href);
2850
- if (activity.id != null) span.setAttribute("activitypub.activity.id", activity.id.href);
2851
- const cacheKey = activity.id == null ? null : [
2852
- ...this.kvPrefixes.activityIdempotence,
2853
- context.origin,
2854
- activity.id.href
2855
- ];
2856
- if (cacheKey != null) {
2857
- if (await this.kv.get(cacheKey) === true) {
2858
- logger.debug("Activity {activityId} has already been processed.", {
2859
- activityId: activity.id?.href,
2860
- activity: message.activity,
2861
- recipient: message.identifier
2862
- });
2863
- return;
2864
- }
2865
- }
2866
3088
  await this._getTracer().startActiveSpan("activitypub.dispatch_inbox_listener", { kind: SpanKind.INTERNAL }, async (span) => {
2867
- const dispatched = this.inboxListeners?.dispatchWithClass(activity);
2868
- if (dispatched == null) {
2869
- logger.error("Unsupported activity type:\n{activity}", {
2870
- activityId: activity.id?.href,
2871
- activity: message.activity,
2872
- recipient: message.identifier,
2873
- trial: message.attempt
2874
- });
2875
- span.setStatus({
2876
- code: SpanStatusCode.ERROR,
2877
- message: `Unsupported activity type: ${getTypeId(activity).href}`
2878
- });
2879
- span.end();
2880
- return;
2881
- }
2882
- const { class: cls, listener } = dispatched;
2883
- span.updateName(`activitypub.dispatch_inbox_listener ${cls.name}`);
2884
- try {
2885
- await listener(context.toInboxContext(message.identifier, message.activity, activity.id?.href, getTypeId(activity).href), activity);
2886
- } catch (error) {
3089
+ let activity = null;
3090
+ let cacheKey = null;
3091
+ const reportInboxError = async (error) => {
2887
3092
  try {
2888
3093
  await this.inboxErrorHandler?.(context, error);
2889
3094
  } catch (error) {
2890
3095
  logger.error("An unexpected error occurred in inbox error handler:\n{error}", {
2891
3096
  error,
2892
3097
  trial: message.attempt,
2893
- activityId: activity.id?.href,
3098
+ activityId: activity?.id?.href,
2894
3099
  activity: message.activity,
2895
3100
  recipient: message.identifier
2896
3101
  });
2897
3102
  }
3103
+ };
3104
+ const handleRetriableFailure = async (error) => {
3105
+ await reportInboxError(error);
2898
3106
  if (this.inboxQueue?.nativeRetrial) {
2899
3107
  logger.error("Failed to process the incoming activity {activityId}; backend will handle retry:\n{error}", {
2900
3108
  error,
2901
- activityId: activity.id?.href,
3109
+ activityId: activity?.id?.href,
2902
3110
  activity: message.activity,
2903
3111
  recipient: message.identifier
2904
3112
  });
@@ -2917,17 +3125,25 @@ var FederationImpl = class extends FederationBuilderImpl {
2917
3125
  logger.error("Failed to process the incoming activity {activityId} (attempt #{attempt}); retry...:\n{error}", {
2918
3126
  error,
2919
3127
  attempt: message.attempt,
2920
- activityId: activity.id?.href,
3128
+ activityId: activity?.id?.href,
2921
3129
  activity: message.activity,
2922
3130
  recipient: message.identifier
2923
3131
  });
3132
+ if (this.inboxQueue == null) {
3133
+ span.setStatus({
3134
+ code: SpanStatusCode.ERROR,
3135
+ message: String(error)
3136
+ });
3137
+ span.end();
3138
+ throw error;
3139
+ }
2924
3140
  await this.inboxQueue?.enqueue({
2925
3141
  ...message,
2926
3142
  attempt: message.attempt + 1
2927
3143
  }, { delay: Temporal.Duration.compare(delay, { seconds: 0 }) < 0 ? Temporal.Duration.from({ seconds: 0 }) : delay });
2928
3144
  } else logger.error("Failed to process the incoming activity {activityId} after {trial} attempts; giving up:\n{error}", {
2929
3145
  error,
2930
- activityId: activity.id?.href,
3146
+ activityId: activity?.id?.href,
2931
3147
  activity: message.activity,
2932
3148
  recipient: message.identifier
2933
3149
  });
@@ -2936,11 +3152,108 @@ var FederationImpl = class extends FederationBuilderImpl {
2936
3152
  message: String(error)
2937
3153
  });
2938
3154
  span.end();
3155
+ };
3156
+ let dispatched;
3157
+ let parseInput = void 0;
3158
+ let parseContextLoader = context.contextLoader;
3159
+ try {
3160
+ const hasSignatureField = hasSignature(message.activity);
3161
+ const shouldParseFromNormalizedSignedPayload = message.ldSignatureVerified === true || message.normalizedActivity != null || message.ldSignatureVerified == null && hasSignatureField;
3162
+ const parseContext = hasSignatureField ? {
3163
+ ...context,
3164
+ contextLoader: getNormalizationContextLoader(context.contextLoader)
3165
+ } : {
3166
+ ...context,
3167
+ contextLoader: wrapContextLoaderForJsonLd(context.contextLoader)
3168
+ };
3169
+ parseContextLoader = parseContext.contextLoader;
3170
+ let normalizedActivity;
3171
+ if (shouldParseFromNormalizedSignedPayload) {
3172
+ normalizedActivity = message.normalizedActivity ?? await compactJsonLd(message.activity, context.contextLoader);
3173
+ assertSafeJsonLd(normalizedActivity);
3174
+ }
3175
+ parseInput = shouldParseFromNormalizedSignedPayload ? detachSignature(normalizedActivity) : hasSignatureField ? detachSignature(message.activity) : message.activity;
3176
+ activity = await Activity.fromJsonLd(parseInput, parseContext);
3177
+ span.setAttribute("activitypub.activity.type", getTypeId(activity).href);
3178
+ if (activity.id != null) span.setAttribute("activitypub.activity.id", activity.id.href);
3179
+ cacheKey = activity.id == null ? null : [
3180
+ ...this.kvPrefixes.activityIdempotence,
3181
+ context.origin,
3182
+ activity.id.href
3183
+ ];
3184
+ if (cacheKey != null) {
3185
+ if (await this.kv.get(cacheKey) === true) {
3186
+ logger.debug("Activity {activityId} has already been processed.", {
3187
+ activityId: activity.id?.href,
3188
+ activity: message.activity,
3189
+ recipient: message.identifier
3190
+ });
3191
+ span.end();
3192
+ return;
3193
+ }
3194
+ }
3195
+ dispatched = this.inboxListeners?.dispatchWithClass(activity);
3196
+ } catch (error) {
3197
+ if (activity == null && error instanceof RangeError && await hasMalformedKnownTemporalLiteral(parseInput, parseContextLoader)) {
3198
+ await reportInboxError(error);
3199
+ logger.error("Failed to parse the queued incoming activity {activityId}:\n{error}", {
3200
+ error,
3201
+ trial: message.attempt,
3202
+ activityId: null,
3203
+ activity: message.activity,
3204
+ recipient: message.identifier
3205
+ });
3206
+ span.setStatus({
3207
+ code: SpanStatusCode.ERROR,
3208
+ message: String(error)
3209
+ });
3210
+ span.end();
3211
+ return;
3212
+ }
3213
+ if (isPermanentInboxParseError(error)) {
3214
+ await reportInboxError(error);
3215
+ logger.error("Failed to parse the queued incoming activity {activityId}:\n{error}", {
3216
+ error,
3217
+ trial: message.attempt,
3218
+ activityId: activity?.id?.href,
3219
+ activity: message.activity,
3220
+ recipient: message.identifier
3221
+ });
3222
+ span.setStatus({
3223
+ code: SpanStatusCode.ERROR,
3224
+ message: String(error)
3225
+ });
3226
+ span.end();
3227
+ return;
3228
+ }
3229
+ await handleRetriableFailure(error);
3230
+ return;
3231
+ }
3232
+ if (dispatched == null) {
3233
+ logger.error("Unsupported activity type:\n{activity}", {
3234
+ activityId: activity.id?.href,
3235
+ activity: message.activity,
3236
+ recipient: message.identifier,
3237
+ trial: message.attempt
3238
+ });
3239
+ span.setStatus({
3240
+ code: SpanStatusCode.ERROR,
3241
+ message: `Unsupported activity type: ${getTypeId(activity).href}`
3242
+ });
3243
+ span.end();
3244
+ return;
3245
+ }
3246
+ const { class: cls, listener } = dispatched;
3247
+ span.updateName(`activitypub.dispatch_inbox_listener ${cls.name}`);
3248
+ try {
3249
+ await listener(context.toInboxContext(message.identifier, message.activity, activity.id?.href, getTypeId(activity).href), activity);
3250
+ } catch (error) {
3251
+ await handleRetriableFailure(error);
2939
3252
  return;
2940
3253
  }
2941
3254
  if (cacheKey != null) await this.kv.set(cacheKey, true, { ttl: Temporal.Duration.from({ days: 1 }) });
2942
3255
  logger.info("Activity {activityId} has been processed.", {
2943
- activityId: activity.id?.href,
3256
+ activityId: activity?.id?.href,
2944
3257
  activity: message.activity,
2945
3258
  recipient: message.identifier
2946
3259
  });
@@ -3286,16 +3599,18 @@ var FederationImpl = class extends FederationBuilderImpl {
3286
3599
  onNotFound
3287
3600
  });
3288
3601
  context = this.#createContext(request, contextData, { documentLoader: await context.getDocumentLoader({ identifier: route.values.identifier }) });
3289
- case "sharedInbox":
3602
+ case "sharedInbox": {
3290
3603
  if (routeName !== "inbox" && this.sharedInboxKeyDispatcher != null) {
3291
3604
  const identity = await this.sharedInboxKeyDispatcher(context);
3292
3605
  if (identity != null) context = this.#createContext(request, contextData, { documentLoader: "identifier" in identity || "username" in identity ? await context.getDocumentLoader(identity) : context.getDocumentLoader(identity) });
3293
3606
  }
3294
3607
  if (!this.manuallyStartQueue) this._startQueueInternal(contextData);
3608
+ const inboxContextFactory = context.toInboxContext.bind(context);
3609
+ inboxContextFactory[rawInboxContextFactorySymbol] = context.toInboxContext.bind(context);
3295
3610
  return await handleInbox(request, {
3296
3611
  recipient: route.values.identifier ?? null,
3297
3612
  context,
3298
- inboxContextFactory: context.toInboxContext.bind(context),
3613
+ inboxContextFactory,
3299
3614
  kv: this.kv,
3300
3615
  kvPrefixes: this.kvPrefixes,
3301
3616
  queue: this.inboxQueue,
@@ -3310,6 +3625,7 @@ var FederationImpl = class extends FederationBuilderImpl {
3310
3625
  tracerProvider: this.tracerProvider,
3311
3626
  idempotencyStrategy: this.idempotencyStrategy
3312
3627
  });
3628
+ }
3313
3629
  case "following": return await handleCollection(request, {
3314
3630
  name: "following",
3315
3631
  identifier: route.values.identifier,
@@ -3985,6 +4301,7 @@ var ContextImpl = class ContextImpl {
3985
4301
  const routeResult = await routeActivity({
3986
4302
  context: this,
3987
4303
  json,
4304
+ ldSignatureVerified: false,
3988
4305
  activity,
3989
4306
  recipient,
3990
4307
  inboxListeners: this.federation.inboxListeners,
@@ -4099,6 +4416,14 @@ var RequestContextImpl = class RequestContextImpl extends ContextImpl {
4099
4416
  };
4100
4417
  var InboxContextImpl = class InboxContextImpl extends ContextImpl {
4101
4418
  recipient;
4419
+ /**
4420
+ * The original received activity payload.
4421
+ *
4422
+ * Fedify may normalize a Linked Data Signature payload internally for safe
4423
+ * parsing, but forwarding must keep the sender's payload unchanged so
4424
+ * third-party signatures/proofs remain intact.
4425
+ * @internal
4426
+ */
4102
4427
  activity;
4103
4428
  activityId;
4104
4429
  activityType;