@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,4 +1,4 @@
1
1
  const { Temporal } = require("@js-temporal/polyfill");
2
2
  const { URLPattern } = require("urlpattern-polyfill");
3
- const require_middleware = require("./middleware-DFcgN1cH.cjs");
3
+ const require_middleware = require("./middleware-BcbU5lKS.cjs");
4
4
  exports.FederationImpl = require_middleware.FederationImpl;
@@ -2,24 +2,25 @@ import { Temporal } from "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
4
  import { n as RouterError } from "./router-CrMLXoOr.mjs";
5
- import { n as version, t as name } from "./deno-Ddb3P7G7.mjs";
5
+ import { n as version, t as name } from "./deno-bqnW1QlD.mjs";
6
6
  import { t as formatAcceptSignature } from "./accept-Dd__NiUL.mjs";
7
- import { a as importJwk, o as validateCryptoKey, t as exportJwk } from "./key-WSedbAA-.mjs";
8
- import { l as verifyRequest, o as parseRfc9421SignatureInput, u as verifyRequestDetailed } from "./http-DRDpStn2.mjs";
9
- import { t as getAuthenticatedDocumentLoader } from "./docloader-CK8tewt1.mjs";
10
- import { n as kvCache } from "./kv-cache-B01V7s3h.mjs";
11
- import { a as signJsonLd, i as hasSignature, o as verifyJsonLd, r as detachSignature } from "./ld-OBpaLfHR.mjs";
12
- import { n as getKeyOwner, t as doesActorOwnKey } from "./owner-BceqCVOs.mjs";
13
- import { n as signObject, r as verifyObject } from "./proof-BojC8L94.mjs";
7
+ import { a as importJwk, o as validateCryptoKey, t as exportJwk } from "./key-B3OBz6A_.mjs";
8
+ import { l as verifyRequest, o as parseRfc9421SignatureInput, u as verifyRequestDetailed } from "./http-DvhFwy2E.mjs";
9
+ import { t as getAuthenticatedDocumentLoader } from "./docloader-D-Fu0kBF.mjs";
10
+ import { n as kvCache } from "./kv-cache-FsT1ljhs.mjs";
11
+ import { a as compactJsonLd, c as getNormalizationContextLoader, d as isInvalidUrlTypeError, f as signJsonLd, g as wrapContextLoaderForJsonLd, l as hasSignature, p as verifyCompactJsonLd, r as assertSafeJsonLd, s as detachSignature, t as InvalidContextReferenceError, u as isClearlyMalformedContextReference } from "./ld-DTxYKZTT.mjs";
12
+ import { n as getKeyOwner, t as doesActorOwnKey } from "./owner-DzcnuWYo.mjs";
13
+ import { n as signObject, r as verifyObject } from "./proof-DmVGi7mI.mjs";
14
14
  import { t as getNodeInfo } from "./client-DEpOVgY1.mjs";
15
15
  import { t as nodeInfoToJson } from "./types-DCP0WLdt.mjs";
16
- import { n as routeActivity } from "./inbox-DTuPJEma.mjs";
17
- import { t as FederationBuilderImpl } from "./builder-D9hlyCx5.mjs";
16
+ import { n as routeActivity } from "./inbox-7b3VYnec.mjs";
17
+ import { t as FederationBuilderImpl } from "./builder-BBifZZ5G.mjs";
18
18
  import { t as buildCollectionSynchronizationHeader } from "./collection-BD6-SZ6O.mjs";
19
19
  import { t as KvKeyCache } from "./keycache-CCSwkQcY.mjs";
20
20
  import { t as acceptsJsonLd } from "./negotiation-DnsfFF8I.mjs";
21
- import { t as createExponentialBackoffPolicy } from "./retry-B_E3V_Dx.mjs";
22
- import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "./send-Buz5Gy8x.mjs";
21
+ import { t as hasMalformedKnownTemporalLiteral } from "./temporal-CmJkd-25.mjs";
22
+ import { t as createExponentialBackoffPolicy } from "./retry-CJL0poaU.mjs";
23
+ import { n as extractInboxes, r as sendActivity, t as SendActivityError } from "./send-5ulOKVCW.mjs";
23
24
  import { Activity, Collection, CollectionPage, CryptographicKey, Link, Multikey, Object as Object$1, OrderedCollection, OrderedCollectionPage, getTypeId, lookupObject, traverseCollection } from "@fedify/vocab";
24
25
  import { lookupWebFinger } from "@fedify/webfinger";
25
26
  import { SpanKind, SpanStatusCode, context, propagation, trace } from "@opentelemetry/api";
