@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
  const { Temporal } = require("@js-temporal/polyfill");
2
2
  const { URLPattern } = require("urlpattern-polyfill");
3
- require("./chunk-DDcVe30Y.cjs");
3
+ const require_chunk = require("./chunk-DDcVe30Y.cjs");
4
4
  const require_transformers = require("./transformers-NeAONrAq.cjs");
5
- const require_http = require("./http-CmDErAnD.cjs");
6
- const require_proof = require("./proof-kuTIVfyZ.cjs");
5
+ const require_http = require("./http-CEXtUHix.cjs");
6
+ const require_proof = require("./proof-JEoW3ODq.cjs");
7
7
  const require_types = require("./types-KC4QAoxe.cjs");
8
- const require_kv_cache = require("./kv-cache-9cdQDeT0.cjs");
8
+ const require_kv_cache = require("./kv-cache-BIapFiSV.cjs");
9
9
  let _logtape_logtape = require("@logtape/logtape");
10
10
  let _fedify_vocab = require("@fedify/vocab");
11
11
  let _opentelemetry_api = require("@opentelemetry/api");
@@ -15,6 +15,8 @@ let url_template = require("url-template");
15
15
  let byte_encodings_hex = require("byte-encodings/hex");
16
16
  let _fedify_vocab_runtime = require("@fedify/vocab-runtime");
17
17
  let _opentelemetry_semantic_conventions = require("@opentelemetry/semantic-conventions");
18
+ let _fedify_vocab_runtime_jsonld = require("@fedify/vocab-runtime/jsonld");
19
+ _fedify_vocab_runtime_jsonld = require_chunk.__toESM(_fedify_vocab_runtime_jsonld);
18
20
  let _fedify_webfinger = require("@fedify/webfinger");
19
21
  let node_url = require("node:url");
20
22
  //#region src/federation/inbox.ts
@@ -51,7 +53,7 @@ var InboxListenerSet = class InboxListenerSet {
51
53
  return this.dispatchWithClass(activity)?.listener ?? null;
52
54
  }
53
55
  };