@@ -154,6 +155,33 @@ function handleNodeInfoJrd(_request, context) {
154
155
  }
155
156
  //#endregion
156
157
  //#region src/federation/handler.ts
158
+ const rawInboxContextFactorySymbol = Symbol("fedify.rawInboxContextFactory");
159
+ function isRemoteContextLoadingFailure$1(error) {
160
+ return error instanceof Error && typeof error.details === "object" && error.details != null && error.details.code === "loading remote context failed";
161
+ }
162
+ function isPermanentRemoteContextError$1(error) {
163
+ if (!(error instanceof Error) || error.name !== "jsonld.InvalidUrl") return false;
164
+ const details = error.details;
165
+ if (details?.code === "invalid remote context") return true;
166
+ return isRemoteContextLoadingFailure$1(error) && typeof details?.url === "string" && !URL.canParse(details.url) && isClearlyMalformedContextReference(details.url);
167
+ }
168
+ function isInvalidJsonLdError(error) {
169
+ if (!(error instanceof Error)) return false;
170
+ const name = error.name;
171
+ return name === "UnsafeJsonLdError" || error instanceof InvalidContextReferenceError || isPermanentRemoteContextError$1(error) || name === "jsonld.SyntaxError" && !isRemoteContextLoadingFailure$1(error);
172
+ }
173
+ function isValidationTypeError(error) {
174
+ return error instanceof TypeError && (/^(Invalid JSON-LD:|Invalid type:|Unexpected type:)/.test(error.message) || isInvalidUrlTypeError(error));
175
+ }
176
+ function isPermanentActivityParseError(error) {
177
+ return isInvalidJsonLdError(error) || isValidationTypeError(error);
178
+ }
179
+ function hasHttpSignatureHeaders(request) {
180
+ return request.headers.has("Signature") || request.headers.has("Signature-Input");
181
+ }
182
+ function hasObjectIntegrityProof(json) {
183
+ return typeof json === "object" && json != null && "proof" in json;
184
+ }
157
185
  /**
158
186
  * Handles an actor request.
159
187
  * @template TContextData The context data to pass to the context.
@@ -215,8 +243,8 @@ async function handleObject(request, { values, context, objectDispatcher, author
215
243
  * @param parameters The parameters for handling the collection.
216
244
  * @returns A promise that resolves to an HTTP response.
217
245
  */
218
- async function handleCollection(request, { name: name$2, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, tracerProvider, onUnauthorized, onNotFound }) {
219
- const spanName = name$2.trim().replace(/\s+/g, "_");
246
+ async function handleCollection(request, { name: name$1, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, tracerProvider, onUnauthorized, onNotFound }) {
247
+ const spanName = name$1.trim().replace(/\s+/g, "_");
220
248
  tracerProvider = tracerProvider ?? trace.getTracerProvider();
221
249
  const tracer = tracerProvider.getTracer(name, version);
222
250
  const cursor = new URL(request.url).searchParams.get("cursor");
@@ -258,7 +286,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
258
286
  collection = new OrderedCollection({
259
287
  id: baseUri,
260
288
  totalItems: totalItems == null ? null : Number(totalItems),
261
- items: filterCollectionItems(itemsOrResponse, name$2, filterPredicate)
289
+ items: filterCollectionItems(itemsOrResponse, name$1, filterPredicate)
262
290
  });
263
291
  } else {
264
292
  const lastCursor = await collectionCallbacks.lastCursor?.(context, identifier);
@@ -279,7 +307,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
279
307
  } else {
280
308
  const uri = new URL(baseUri);
281
309
  uri.searchParams.set("cursor", cursor);
282
- const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$2}`, {
310
+ const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$1}`, {
283
311
  kind: SpanKind.SERVER,
284
312
  attributes: {
285
313
  "activitypub.collection.id": uri.href,
@@ -323,7 +351,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
323
351
  id: uri,
324
352
  prev,
325
353
  next,
326
- items: filterCollectionItems(items, name$2, filterPredicate),
354
+ items: filterCollectionItems(items, name$1, filterPredicate),
327
355
  partOf
328
356
  });
329
357
  }
@@ -473,28 +501,104 @@ async function handleInboxInternal(request, parameters, span) {
473
501
  });
474
502
  }
475
503
  const keyCache = new KvKeyCache(kv, kvPrefixes.publicKey, ctx);