54
- async function routeActivity({ context: ctx, json, activity, recipient, inboxListeners, inboxContextFactory, inboxErrorHandler, kv, kvPrefixes, queue, span, tracerProvider, idempotencyStrategy }) {
56
+ async function routeActivity({ context: ctx, json, originalJson, normalizedActivity, ldSignatureVerified, activity, recipient, inboxListeners, inboxContextFactory, listenerInboxContextFactory, inboxErrorHandler, kv, kvPrefixes, queue, span, tracerProvider, idempotencyStrategy }) {
55
57
  const logger = (0, _logtape_logtape.getLogger)([
56
58
  "fedify",
57
59
  "federation",
@@ -108,7 +110,9 @@ async function routeActivity({ context: ctx, json, activity, recipient, inboxLis
108
110
  type: "inbox",
109
111
  id: crypto.randomUUID(),
110
112
  baseUrl: ctx.origin,
111
- activity: json,
113
+ activity: originalJson ?? json,
114
+ ...normalizedActivity == null ? {} : { normalizedActivity },
115
+ ...ldSignatureVerified == null ? {} : { ldSignatureVerified },
112
116
  identifier: recipient,
113
117
  attempt: 0,
114
118
  started: (/* @__PURE__ */ new Date()).toISOString(),
@@ -152,7 +156,8 @@ async function routeActivity({ context: ctx, json, activity, recipient, inboxLis
152
156
  const { class: cls, listener } = dispatched;
153
157
  span.updateName(`activitypub.dispatch_inbox_listener ${cls.name}`);
154
158
  try {
155
- await listener(inboxContextFactory(recipient, json, activity?.id?.href, (0, _fedify_vocab.getTypeId)(activity).href), activity);
159
+ const contextFactory = listenerInboxContextFactory ?? inboxContextFactory;
160
+ await listener(contextFactory(recipient, contextFactory === inboxContextFactory ? json : originalJson ?? json, activity?.id?.href, (0, _fedify_vocab.getTypeId)(activity).href), activity);
156
161
  } catch (error) {
157
162
  try {
158
163
  await inboxErrorHandler?.(ctx, error);
@@ -331,7 +336,7 @@ var FederationBuilderImpl = class {
331
336
  this.collectionTypeIds = {};
332
337
  }
333
338
  async build(options) {
334
- const { FederationImpl } = await Promise.resolve().then(() => require("./middleware-8P6Z15Wq.cjs"));
339
+ const { FederationImpl } = await Promise.resolve().then(() => require("./middleware-DUT--Qen.cjs"));
335
340
  const f = new FederationImpl(options);
336
341
  const trailingSlashInsensitiveValue = f.router.trailingSlashInsensitive;
337
342
  f.router = this.router.clone();
@@ -1009,7 +1014,123 @@ function acceptsJsonLd(request) {
1009
1014
  return types.includes("application/activity+json") || types.includes("application/ld+json") || types.includes("application/json");
1010
1015
  }
1011
1016
  //#endregion
1017
+ //#region src/federation/temporal.ts
1018
+ function isPlainObject(value) {
1019
+ return typeof value === "object" && value != null && !Array.isArray(value);
1020
+ }
1021
+ function normalizeDateTimeLiteral(value) {
1022
+ return value.substring(19).match(/[Z+-]/) ? value : value + "Z";
1023
+ }
1024
+ function isMalformedDateTimeLiteral(value) {
1025
+ if (typeof value !== "string") return false;
1026
+ try {
1027
+ Temporal.Instant.from(normalizeDateTimeLiteral(value));
1028
+ return false;
1029
+ } catch {
1030
+ return true;
1031
+ }
1032
+ }
1033
+ function isMalformedDurationLiteral(value) {
1034
+ if (typeof value !== "string") return false;
1035
+ try {
1036
+ Temporal.Duration.from(value);
1037
+ return false;
1038
+ } catch {
1039
+ return true;
1040
+ }
1041
+ }
1042
+ const TEMPORAL_DATE_TIME_IRIS = new Set([
1043
+ "https://www.w3.org/ns/activitystreams#deleted",
1044
+ "https://www.w3.org/ns/activitystreams#endTime",
1045
+ "https://www.w3.org/ns/activitystreams#published",
1046
+ "https://www.w3.org/ns/activitystreams#startTime",
1047
+ "https://www.w3.org/ns/activitystreams#updated",
1048
+ "http://purl.org/dc/terms/created",
1049
+ "https://w3id.org/security#created"
1050
+ ]);
1051
+ const TEMPORAL_DURATION_IRIS = new Set(["https://www.w3.org/ns/activitystreams#duration"]);
1052
+ const QUESTION_CLOSED_IRI = "https://www.w3.org/ns/activitystreams#closed";
1053
+ const XSD_DATE_TIME_IRI = "http://www.w3.org/2001/XMLSchema#dateTime";
1054
+ function hasMalformedExpandedDateTimeLiteral(value) {
1055
+ if (Array.isArray(value)) return value.some(hasMalformedExpandedDateTimeLiteral);
1056
+ return isPlainObject(value) && "@value" in value && isMalformedDateTimeLiteral(value["@value"]);
1057
+ }
1058
+ function hasMalformedExpandedQuestionClosedLiteral(value) {
1059
+ if (Array.isArray(value)) return value.some(hasMalformedExpandedQuestionClosedLiteral);
1060
+ if (!isPlainObject(value) || !("@value" in value)) return false;
1061
+ const literal = value["@value"];
1062
+ if (typeof literal === "boolean") return false;
1063
+ if (typeof literal !== "string") return false;
1064
+ if (value["@type"] !== XSD_DATE_TIME_IRI) return false;
1065
+ if (new Date(literal).toString() === "Invalid Date") return false;
1066
+ return isMalformedDateTimeLiteral(literal);
1067
+ }
1068
+ function hasMalformedExpandedDurationLiteral(value) {
1069
+ if (Array.isArray(value)) return value.some(hasMalformedExpandedDurationLiteral);
1070
+ return isPlainObject(value) && "@value" in value && isMalformedDurationLiteral(value["@value"]);
1071
+ }
1072
+ function hasMalformedKnownTemporalLiteralInternal(value, visited) {
1073
+ if (Array.isArray(value)) return value.some((item) => hasMalformedKnownTemporalLiteralInternal(item, visited));
1074
+ if (!isPlainObject(value)) return false;
1075
+ if (visited.has(value)) return false;
1076
+ visited.add(value);
1077
+ if ("@value" in value) return false;
1078
+ for (const [key, child] of Object.entries(value)) {
1079
+ if (TEMPORAL_DATE_TIME_IRIS.has(key)) {
1080
+ if (hasMalformedExpandedDateTimeLiteral(child)) return true;
1081
+ continue;
1082
+ }
1083
+ if (key === QUESTION_CLOSED_IRI) {
1084
+ if (hasMalformedExpandedQuestionClosedLiteral(child)) return true;
1085
+ continue;
1086
+ }
1087
+ if (TEMPORAL_DURATION_IRIS.has(key)) {
1088
+ if (hasMalformedExpandedDurationLiteral(child)) return true;
1089
+ continue;
1090
+ }
1091
+ if (hasMalformedKnownTemporalLiteralInternal(child, visited)) return true;
1092
+ }
1093
+ return false;
1094
+ }
1095
+ async function hasMalformedKnownTemporalLiteral(value, contextLoader) {
1096
+ try {
1097
+ return hasMalformedKnownTemporalLiteralInternal(await _fedify_vocab_runtime_jsonld.default.expand(value, {
1098
+ documentLoader: require_proof.getNormalizationContextLoader(contextLoader),
1099
+ keepFreeFloatingNodes: true
1100
+ }), /* @__PURE__ */ new Set());
1101
+ } catch {
1102
+ return false;
1103
+ }
1104
+ }
1105
+ //#endregion
1012
1106
  //#region src/federation/handler.ts
1107
+ const rawInboxContextFactorySymbol = Symbol("fedify.rawInboxContextFactory");
1108
+ function isRemoteContextLoadingFailure$1(error) {
1109
+ return error instanceof Error && typeof error.details === "object" && error.details != null && error.details.code === "loading remote context failed";
1110
+ }
1111
+ function isPermanentRemoteContextError$1(error) {
1112
+ if (!(error instanceof Error) || error.name !== "jsonld.InvalidUrl") return false;
1113
+ const details = error.details;
1114
+ if (details?.code === "invalid remote context") return true;
1115
+ return isRemoteContextLoadingFailure$1(error) && typeof details?.url === "string" && !URL.canParse(details.url) && require_proof.isClearlyMalformedContextReference(details.url);
1116
+ }
1117
+ function isInvalidJsonLdError(error) {
1118
+ if (!(error instanceof Error)) return false;
1119
+ const name = error.name;
1120
+ return name === "UnsafeJsonLdError" || error instanceof require_proof.InvalidContextReferenceError || isPermanentRemoteContextError$1(error) || name === "jsonld.SyntaxError" && !isRemoteContextLoadingFailure$1(error);
1121
+ }
1122
+ function isValidationTypeError(error) {
1123
+ return error instanceof TypeError && (/^(Invalid JSON-LD:|Invalid type:|Unexpected type:)/.test(error.message) || require_proof.isInvalidUrlTypeError(error));
1124
+ }
1125
+ function isPermanentActivityParseError(error) {
1126
+ return isInvalidJsonLdError(error) || isValidationTypeError(error);
1127
+ }
1128
+ function hasHttpSignatureHeaders(request) {
1129
+ return request.headers.has("Signature") || request.headers.has("Signature-Input");
1130
+ }
1131
+ function hasObjectIntegrityProof(json) {
1132
+ return typeof json === "object" && json != null && "proof" in json;
1133
+ }
1013
1134
  /**
1014
1135
  * Handles an actor request.
1015
1136
  * @template TContextData The context data to pass to the context.
@@ -1071,8 +1192,8 @@ async function handleObject(request, { values, context, objectDispatcher, author
1071
1192
  * @param parameters The parameters for handling the collection.
1072
1193
  * @returns A promise that resolves to an HTTP response.
1073
1194
  */
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, "_");
1195
+ async function handleCollection(request, { name: name$1, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, tracerProvider, onUnauthorized, onNotFound }) {
1196
+ const spanName = name$1.trim().replace(/\s+/g, "_");
1076
1197
  tracerProvider = tracerProvider ?? _opentelemetry_api.trace.getTracerProvider();
1077
1198
  const tracer = tracerProvider.getTracer(require_http.name, require_http.version);
1078
1199
  const cursor = new URL(request.url).searchParams.get("cursor");
@@ -1114,7 +1235,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
1114
1235
  collection = new _fedify_vocab.OrderedCollection({
1115
1236
  id: baseUri,
1116
1237
  totalItems: totalItems == null ? null : Number(totalItems),
1117
- items: filterCollectionItems(itemsOrResponse, name$2, filterPredicate)
1238
+ items: filterCollectionItems(itemsOrResponse, name$1, filterPredicate)
1118
1239
  });
1119
1240
  } else {
1120
1241
  const lastCursor = await collectionCallbacks.lastCursor?.(context, identifier);
@@ -1135,7 +1256,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
1135
1256
  } else {
1136
1257
  const uri = new URL(baseUri);
1137
1258
  uri.searchParams.set("cursor", cursor);
1138
- const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$2}`, {
1259
+ const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$1}`, {
1139
1260
  kind: _opentelemetry_api.SpanKind.SERVER,
1140
1261
  attributes: {
1141
1262
  "activitypub.collection.id": uri.href,
@@ -1179,7 +1300,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
1179
1300
  id: uri,
1180
1301
  prev,
1181
1302
  next,
1182
- items: filterCollectionItems(items, name$2, filterPredicate),
1303
+ items: filterCollectionItems(items, name$1, filterPredicate),
1183
1304
  partOf
1184
1305
  });
1185
1306
  }
@@ -1329,28 +1450,104 @@ async function handleInboxInternal(request, parameters, span) {
1329
1450
  });
1330
1451
  }
1331
1452
  const keyCache = new KvKeyCache(kv, kvPrefixes.publicKey, ctx);
1332
- let ldSigVerified;
1333
- try {
1334
- ldSigVerified = await require_proof.verifyJsonLd(json, {
1335
- contextLoader: ctx.contextLoader,
1336
- documentLoader: ctx.documentLoader,
1337
- keyCache,
1338
- tracerProvider
1453
+ const jsonWithoutSig = require_proof.detachSignature(json);
1454
+ const hasLdSignature = require_proof.hasSignature(json);
1455
+ const canAttemptAlternateAuthAfterLdSignatureFailure = skipSignatureVerification || hasHttpSignatureHeaders(request) || hasObjectIntegrityProof(jsonWithoutSig);
1456
+ let deferredLdSignatureError = void 0;
1457
+ const respondInvalidActivity = async (error) => {
1458
+ logger.error("Failed to parse activity:\n{error}", {
1459
+ recipient,
1460
+ activity: json,
1461
+ error
1339
1462
  });
1340
- } catch (error) {
1341
- if (error instanceof Error && error.name === "jsonld.SyntaxError") {
1342
- logger.error("Failed to parse JSON-LD:\n{error}", {
1463
+ try {
1464
+ await inboxErrorHandler?.(ctx, error);
1465
+ } catch (error) {
1466
+ logger.error("An unexpected error occurred in inbox error handler:\n{error}", {
1467
+ error,
1468
+ activity: json,
1469
+ recipient
1470
+ });
1471
+ }
1472
+ span.setStatus({
1473
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
1474
+ message: `Failed to parse activity:\n${error}`
1475
+ });
1476
+ return new Response("Invalid activity.", {
1477
+ status: 400,
1478
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
1479
+ });
1480
+ };
1481
+ let compactedJson = json;
1482
+ let compactedJsonWithoutSig = jsonWithoutSig;
1483
+ let ldSigVerified = false;
1484
+ if (hasLdSignature) {
1485
+ try {
1486
+ compactedJson = await require_proof.compactJsonLd(json, ctx.contextLoader);
1487
+ } catch (error) {
1488
+ if (isInvalidJsonLdError(error)) {
1489
+ logger.error("Failed to parse JSON-LD:\n{error}", {
1490
+ recipient,
1491
+ error
1492
+ });
1493
+ return new Response("Invalid JSON-LD.", {
1494
+ status: 400,
1495
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
1496
+ });
1497
+ }
1498
+ if (!canAttemptAlternateAuthAfterLdSignatureFailure) throw error;
1499
+ if (!skipSignatureVerification) deferredLdSignatureError = error;
1500
+ logger.debug("Failed to normalize JSON-LD for Linked Data Signatures; deferring to another authentication path only if it verifies:\n{error}", {
1343
1501
  recipient,
1344
1502
  error
1345
1503
  });
1346
- return new Response("Invalid JSON-LD.", {
1347
- status: 400,
1348
- headers: { "Content-Type": "text/plain; charset=utf-8" }
1349
- });
1350
1504
  }
1351
- ldSigVerified = false;
1505
+ if (compactedJson !== json) {
1506
+ compactedJsonWithoutSig = require_proof.detachSignature(compactedJson);
1507
+ try {
1508
+ ldSigVerified = await require_proof.verifyCompactJsonLd(compactedJson, {
1509
+ contextLoader: ctx.contextLoader,
1510
+ documentLoader: ctx.documentLoader,
1511
+ keyCache,
1512
+ tracerProvider
1513
+ });
1514
+ } catch (error) {
1515
+ if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(compactedJsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(error);
1516
+ if (isInvalidJsonLdError(error)) {
1517
+ logger.error("Failed to parse JSON-LD:\n{error}", {
1518
+ recipient,
1519
+ error
1520
+ });
1521
+ return new Response("Invalid JSON-LD.", {
1522
+ status: 400,
1523
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
1524
+ });
1525
+ }
1526
+ if (!canAttemptAlternateAuthAfterLdSignatureFailure) throw error;
1527
+ if (!skipSignatureVerification) try {
1528
+ await _fedify_vocab.Object.fromJsonLd(compactedJson, {
1529
+ contextLoader: require_proof.getNormalizationContextLoader(ctx.contextLoader),
1530
+ documentLoader: ctx.documentLoader,
1531
+ tracerProvider
1532
+ });
1533
+ } catch (parseError) {
1534
+ if (parseError instanceof RangeError && await hasMalformedKnownTemporalLiteral(compactedJsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(parseError);
1535
+ if (isInvalidJsonLdError(parseError)) {
1536
+ logger.error("Failed to parse JSON-LD:\n{error}", {
1537
+ recipient,
1538
+ error: parseError
1539
+ });
1540
+ return new Response("Invalid JSON-LD.", {
1541
+ status: 400,
1542
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
1543
+ });
1544
+ }
1545
+ deferredLdSignatureError = parseError;
1546
+ }
1547
+ ldSigVerified = false;
1548
+ }
1549
+ }
1352
1550
  }
1353
- const jsonWithoutSig = require_proof.detachSignature(json);
1354
1551
  let activity = null;
1355
1552
  let activityVerified = false;
1356
1553
  if (ldSigVerified) {
@@ -1358,7 +1555,16 @@ async function handleInboxInternal(request, parameters, span) {
1358
1555
  recipient,
1359
1556
  json
1360
1557
  });
1361
- activity = await _fedify_vocab.Activity.fromJsonLd(jsonWithoutSig, ctx);
1558
+ try {
1559
+ activity = await _fedify_vocab.Activity.fromJsonLd(compactedJsonWithoutSig, {
1560
+ ...ctx,
1561
+ contextLoader: require_proof.getNormalizationContextLoader(ctx.contextLoader)
1562
+ });
1563
+ } catch (error) {
1564
+ if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(compactedJsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(error);
1565
+ if (!isPermanentActivityParseError(error)) throw error;
1566
+ return await respondInvalidActivity(error);
1567
+ }
1362
1568
  activityVerified = true;
1363
1569
  } else {
1364
1570
  logger.debug("Linked Data Signatures are not verified.", {
@@ -1367,12 +1573,21 @@ async function handleInboxInternal(request, parameters, span) {
1367
1573
  });
1368
1574
  try {
1369
1575
  activity = await require_proof.verifyObject(_fedify_vocab.Activity, jsonWithoutSig, {
1370
- contextLoader: ctx.contextLoader,
1576
+ contextLoader: require_proof.wrapContextLoaderForJsonLd(ctx.contextLoader),
1371
1577
  documentLoader: ctx.documentLoader,
1372
1578
  keyCache,
1373
1579
  tracerProvider
1374
1580
  });
1375
1581
  } catch (error) {
1582
+ if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(jsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(error);
1583
+ if (deferredLdSignatureError != null) {
1584
+ logger.debug("Object Integrity Proof fallback did not supersede a deferred Linked Data Signature failure:\n{error}", {
1585
+ recipient,
1586
+ error
1587
+ });
1588
+ activity = null;
1589
+ }
1590
+ if (!isPermanentActivityParseError(error)) throw error;
1376
1591
  logger.error("Failed to parse activity:\n{error}", {
1377
1592
  recipient,
1378
1593
  activity: json,
@@ -1420,6 +1635,7 @@ async function handleInboxInternal(request, parameters, span) {
1420
1635
  tracerProvider
1421
1636
  });
1422
1637
  if (verification.verified === false) {
1638
+ if (deferredLdSignatureError != null) throw deferredLdSignatureError;
1423
1639
  const reason = verification.reason;
1424
1640
  logger.error("Failed to verify the request's HTTP Signatures.", {
1425
1641
  recipient,
@@ -1495,7 +1711,15 @@ async function handleInboxInternal(request, parameters, span) {
1495
1711
  }
1496
1712
  httpSigKey = verification.key;
1497
1713
  }
1498
- activity = await _fedify_vocab.Activity.fromJsonLd(jsonWithoutSig, ctx);
1714
+ try {
1715
+ activity = await _fedify_vocab.Activity.fromJsonLd(jsonWithoutSig, {
1716
+ ...ctx,
1717
+ contextLoader: require_proof.wrapContextLoaderForJsonLd(ctx.contextLoader)
1718
+ });
1719
+ } catch (error) {
1720
+ if (!isPermanentActivityParseError(error)) throw error;
1721
+ return await respondInvalidActivity(error);
1722
+ }
1499
1723
  }
1500
1724
  if (activity.id != null) span.setAttribute("activitypub.activity.id", activity.id.href);
1501
1725
  span.setAttribute("activitypub.activity.type", (0, _fedify_vocab.getTypeId)(activity).href);
@@ -1507,6 +1731,7 @@ async function handleInboxInternal(request, parameters, span) {
1507
1731
  "http_signatures.key_id": httpSigKey?.id?.href ?? ""
1508
1732
  });
1509
1733
  if (httpSigKey != null && !await require_proof.doesActorOwnKey(activity, httpSigKey, ctx)) {
1734
+ if (deferredLdSignatureError != null) throw deferredLdSignatureError;
1510
1735
  logger.error("The signer ({keyId}) and the actor ({actorId}) do not match.", {
1511
1736
  activity: json,
1512
1737
  recipient,
@@ -1531,10 +1756,14 @@ async function handleInboxInternal(request, parameters, span) {
1531
1756
  const routeResult = await routeActivity({
1532
1757
  context: ctx,
1533
1758
  json,
1759
+ originalJson: json,
1760
+ normalizedActivity: hasLdSignature && compactedJson !== json ? compactedJson : void 0,
1761
+ ldSignatureVerified: hasLdSignature ? ldSigVerified : void 0,
1534
1762
  activity,
1535
1763
  recipient,
1536
1764
  inboxListeners,
1537
1765
  inboxContextFactory,
1766
+ listenerInboxContextFactory: ldSigVerified ? inboxContextFactory[rawInboxContextFactorySymbol] : void 0,
1538
1767
  inboxErrorHandler,
1539
1768
  kv,
1540
1769
  kvPrefixes,
@@ -1652,8 +1881,8 @@ var CustomCollectionHandler = class {
1652
1881
  * @param CollectionPage The CollectionPage constructor.
1653
1882
  * @param filterPredicate Optional filter predicate for items.
1654
1883
  */
1655
- constructor(name$1, values, context, callbacks, tracerProvider = _opentelemetry_api.trace.getTracerProvider(), Collection, CollectionPage, filterPredicate) {
1656
- this.name = name$1;
1884
+ constructor(name$2, values, context, callbacks, tracerProvider = _opentelemetry_api.trace.getTracerProvider(), Collection, CollectionPage, filterPredicate) {
1885
+ this.name = name$2;
1657
1886
  this.values = values;
1658
1887
  this.context = context;
1659
1888
  this.callbacks = callbacks;
@@ -2445,6 +2674,18 @@ async function handleWebFingerInternal(request, { context, host, actorDispatcher
2445
2674
  }
2446
2675
  //#endregion
2447
2676
  //#region src/federation/middleware.ts
2677
+ function isRemoteContextLoadingFailure(error) {
2678
+ return error instanceof Error && typeof error.details === "object" && error.details != null && error.details.code === "loading remote context failed";
2679
+ }
2680
+ function isPermanentRemoteContextError(error) {
2681
+ if (!(error instanceof Error) || error.name !== "jsonld.InvalidUrl") return false;
2682
+ const details = error.details;
2683
+ if (details?.code === "invalid remote context") return true;
2684
+ return isRemoteContextLoadingFailure(error) && typeof details?.url === "string" && !URL.canParse(details.url) && require_proof.isClearlyMalformedContextReference(details.url);
2685
+ }
2686
+ function isPermanentInboxParseError(error) {
2687
+ return error instanceof Error && (error.name === "UnsafeJsonLdError" || error instanceof require_proof.InvalidContextReferenceError || isPermanentRemoteContextError(error) || error.name === "jsonld.SyntaxError" && !isRemoteContextLoadingFailure(error)) || error instanceof TypeError && (/^(Invalid JSON-LD:|Invalid type:|Unexpected type:)/.test(error.message) || require_proof.isInvalidUrlTypeError(error));
2688
+ }
2448
2689
  /**
2449
2690
  * Create a new {@link Federation} instance.
2450
2691
  * @param parameters Parameters for initializing the instance.
@@ -2825,7 +3066,7 @@ var FederationImpl = class extends FederationBuilderImpl {
2825
3066
  }
2826
3067
  logger.info("Successfully sent activity {activityId} to {inbox}.", { ...logData });
2827
3068
  }
2828
- async #listenInboxMessage(ctxData, message, span) {
3069
+ async #listenInboxMessage(ctxData, message, _span) {
2829
3070
  const logger = (0, _logtape_logtape.getLogger)([
2830
3071
  "fedify",
2831
3072
  "federation",
@@ -2838,60 +3079,28 @@ var FederationImpl = class extends FederationBuilderImpl {
2838
3079
  const identity = await this.sharedInboxKeyDispatcher(context);
2839
3080
  if (identity != null) context = this.#createContext(baseUrl, ctxData, { documentLoader: "identifier" in identity || "username" in identity ? await context.getDocumentLoader(identity) : context.getDocumentLoader(identity) });
2840
3081
  }
2841
- const activity = await _fedify_vocab.Activity.fromJsonLd(message.activity, context);
2842
- span.setAttribute("activitypub.activity.type", (0, _fedify_vocab.getTypeId)(activity).href);
2843
- if (activity.id != null) span.setAttribute("activitypub.activity.id", activity.id.href);
2844
- const cacheKey = activity.id == null ? null : [
2845
- ...this.kvPrefixes.activityIdempotence,
2846
- context.origin,
2847
- activity.id.href
2848
- ];
2849
- if (cacheKey != null) {
2850
- if (await this.kv.get(cacheKey) === true) {
2851
- logger.debug("Activity {activityId} has already been processed.", {
2852
- activityId: activity.id?.href,
2853
- activity: message.activity,
2854
- recipient: message.identifier
2855
- });
2856
- return;
2857
- }
2858
- }
2859
3082
  await this._getTracer().startActiveSpan("activitypub.dispatch_inbox_listener", { kind: _opentelemetry_api.SpanKind.INTERNAL }, async (span) => {
2860
- const dispatched = this.inboxListeners?.dispatchWithClass(activity);
2861
- if (dispatched == null) {
2862
- logger.error("Unsupported activity type:\n{activity}", {
2863
- activityId: activity.id?.href,
2864
- activity: message.activity,
2865
- recipient: message.identifier,
2866
- trial: message.attempt
2867
- });
2868
- span.setStatus({
2869
- code: _opentelemetry_api.SpanStatusCode.ERROR,
2870
- message: `Unsupported activity type: ${(0, _fedify_vocab.getTypeId)(activity).href}`
2871
- });
2872
- span.end();
2873
- return;
2874
- }
2875
- const { class: cls, listener } = dispatched;
2876
- span.updateName(`activitypub.dispatch_inbox_listener ${cls.name}`);
2877
- try {
2878
- await listener(context.toInboxContext(message.identifier, message.activity, activity.id?.href, (0, _fedify_vocab.getTypeId)(activity).href), activity);
2879
- } catch (error) {
3083
+ let activity = null;
3084
+ let cacheKey = null;
3085
+ const reportInboxError = async (error) => {
2880
3086
  try {
2881
3087
  await this.inboxErrorHandler?.(context, error);
2882
3088
  } catch (error) {
2883
3089
  logger.error("An unexpected error occurred in inbox error handler:\n{error}", {
2884
3090
  error,
2885
3091
  trial: message.attempt,
2886
- activityId: activity.id?.href,
3092
+ activityId: activity?.id?.href,
2887
3093
  activity: message.activity,
2888
3094
  recipient: message.identifier
2889
3095
  });
2890
3096
  }
3097
+ };
3098
+ const handleRetriableFailure = async (error) => {
3099
+ await reportInboxError(error);
2891
3100
  if (this.inboxQueue?.nativeRetrial) {
2892
3101
  logger.error("Failed to process the incoming activity {activityId}; backend will handle retry:\n{error}", {
2893
3102
  error,
2894
- activityId: activity.id?.href,
3103
+ activityId: activity?.id?.href,
2895
3104
  activity: message.activity,
2896
3105
  recipient: message.identifier
2897
3106
  });
@@ -2910,17 +3119,25 @@ var FederationImpl = class extends FederationBuilderImpl {
2910
3119
  logger.error("Failed to process the incoming activity {activityId} (attempt #{attempt}); retry...:\n{error}", {
2911
3120
  error,
2912
3121
  attempt: message.attempt,
2913
- activityId: activity.id?.href,
3122
+ activityId: activity?.id?.href,
2914
3123
  activity: message.activity,
2915
3124
  recipient: message.identifier
2916
3125
  });
3126
+ if (this.inboxQueue == null) {
3127
+ span.setStatus({
3128
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
3129
+ message: String(error)
3130
+ });
3131
+ span.end();
3132
+ throw error;
3133
+ }
2917
3134
  await this.inboxQueue?.enqueue({
2918
3135
  ...message,
2919
3136
  attempt: message.attempt + 1
2920
3137
  }, { delay: Temporal.Duration.compare(delay, { seconds: 0 }) < 0 ? Temporal.Duration.from({ seconds: 0 }) : delay });
2921
3138
  } else logger.error("Failed to process the incoming activity {activityId} after {trial} attempts; giving up:\n{error}", {
2922
3139
  error,
2923
- activityId: activity.id?.href,
3140
+ activityId: activity?.id?.href,
2924
3141
  activity: message.activity,
2925
3142
  recipient: message.identifier
2926
3143
  });
@@ -2929,11 +3146,108 @@ var FederationImpl = class extends FederationBuilderImpl {
2929
3146
  message: String(error)
2930
3147
  });
2931
3148
  span.end();
3149
+ };
3150
+ let dispatched;
3151
+ let parseInput = void 0;
3152
+ let parseContextLoader = context.contextLoader;
3153
+ try {
3154
+ const hasSignatureField = require_proof.hasSignature(message.activity);
3155
+ const shouldParseFromNormalizedSignedPayload = message.ldSignatureVerified === true || message.normalizedActivity != null || message.ldSignatureVerified == null && hasSignatureField;
3156
+ const parseContext = hasSignatureField ? {
3157
+ ...context,
3158
+ contextLoader: require_proof.getNormalizationContextLoader(context.contextLoader)
3159
+ } : {
3160
+ ...context,
3161
+ contextLoader: require_proof.wrapContextLoaderForJsonLd(context.contextLoader)
3162
+ };
3163
+ parseContextLoader = parseContext.contextLoader;
3164
+ let normalizedActivity;
3165
+ if (shouldParseFromNormalizedSignedPayload) {
3166
+ normalizedActivity = message.normalizedActivity ?? await require_proof.compactJsonLd(message.activity, context.contextLoader);
3167
+ require_proof.assertSafeJsonLd(normalizedActivity);
3168
+ }
3169
+ parseInput = shouldParseFromNormalizedSignedPayload ? require_proof.detachSignature(normalizedActivity) : hasSignatureField ? require_proof.detachSignature(message.activity) : message.activity;
3170
+ activity = await _fedify_vocab.Activity.fromJsonLd(parseInput, parseContext);
3171
+ span.setAttribute("activitypub.activity.type", (0, _fedify_vocab.getTypeId)(activity).href);
3172
+ if (activity.id != null) span.setAttribute("activitypub.activity.id", activity.id.href);
3173
+ cacheKey = activity.id == null ? null : [
3174
+ ...this.kvPrefixes.activityIdempotence,
3175
+ context.origin,
3176
+ activity.id.href
3177
+ ];
3178
+ if (cacheKey != null) {
3179
+ if (await this.kv.get(cacheKey) === true) {
3180
+ logger.debug("Activity {activityId} has already been processed.", {
3181
+ activityId: activity.id?.href,
3182
+ activity: message.activity,
3183
+ recipient: message.identifier
3184
+ });
3185
+ span.end();
3186
+ return;
3187
+ }
3188
+ }
3189
+ dispatched = this.inboxListeners?.dispatchWithClass(activity);
3190
+ } catch (error) {
3191
+ if (activity == null && error instanceof RangeError && await hasMalformedKnownTemporalLiteral(parseInput, parseContextLoader)) {
3192
+ await reportInboxError(error);
3193
+ logger.error("Failed to parse the queued incoming activity {activityId}:\n{error}", {
3194
+ error,
3195
+ trial: message.attempt,
3196
+ activityId: null,
3197
+ activity: message.activity,
3198
+ recipient: message.identifier
3199
+ });
3200
+ span.setStatus({
3201
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
3202
+ message: String(error)
3203
+ });
3204
+ span.end();
3205
+ return;
3206
+ }
3207
+ if (isPermanentInboxParseError(error)) {
3208
+ await reportInboxError(error);
3209
+ logger.error("Failed to parse the queued incoming activity {activityId}:\n{error}", {
3210
+ error,
3211
+ trial: message.attempt,
3212
+ activityId: activity?.id?.href,
3213
+ activity: message.activity,
3214
+ recipient: message.identifier
3215
+ });
3216
+ span.setStatus({
3217
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
3218
+ message: String(error)
3219
+ });
3220
+ span.end();
3221
+ return;
3222
+ }
3223
+ await handleRetriableFailure(error);
3224
+ return;
3225
+ }
3226
+ if (dispatched == null) {
3227
+ logger.error("Unsupported activity type:\n{activity}", {
3228
+ activityId: activity.id?.href,
3229
+ activity: message.activity,
3230
+ recipient: message.identifier,
3231
+ trial: message.attempt
3232
+ });
3233
+ span.setStatus({
3234
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
3235
+ message: `Unsupported activity type: ${(0, _fedify_vocab.getTypeId)(activity).href}`
3236
+ });
3237
+ span.end();
3238
+ return;
3239
+ }
3240
+ const { class: cls, listener } = dispatched;
3241
+ span.updateName(`activitypub.dispatch_inbox_listener ${cls.name}`);
3242
+ try {
3243
+ await listener(context.toInboxContext(message.identifier, message.activity, activity.id?.href, (0, _fedify_vocab.getTypeId)(activity).href), activity);
3244
+ } catch (error) {
3245
+ await handleRetriableFailure(error);
2932
3246
  return;
2933
3247
  }
2934
3248
  if (cacheKey != null) await this.kv.set(cacheKey, true, { ttl: Temporal.Duration.from({ days: 1 }) });
2935
3249
  logger.info("Activity {activityId} has been processed.", {
2936
- activityId: activity.id?.href,
3250
+ activityId: activity?.id?.href,
2937
3251
  activity: message.activity,
2938
3252
  recipient: message.identifier
2939
3253
  });
@@ -3279,16 +3593,18 @@ var FederationImpl = class extends FederationBuilderImpl {
3279
3593
  onNotFound
3280
3594
  });
3281
3595
  context = this.#createContext(request, contextData, { documentLoader: await context.getDocumentLoader({ identifier: route.values.identifier }) });
3282
- case "sharedInbox":
3596
+ case "sharedInbox": {
3283
3597
  if (routeName !== "inbox" && this.sharedInboxKeyDispatcher != null) {
3284
3598
  const identity = await this.sharedInboxKeyDispatcher(context);
3285
3599
  if (identity != null) context = this.#createContext(request, contextData, { documentLoader: "identifier" in identity || "username" in identity ? await context.getDocumentLoader(identity) : context.getDocumentLoader(identity) });
3286
3600
  }
3287
3601
  if (!this.manuallyStartQueue) this._startQueueInternal(contextData);
3602
+ const inboxContextFactory = context.toInboxContext.bind(context);
3603
+ inboxContextFactory[rawInboxContextFactorySymbol] = context.toInboxContext.bind(context);
3288
3604
  return await handleInbox(request, {
3289
3605
  recipient: route.values.identifier ?? null,
3290
3606
  context,
3291
- inboxContextFactory: context.toInboxContext.bind(context),
3607
+ inboxContextFactory,
3292
3608
  kv: this.kv,
3293
3609
  kvPrefixes: this.kvPrefixes,
3294
3610
  queue: this.inboxQueue,
@@ -3303,6 +3619,7 @@ var FederationImpl = class extends FederationBuilderImpl {
3303
3619
  tracerProvider: this.tracerProvider,
3304
3620
  idempotencyStrategy: this.idempotencyStrategy
3305
3621
  });
3622
+ }
3306
3623
  case "following": return await handleCollection(request, {
3307
3624
  name: "following",
3308
3625
  identifier: route.values.identifier,
@@ -3978,6 +4295,7 @@ var ContextImpl = class ContextImpl {
3978
4295
  const routeResult = await routeActivity({
3979
4296
  context: this,
3980
4297
  json,
4298
+ ldSignatureVerified: false,
3981
4299
  activity,
3982
4300
  recipient,
3983
4301
  inboxListeners: this.federation.inboxListeners,
@@ -4092,6 +4410,14 @@ var RequestContextImpl = class RequestContextImpl extends ContextImpl {
4092
4410
  };
4093
4411
  var InboxContextImpl = class InboxContextImpl extends ContextImpl {
4094
4412
  recipient;
4413
+ /**
4414
+ * The original received activity payload.
4415
+ *
4416
+ * Fedify may normalize a Linked Data Signature payload internally for safe
4417
+ * parsing, but forwarding must keep the sender's payload unchanged so
4418
+ * third-party signatures/proofs remain intact.
4419
+ * @internal
4420
+ */
4095
4421
  activity;
4096
4422
  activityId;
4097
4423
  activityType;