476
- let ldSigVerified;
477
- try {
478
- ldSigVerified = await verifyJsonLd(json, {
479
- contextLoader: ctx.contextLoader,
480
- documentLoader: ctx.documentLoader,
481
- keyCache,
482
- tracerProvider
504
+ const jsonWithoutSig = detachSignature(json);
505
+ const hasLdSignature = hasSignature(json);
506
+ const canAttemptAlternateAuthAfterLdSignatureFailure = skipSignatureVerification || hasHttpSignatureHeaders(request) || hasObjectIntegrityProof(jsonWithoutSig);
507
+ let deferredLdSignatureError = void 0;
508
+ const respondInvalidActivity = async (error) => {
509
+ logger.error("Failed to parse activity:\n{error}", {
510
+ recipient,
511
+ activity: json,
512
+ error
483
513
  });
484
- } catch (error) {
485
- if (error instanceof Error && error.name === "jsonld.SyntaxError") {
486
- logger.error("Failed to parse JSON-LD:\n{error}", {
514
+ try {
515
+ await inboxErrorHandler?.(ctx, error);
516
+ } catch (error) {
517
+ logger.error("An unexpected error occurred in inbox error handler:\n{error}", {
518
+ error,
519
+ activity: json,
520
+ recipient
521
+ });
522
+ }
523
+ span.setStatus({
524
+ code: SpanStatusCode.ERROR,
525
+ message: `Failed to parse activity:\n${error}`
526
+ });
527
+ return new Response("Invalid activity.", {
528
+ status: 400,
529
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
530
+ });
531
+ };
532
+ let compactedJson = json;
533
+ let compactedJsonWithoutSig = jsonWithoutSig;
534
+ let ldSigVerified = false;
535
+ if (hasLdSignature) {
536
+ try {
537
+ compactedJson = await compactJsonLd(json, ctx.contextLoader);
538
+ } catch (error) {
539
+ if (isInvalidJsonLdError(error)) {
540
+ logger.error("Failed to parse JSON-LD:\n{error}", {
541
+ recipient,
542
+ error
543
+ });
544
+ return new Response("Invalid JSON-LD.", {
545
+ status: 400,
546
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
547
+ });
548
+ }
549
+ if (!canAttemptAlternateAuthAfterLdSignatureFailure) throw error;
550
+ if (!skipSignatureVerification) deferredLdSignatureError = error;
551
+ logger.debug("Failed to normalize JSON-LD for Linked Data Signatures; deferring to another authentication path only if it verifies:\n{error}", {
487
552
  recipient,
488
553
  error
489
554
  });
490
- return new Response("Invalid JSON-LD.", {
491
- status: 400,
492
- headers: { "Content-Type": "text/plain; charset=utf-8" }
493
- });
494
555
  }
495
- ldSigVerified = false;
556
+ if (compactedJson !== json) {
557
+ compactedJsonWithoutSig = detachSignature(compactedJson);
558
+ try {
559
+ ldSigVerified = await verifyCompactJsonLd(compactedJson, {
560
+ contextLoader: ctx.contextLoader,
561
+ documentLoader: ctx.documentLoader,
562
+ keyCache,
563
+ tracerProvider
564
+ });
565
+ } catch (error) {
566
+ if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(compactedJsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(error);
567
+ if (isInvalidJsonLdError(error)) {
568
+ logger.error("Failed to parse JSON-LD:\n{error}", {
569
+ recipient,
570
+ error
571
+ });
572
+ return new Response("Invalid JSON-LD.", {
573
+ status: 400,
574
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
575
+ });
576
+ }
577
+ if (!canAttemptAlternateAuthAfterLdSignatureFailure) throw error;
578
+ if (!skipSignatureVerification) try {
579
+ await Object$1.fromJsonLd(compactedJson, {
580
+ contextLoader: getNormalizationContextLoader(ctx.contextLoader),
581
+ documentLoader: ctx.documentLoader,
582
+ tracerProvider
583
+ });
584
+ } catch (parseError) {
585
+ if (parseError instanceof RangeError && await hasMalformedKnownTemporalLiteral(compactedJsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(parseError);
586
+ if (isInvalidJsonLdError(parseError)) {
587
+ logger.error("Failed to parse JSON-LD:\n{error}", {
588
+ recipient,
589
+ error: parseError
590
+ });
591
+ return new Response("Invalid JSON-LD.", {
592
+ status: 400,
593
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
594
+ });
595
+ }
596
+ deferredLdSignatureError = parseError;
597
+ }
598
+ ldSigVerified = false;
599
+ }
600
+ }
496
601
  }
497
- const jsonWithoutSig = detachSignature(json);
498
602
  let activity = null;
499
603
  let activityVerified = false;
500
604
  if (ldSigVerified) {
@@ -502,7 +606,16 @@ async function handleInboxInternal(request, parameters, span) {
502
606
  recipient,
503
607
  json
504
608
  });
505
- activity = await Activity.fromJsonLd(jsonWithoutSig, ctx);
609
+ try {
610
+ activity = await Activity.fromJsonLd(compactedJsonWithoutSig, {
611
+ ...ctx,
612
+ contextLoader: getNormalizationContextLoader(ctx.contextLoader)
613
+ });
614
+ } catch (error) {
615
+ if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(compactedJsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(error);
616
+ if (!isPermanentActivityParseError(error)) throw error;
617
+ return await respondInvalidActivity(error);
618
+ }
506
619
  activityVerified = true;
507
620
  } else {
508
621
  logger.debug("Linked Data Signatures are not verified.", {
@@ -511,12 +624,21 @@ async function handleInboxInternal(request, parameters, span) {
511
624
  });
512
625
  try {
513
626
  activity = await verifyObject(Activity, jsonWithoutSig, {
514
- contextLoader: ctx.contextLoader,
627
+ contextLoader: wrapContextLoaderForJsonLd(ctx.contextLoader),
515
628
  documentLoader: ctx.documentLoader,
516
629
  keyCache,
517
630
  tracerProvider
518
631
  });
519
632
  } catch (error) {
633
+ if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(jsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(error);
634
+ if (deferredLdSignatureError != null) {
635
+ logger.debug("Object Integrity Proof fallback did not supersede a deferred Linked Data Signature failure:\n{error}", {
636
+ recipient,
637
+ error
638
+ });
639
+ activity = null;
640
+ }
641
+ if (!isPermanentActivityParseError(error)) throw error;
520
642
  logger.error("Failed to parse activity:\n{error}", {
521
643
  recipient,
522
644
  activity: json,
@@ -564,6 +686,7 @@ async function handleInboxInternal(request, parameters, span) {
564
686
  tracerProvider
565
687
  });
566
688
  if (verification.verified === false) {
689
+ if (deferredLdSignatureError != null) throw deferredLdSignatureError;
567
690
  const reason = verification.reason;
568
691
  logger.error("Failed to verify the request's HTTP Signatures.", {
569
692
  recipient,
@@ -639,7 +762,15 @@ async function handleInboxInternal(request, parameters, span) {
639
762
  }
640
763
  httpSigKey = verification.key;
641
764
  }
642
- activity = await Activity.fromJsonLd(jsonWithoutSig, ctx);
765
+ try {
766
+ activity = await Activity.fromJsonLd(jsonWithoutSig, {
767
+ ...ctx,
768
+ contextLoader: wrapContextLoaderForJsonLd(ctx.contextLoader)
769
+ });
770
+ } catch (error) {
771
+ if (!isPermanentActivityParseError(error)) throw error;
772
+ return await respondInvalidActivity(error);
773
+ }
643
774
  }
644
775
  if (activity.id != null) span.setAttribute("activitypub.activity.id", activity.id.href);
645
776
  span.setAttribute("activitypub.activity.type", getTypeId(activity).href);
@@ -651,6 +782,7 @@ async function handleInboxInternal(request, parameters, span) {
651
782
  "http_signatures.key_id": httpSigKey?.id?.href ?? ""
652
783
  });
653
784
  if (httpSigKey != null && !await doesActorOwnKey(activity, httpSigKey, ctx)) {
785
+ if (deferredLdSignatureError != null) throw deferredLdSignatureError;
654
786
  logger.error("The signer ({keyId}) and the actor ({actorId}) do not match.", {
655
787
  activity: json,
656
788
  recipient,
@@ -675,10 +807,14 @@ async function handleInboxInternal(request, parameters, span) {
675
807
  const routeResult = await routeActivity({
676
808
  context: ctx,
677
809
  json,
810
+ originalJson: json,
811
+ normalizedActivity: hasLdSignature && compactedJson !== json ? compactedJson : void 0,
812
+ ldSignatureVerified: hasLdSignature ? ldSigVerified : void 0,
678
813
  activity,
679
814
  recipient,
680
815
  inboxListeners,
681
816
  inboxContextFactory,
817
+ listenerInboxContextFactory: ldSigVerified ? inboxContextFactory[rawInboxContextFactorySymbol] : void 0,
682
818
  inboxErrorHandler,
683
819
  kv,
684
820
  kvPrefixes,
@@ -796,8 +932,8 @@ var CustomCollectionHandler = class {
796
932
  * @param CollectionPage The CollectionPage constructor.
797
933
  * @param filterPredicate Optional filter predicate for items.
798
934
  */
799
- constructor(name$1, values, context, callbacks, tracerProvider = trace.getTracerProvider(), Collection, CollectionPage, filterPredicate) {
800
- this.name = name$1;
935
+ constructor(name$2, values, context, callbacks, tracerProvider = trace.getTracerProvider(), Collection, CollectionPage, filterPredicate) {
936
+ this.name = name$2;
801
937
  this.values = values;
802
938
  this.context = context;
803
939
  this.callbacks = callbacks;
@@ -1339,6 +1475,18 @@ async function handleWebFingerInternal(request, { context, host, actorDispatcher
1339
1475
  }
1340
1476
  //#endregion
1341
1477
  //#region src/federation/middleware.ts
1478
+ function isRemoteContextLoadingFailure(error) {
1479
+ return error instanceof Error && typeof error.details === "object" && error.details != null && error.details.code === "loading remote context failed";
1480
+ }
1481
+ function isPermanentRemoteContextError(error) {
1482
+ if (!(error instanceof Error) || error.name !== "jsonld.InvalidUrl") return false;
1483
+ const details = error.details;
1484
+ if (details?.code === "invalid remote context") return true;
1485
+ return isRemoteContextLoadingFailure(error) && typeof details?.url === "string" && !URL.canParse(details.url) && isClearlyMalformedContextReference(details.url);
1486
+ }
1487
+ function isPermanentInboxParseError(error) {
1488
+ 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));
1489
+ }
1342
1490
  /**
1343
1491
  * Create a new {@link Federation} instance.
1344
1492
  * @param parameters Parameters for initializing the instance.
@@ -1719,7 +1867,7 @@ var FederationImpl = class extends FederationBuilderImpl {
1719
1867
  }
1720
1868
  logger.info("Successfully sent activity {activityId} to {inbox}.", { ...logData });
1721
1869
  }
1722
- async #listenInboxMessage(ctxData, message, span) {
1870
+ async #listenInboxMessage(ctxData, message, _span) {
1723
1871
  const logger = getLogger([
1724
1872
  "fedify",
1725
1873
  "federation",
@@ -1732,60 +1880,28 @@ var FederationImpl = class extends FederationBuilderImpl {
1732
1880
  const identity = await this.sharedInboxKeyDispatcher(context);
1733
1881
  if (identity != null) context = this.#createContext(baseUrl, ctxData, { documentLoader: "identifier" in identity || "username" in identity ? await context.getDocumentLoader(identity) : context.getDocumentLoader(identity) });
1734
1882
  }
1735
- const activity = await Activity.fromJsonLd(message.activity, context);
1736
- span.setAttribute("activitypub.activity.type", getTypeId(activity).href);
1737
- if (activity.id != null) span.setAttribute("activitypub.activity.id", activity.id.href);
1738
- const cacheKey = activity.id == null ? null : [
1739
- ...this.kvPrefixes.activityIdempotence,
1740
- context.origin,
1741
- activity.id.href
1742
- ];
1743
- if (cacheKey != null) {
1744
- if (await this.kv.get(cacheKey) === true) {
1745
- logger.debug("Activity {activityId} has already been processed.", {
1746
- activityId: activity.id?.href,
1747
- activity: message.activity,
1748
- recipient: message.identifier
1749
- });
1750
- return;
1751
- }
1752
- }
1753
1883
  await this._getTracer().startActiveSpan("activitypub.dispatch_inbox_listener", { kind: SpanKind.INTERNAL }, async (span) => {
1754
- const dispatched = this.inboxListeners?.dispatchWithClass(activity);
1755
- if (dispatched == null) {
1756
- logger.error("Unsupported activity type:\n{activity}", {
1757
- activityId: activity.id?.href,
1758
- activity: message.activity,
1759
- recipient: message.identifier,
1760
- trial: message.attempt
1761
- });
1762
- span.setStatus({
1763
- code: SpanStatusCode.ERROR,
1764
- message: `Unsupported activity type: ${getTypeId(activity).href}`
1765
- });
1766
- span.end();
1767
- return;
1768
- }
1769
- const { class: cls, listener } = dispatched;
1770
- span.updateName(`activitypub.dispatch_inbox_listener ${cls.name}`);
1771
- try {
1772
- await listener(context.toInboxContext(message.identifier, message.activity, activity.id?.href, getTypeId(activity).href), activity);
1773
- } catch (error) {
1884
+ let activity = null;
1885
+ let cacheKey = null;
1886
+ const reportInboxError = async (error) => {
1774
1887
  try {
1775
1888
  await this.inboxErrorHandler?.(context, error);
1776
1889
  } catch (error) {
1777
1890
  logger.error("An unexpected error occurred in inbox error handler:\n{error}", {
1778
1891
  error,
1779
1892
  trial: message.attempt,
1780
- activityId: activity.id?.href,
1893
+ activityId: activity?.id?.href,
1781
1894
  activity: message.activity,
1782
1895
  recipient: message.identifier
1783
1896
  });
1784
1897
  }
1898
+ };
1899
+ const handleRetriableFailure = async (error) => {
1900
+ await reportInboxError(error);
1785
1901
  if (this.inboxQueue?.nativeRetrial) {
1786
1902
  logger.error("Failed to process the incoming activity {activityId}; backend will handle retry:\n{error}", {
1787
1903
  error,
1788
- activityId: activity.id?.href,
1904
+ activityId: activity?.id?.href,
1789
1905
  activity: message.activity,
1790
1906
  recipient: message.identifier
1791
1907
  });
@@ -1804,17 +1920,25 @@ var FederationImpl = class extends FederationBuilderImpl {
1804
1920
  logger.error("Failed to process the incoming activity {activityId} (attempt #{attempt}); retry...:\n{error}", {
1805
1921
  error,
1806
1922
  attempt: message.attempt,
1807
- activityId: activity.id?.href,
1923
+ activityId: activity?.id?.href,
1808
1924
  activity: message.activity,
1809
1925
  recipient: message.identifier
1810
1926
  });
1927
+ if (this.inboxQueue == null) {
1928
+ span.setStatus({
1929
+ code: SpanStatusCode.ERROR,
1930
+ message: String(error)
1931
+ });
1932
+ span.end();
1933
+ throw error;
1934
+ }
1811
1935
  await this.inboxQueue?.enqueue({
1812
1936
  ...message,
1813
1937
  attempt: message.attempt + 1
1814
1938
  }, { delay: Temporal.Duration.compare(delay, { seconds: 0 }) < 0 ? Temporal.Duration.from({ seconds: 0 }) : delay });
1815
1939
  } else logger.error("Failed to process the incoming activity {activityId} after {trial} attempts; giving up:\n{error}", {
1816
1940
  error,
1817
- activityId: activity.id?.href,
1941
+ activityId: activity?.id?.href,
1818
1942
  activity: message.activity,
1819
1943
  recipient: message.identifier
1820
1944
  });
@@ -1823,11 +1947,108 @@ var FederationImpl = class extends FederationBuilderImpl {
1823
1947
  message: String(error)
1824
1948
  });
1825
1949
  span.end();
1950
+ };
1951
+ let dispatched;
1952
+ let parseInput = void 0;
1953
+ let parseContextLoader = context.contextLoader;
1954
+ try {
1955
+ const hasSignatureField = hasSignature(message.activity);
1956
+ const shouldParseFromNormalizedSignedPayload = message.ldSignatureVerified === true || message.normalizedActivity != null || message.ldSignatureVerified == null && hasSignatureField;
1957
+ const parseContext = hasSignatureField ? {
1958
+ ...context,
1959
+ contextLoader: getNormalizationContextLoader(context.contextLoader)
1960
+ } : {
1961
+ ...context,
1962
+ contextLoader: wrapContextLoaderForJsonLd(context.contextLoader)
1963
+ };
1964
+ parseContextLoader = parseContext.contextLoader;
1965
+ let normalizedActivity;
1966
+ if (shouldParseFromNormalizedSignedPayload) {
1967
+ normalizedActivity = message.normalizedActivity ?? await compactJsonLd(message.activity, context.contextLoader);
1968
+ assertSafeJsonLd(normalizedActivity);
1969
+ }
1970
+ parseInput = shouldParseFromNormalizedSignedPayload ? detachSignature(normalizedActivity) : hasSignatureField ? detachSignature(message.activity) : message.activity;
1971
+ activity = await Activity.fromJsonLd(parseInput, parseContext);
1972
+ span.setAttribute("activitypub.activity.type", getTypeId(activity).href);
1973
+ if (activity.id != null) span.setAttribute("activitypub.activity.id", activity.id.href);
1974
+ cacheKey = activity.id == null ? null : [
1975
+ ...this.kvPrefixes.activityIdempotence,
1976
+ context.origin,
1977
+ activity.id.href
1978
+ ];
1979
+ if (cacheKey != null) {
1980
+ if (await this.kv.get(cacheKey) === true) {
1981
+ logger.debug("Activity {activityId} has already been processed.", {
1982
+ activityId: activity.id?.href,
1983
+ activity: message.activity,
1984
+ recipient: message.identifier
1985
+ });
1986
+ span.end();
1987
+ return;
1988
+ }
1989
+ }
1990
+ dispatched = this.inboxListeners?.dispatchWithClass(activity);
1991
+ } catch (error) {
1992
+ if (activity == null && error instanceof RangeError && await hasMalformedKnownTemporalLiteral(parseInput, parseContextLoader)) {
1993
+ await reportInboxError(error);
1994
+ logger.error("Failed to parse the queued incoming activity {activityId}:\n{error}", {
1995
+ error,
1996
+ trial: message.attempt,
1997
+ activityId: null,
1998
+ activity: message.activity,
1999
+ recipient: message.identifier
2000
+ });
2001
+ span.setStatus({
2002
+ code: SpanStatusCode.ERROR,
2003
+ message: String(error)
2004
+ });
2005
+ span.end();
2006
+ return;
2007
+ }
2008
+ if (isPermanentInboxParseError(error)) {
2009
+ await reportInboxError(error);
2010
+ logger.error("Failed to parse the queued incoming activity {activityId}:\n{error}", {
2011
+ error,
2012
+ trial: message.attempt,
2013
+ activityId: activity?.id?.href,
2014
+ activity: message.activity,
2015
+ recipient: message.identifier
2016
+ });
2017
+ span.setStatus({
2018
+ code: SpanStatusCode.ERROR,
2019
+ message: String(error)
2020
+ });
2021
+ span.end();
2022
+ return;
2023
+ }
2024
+ await handleRetriableFailure(error);
2025
+ return;
2026
+ }
2027
+ if (dispatched == null) {
2028
+ logger.error("Unsupported activity type:\n{activity}", {
2029
+ activityId: activity.id?.href,
2030
+ activity: message.activity,
2031
+ recipient: message.identifier,
2032
+ trial: message.attempt
2033
+ });
2034
+ span.setStatus({
2035
+ code: SpanStatusCode.ERROR,
2036
+ message: `Unsupported activity type: ${getTypeId(activity).href}`
2037
+ });
2038
+ span.end();
2039
+ return;
2040
+ }
2041
+ const { class: cls, listener } = dispatched;
2042
+ span.updateName(`activitypub.dispatch_inbox_listener ${cls.name}`);
2043
+ try {
2044
+ await listener(context.toInboxContext(message.identifier, message.activity, activity.id?.href, getTypeId(activity).href), activity);
2045
+ } catch (error) {
2046
+ await handleRetriableFailure(error);
1826
2047
  return;
1827
2048
  }
1828
2049
  if (cacheKey != null) await this.kv.set(cacheKey, true, { ttl: Temporal.Duration.from({ days: 1 }) });
1829
2050
  logger.info("Activity {activityId} has been processed.", {
1830
- activityId: activity.id?.href,
2051
+ activityId: activity?.id?.href,
1831
2052
  activity: message.activity,
1832
2053
  recipient: message.identifier
1833
2054
  });
@@ -2173,16 +2394,18 @@ var FederationImpl = class extends FederationBuilderImpl {
2173
2394
  onNotFound
2174
2395
  });
2175
2396
  context = this.#createContext(request, contextData, { documentLoader: await context.getDocumentLoader({ identifier: route.values.identifier }) });
2176
- case "sharedInbox":
2397
+ case "sharedInbox": {
2177
2398
  if (routeName !== "inbox" && this.sharedInboxKeyDispatcher != null) {
2178
2399
  const identity = await this.sharedInboxKeyDispatcher(context);
2179
2400
  if (identity != null) context = this.#createContext(request, contextData, { documentLoader: "identifier" in identity || "username" in identity ? await context.getDocumentLoader(identity) : context.getDocumentLoader(identity) });
2180
2401
  }
2181
2402
  if (!this.manuallyStartQueue) this._startQueueInternal(contextData);
2403
+ const inboxContextFactory = context.toInboxContext.bind(context);
2404
+ inboxContextFactory[rawInboxContextFactorySymbol] = context.toInboxContext.bind(context);
2182
2405
  return await handleInbox(request, {
2183
2406
  recipient: route.values.identifier ?? null,
2184
2407
  context,
2185
- inboxContextFactory: context.toInboxContext.bind(context),
2408
+ inboxContextFactory,
2186
2409
  kv: this.kv,
2187
2410
  kvPrefixes: this.kvPrefixes,
2188
2411
  queue: this.inboxQueue,
@@ -2197,6 +2420,7 @@ var FederationImpl = class extends FederationBuilderImpl {
2197
2420
  tracerProvider: this.tracerProvider,
2198
2421
  idempotencyStrategy: this.idempotencyStrategy
2199
2422
  });
2423
+ }
2200
2424
  case "following": return await handleCollection(request, {
2201
2425
  name: "following",
2202
2426
  identifier: route.values.identifier,
@@ -2872,6 +3096,7 @@ var ContextImpl = class ContextImpl {
2872
3096
  const routeResult = await routeActivity({
2873
3097
  context: this,
2874
3098
  json,
3099
+ ldSignatureVerified: false,
2875
3100
  activity,
2876
3101
  recipient,
2877
3102
  inboxListeners: this.federation.inboxListeners,
@@ -2986,6 +3211,14 @@ var RequestContextImpl = class RequestContextImpl extends ContextImpl {
2986
3211
  };
2987
3212
  var InboxContextImpl = class InboxContextImpl extends ContextImpl {
2988
3213
  recipient;
3214
+ /**
3215
+ * The original received activity payload.
3216
+ *
3217
+ * Fedify may normalize a Linked Data Signature payload internally for safe
3218
+ * parsing, but forwarding must keep the sender's payload unchanged so
3219
+ * third-party signatures/proofs remain intact.
3220
+ * @internal
3221
+ */
2989
3222
  activity;
2990
3223
  activityId;
2991
3224
  activityType;
@@ -1,5 +1,5 @@
1
1
  import "@js-temporal/polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as FederationImpl } from "./middleware-DDsnRvFC.mjs";
4
+ import { n as FederationImpl } from "./middleware-DmT9t0BJ.mjs";
5
5
  export { FederationImpl };
@@ -1,6 +1,5 @@
1
- import { Temporal } from "@js-temporal/polyfill";
2
- import { URLPattern } from "urlpattern-polyfill";
3
- import { Q as ActorHandleMapper, X as ActorAliasMapper, Z as ActorDispatcher, _t as WebFingerLinksDispatcher, s as RequestContext } from "./context-C0fssarc.js";
1
+ /// <reference lib="esnext.temporal" />
2
+ import { Q as ActorHandleMapper, X as ActorAliasMapper, Z as ActorDispatcher, _t as WebFingerLinksDispatcher, s as RequestContext } from "./context-o22vjzS2.cjs";
4
3
  import { Span, Tracer } from "@opentelemetry/api";
5
4
 
6
5
  //#region src/federation/webfinger.d.ts
@@ -1,7 +1,8 @@
1
- import { n as HttpMessageSignaturesSpecDeterminer } from "./http-CrGuipxe.cjs";
2
- import { n as KvStore, t as KvKey } from "./kv-CbLNp3zQ.cjs";
3
- import { DocumentLoader, DocumentLoaderFactoryOptions } from "@fedify/vocab-runtime";
1
+ /// <reference lib="esnext.temporal" />
2
+ import { n as HttpMessageSignaturesSpecDeterminer } from "./http-BDZeS5om.js";
3
+ import { n as KvStore, t as KvKey } from "./kv-D6hNiMTK.js";
4
4
  import { TracerProvider } from "@opentelemetry/api";
5
+ import { DocumentLoader, DocumentLoaderFactoryOptions } from "@fedify/vocab-runtime";
5
6
 
6
7
  //#region src/utils/docloader.d.ts
7
8
  /**
@@ -1,4 +1,5 @@
1
- import { Q as ActorHandleMapper, X as ActorAliasMapper, Z as ActorDispatcher, _t as WebFingerLinksDispatcher, s as RequestContext } from "./context-LmrZPwti.cjs";
1
+ /// <reference lib="esnext.temporal" />
2
+ import { Q as ActorHandleMapper, X as ActorAliasMapper, Z as ActorDispatcher, _t as WebFingerLinksDispatcher, s as RequestContext } from "./context-_l9t359k.js";
2
3
  import { Span, Tracer } from "@opentelemetry/api";
3
4
 
4
5
  //#region src/federation/webfinger.d.ts
@@ -1,7 +1,8 @@
1
- import { S as KeyCache } from "./http-CrGuipxe.cjs";
1
+ /// <reference lib="esnext.temporal" />
2
+ import { S as KeyCache } from "./http-BDZeS5om.js";
2
3
  import { CryptographicKey, DataIntegrityProof, Multikey, Object as Object$1 } from "@fedify/vocab";
3
- import { DocumentLoader } from "@fedify/vocab-runtime";
4
4
  import { TracerProvider } from "@opentelemetry/api";
5
+ import { DocumentLoader } from "@fedify/vocab-runtime";
5
6
 
6
7
  //#region src/sig/ld.d.ts
7
8
  /**