@fedify/fedify 2.5.0-dev.2446 → 2.5.0-dev.2448

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 (117) hide show
  1. package/dist/{activity-listener-C-BSbKI7.mjs → activity-listener-CELFAodc.mjs} +8 -4
  2. package/dist/{builder-Da8b9O6v.mjs → builder-DbC8bn6k.mjs} +5 -5
  3. package/dist/compat/mod.d.cts +1 -1
  4. package/dist/compat/mod.d.ts +1 -1
  5. package/dist/compat/transformers.test.mjs +1 -1
  6. package/dist/{compound-proof-D1djO5fa.mjs → compound-proof-heeXW2_A.mjs} +2 -2
  7. package/dist/{context-CQ9H8vNW.d.cts → context-BazwQrHn.d.cts} +304 -4
  8. package/dist/{context-8Tu9kea0.mjs → context-COHd1Rsh.mjs} +2 -0
  9. package/dist/{context-BdB54V-A.d.ts → context-GlHrzdSP.d.ts} +304 -4
  10. package/dist/{deno-eNpfj4oR.mjs → deno-Dpewt_jJ.mjs} +1 -1
  11. package/dist/{docloader-B-0nqOB1.mjs → docloader-CpsDyqVN.mjs} +2 -2
  12. package/dist/federation/builder.test.mjs +1 -1
  13. package/dist/federation/compound-fanout.test.mjs +3 -3
  14. package/dist/federation/handler-compound-proof.test.mjs +5 -5
  15. package/dist/federation/handler.test.mjs +21 -6
  16. package/dist/federation/hashlink-media.test.mjs +2 -2
  17. package/dist/federation/idempotency.test.mjs +2 -2
  18. package/dist/federation/inbox-actorless.test.d.mts +2 -0
  19. package/dist/federation/inbox-actorless.test.mjs +487 -0
  20. package/dist/federation/inbox-authentication.test.d.mts +2 -0
  21. package/dist/federation/inbox-authentication.test.mjs +116 -0
  22. package/dist/federation/inbox-report.test.mjs +5 -5
  23. package/dist/federation/inbox-view.test.d.mts +2 -0
  24. package/dist/federation/inbox-view.test.mjs +369 -0
  25. package/dist/federation/inbox.test.mjs +22 -12
  26. package/dist/federation/metrics.test.mjs +1 -1
  27. package/dist/federation/middleware.test.mjs +9 -9
  28. package/dist/federation/mod.cjs +2 -1
  29. package/dist/federation/mod.d.cts +3 -3
  30. package/dist/federation/mod.d.ts +3 -3
  31. package/dist/federation/mod.js +8 -2
  32. package/dist/federation/mq.test.mjs +36 -1
  33. package/dist/federation/outgoing-proof.test.mjs +4 -4
  34. package/dist/federation/portable-actor.test.mjs +3 -3
  35. package/dist/federation/portable-chain.test.mjs +3 -3
  36. package/dist/federation/portable-collection.test.mjs +4 -4
  37. package/dist/federation/portable-delivery.test.mjs +2 -2
  38. package/dist/federation/portable-gateway-keys.test.mjs +6 -6
  39. package/dist/federation/portable-inbox.test.mjs +4 -4
  40. package/dist/federation/portable-parse-uri.test.mjs +1 -1
  41. package/dist/federation/portable-route-activity.test.mjs +2 -2
  42. package/dist/federation/portable.test.mjs +3 -3
  43. package/dist/federation/send.test.mjs +3 -3
  44. package/dist/federation/tasks/enqueue.test.mjs +3 -3
  45. package/dist/federation/tasks/tasks.test.mjs +4 -4
  46. package/dist/federation/temporal.test.mjs +1 -1
  47. package/dist/federation/webfinger.test.mjs +2 -2
  48. package/dist/{federation-BVH7drJA.cjs → federation-D8YexkP1.cjs} +1172 -131
  49. package/dist/{http-DlNxxW_5.js → http-BBr-VLil.js} +143 -16
  50. package/dist/{http-BUJ5SFh6.mjs → http-BZkyQIkb.mjs} +4 -4
  51. package/dist/{http-BfW3wyQ4.cjs → http-CAl7Num5.cjs} +161 -16
  52. package/dist/inbox-authentication-DflPYlLL.mjs +171 -0
  53. package/dist/inbox-view-NE9wXdLo.mjs +385 -0
  54. package/dist/inbox-wDuMKaFW.mjs +312 -0
  55. package/dist/{kv-cache-D_m3A241.mjs → kv-cache-CTQSfkmp.mjs} +1 -1
  56. package/dist/{kv-cache-kkBB0vbI.cjs → kv-cache-CjgCOz3i.cjs} +1 -1
  57. package/dist/{kv-cache-7015lbrx.js → kv-cache-U7uz5nx4.js} +1 -1
  58. package/dist/{ld-CD2CsPxq.mjs → ld-CQFuiah9.mjs} +52 -9
  59. package/dist/ld-CcuZ4NZa.d.ts +250 -0
  60. package/dist/ld-Cel1RRZ5.d.cts +250 -0
  61. package/dist/{metrics-D3LHk6Z4.mjs → metrics-B-QxRSSR.mjs} +1 -1
  62. package/dist/{middleware-CCDXvXOG.mjs → middleware-BdlNa8BD.mjs} +1 -1
  63. package/dist/{middleware-CzoWvrdZ.mjs → middleware-CuVa0UuS.mjs} +459 -98
  64. package/dist/{middleware-N9eM16XE.js → middleware-P-AZEQMn.js} +1169 -141
  65. package/dist/mod-B2783_3v.d.cts +154 -0
  66. package/dist/mod-BHQE-7WQ.d.ts +154 -0
  67. package/dist/{mod-D0bxB8jS.d.ts → mod-DjZfJj9R.d.ts} +1 -1
  68. package/dist/{mod-B0dGgQsg.d.cts → mod-DvWOv2Ho.d.cts} +1 -1
  69. package/dist/mod.cjs +5 -4
  70. package/dist/mod.d.cts +6 -6
  71. package/dist/mod.d.ts +6 -6
  72. package/dist/mod.js +5 -5
  73. package/dist/{mq-Dm1Wmtm-.mjs → mq-eF96QxUT.mjs} +6 -0
  74. package/dist/nodeinfo/handler.test.mjs +2 -2
  75. package/dist/{owner-C5-FVX6t.mjs → owner-ccKkQOAf.mjs} +3 -3
  76. package/dist/{portable-collection-Cony3_LC.mjs → portable-collection-CndnpXfo.mjs} +2 -2
  77. package/dist/{portable-collection-CC2zNAje.js → portable-collection-DUlQ3s2M.js} +1 -1
  78. package/dist/{portable-collection-C-Z1SKDy.cjs → portable-collection-TC-kvkVI.cjs} +1 -1
  79. package/dist/{portable-key-DNc_flH-.mjs → portable-key-D5tFOoV-.mjs} +1 -1
  80. package/dist/{proof-Cryb1c70.mjs → proof-DPoF06nz.mjs} +98 -10
  81. package/dist/{send-AF25pWaJ.mjs → send-CF44O1Df.mjs} +3 -3
  82. package/dist/sig/compound-proof-discovery.test.mjs +1 -1
  83. package/dist/sig/compound-proof-producer.test.mjs +1 -1
  84. package/dist/sig/compound-proof-verification.test.mjs +2 -2
  85. package/dist/sig/compound-proof.test.mjs +2 -2
  86. package/dist/sig/http.test.mjs +2 -2
  87. package/dist/sig/key.test.mjs +1 -1
  88. package/dist/sig/ld.test.mjs +1 -1
  89. package/dist/sig/mitra-receive-interop.test.mjs +4 -4
  90. package/dist/sig/mitra-send-interop.test.mjs +3 -3
  91. package/dist/sig/mod.cjs +2 -2
  92. package/dist/sig/mod.d.cts +2 -2
  93. package/dist/sig/mod.d.ts +2 -2
  94. package/dist/sig/mod.js +2 -2
  95. package/dist/sig/owner.test.mjs +1 -1
  96. package/dist/sig/portable-collection.test.mjs +1 -1
  97. package/dist/sig/portable-interop.test.mjs +3 -3
  98. package/dist/sig/portable-key.test.mjs +5 -5
  99. package/dist/sig/proof.test.mjs +44 -4
  100. package/dist/sig/tootik-request-interop.test.mjs +4 -4
  101. package/dist/sig/tootik-send-interop.test.mjs +3 -3
  102. package/dist/sig/verification.test.mjs +3 -3
  103. package/dist/{temporal-BsAeuUJj.mjs → temporal-XlZ4Wb94.mjs} +1 -1
  104. package/dist/testing/kv.test.mjs +1 -1
  105. package/dist/testing/mod.d.mts +262 -2
  106. package/dist/testing/mod.mjs +2 -2
  107. package/dist/{testing-t8xDeJZ5.mjs → testing-Dbr21vZk.mjs} +1 -1
  108. package/dist/utils/docloader.test.mjs +2 -2
  109. package/dist/utils/kv-cache.test.mjs +1 -1
  110. package/dist/utils/mod.cjs +1 -1
  111. package/dist/utils/mod.js +1 -1
  112. package/package.json +7 -7
  113. package/dist/inbox-CHvMaIFX.mjs +0 -188
  114. package/dist/mod-BRpER-B0.d.ts +0 -320
  115. package/dist/mod-xAfaLS4T.d.cts +0 -320
  116. package/dist/owner-BF0L9cMt.d.cts +0 -78
  117. package/dist/owner-BSzr5-I8.d.ts +0 -78
@@ -1,10 +1,10 @@
1
1
  const { URLPattern } = require("urlpattern-polyfill");
2
2
  const require_rolldown_runtime = require("./rolldown-runtime-Dmm3t92W.cjs");
3
3
  const require_transformers = require("./transformers-BalCrSsr.cjs");
4
- const require_http = require("./http-BfW3wyQ4.cjs");
4
+ const require_http = require("./http-CAl7Num5.cjs");
5
5
  const require_types = require("./types-poIao40f.cjs");
6
- const require_portable_collection = require("./portable-collection-C-Z1SKDy.cjs");
7
- const require_kv_cache = require("./kv-cache-kkBB0vbI.cjs");
6
+ const require_portable_collection = require("./portable-collection-TC-kvkVI.cjs");
7
+ const require_kv_cache = require("./kv-cache-CjgCOz3i.cjs");
8
8
  let _logtape_logtape = require("@logtape/logtape");
9
9
  let _fedify_uri_template = require("@fedify/uri-template");
10
10
  let _fedify_vocab = require("@fedify/vocab");
@@ -15,6 +15,8 @@ let byte_encodings_hex = require("byte-encodings/hex");
15
15
  let _fedify_vocab_runtime_jsonld = require("@fedify/vocab-runtime/jsonld");
16
16
  _fedify_vocab_runtime_jsonld = require_rolldown_runtime.__toESM(_fedify_vocab_runtime_jsonld, 1);
17
17
  let es_toolkit = require("es-toolkit");
18
+ let json_canon = require("json-canon");
19
+ json_canon = require_rolldown_runtime.__toESM(json_canon, 1);
18
20
  let _opentelemetry_semantic_conventions = require("@opentelemetry/semantic-conventions");
19
21
  let _fedify_webfinger = require("@fedify/webfinger");
20
22
  let _opentelemetry_sdk_metrics = require("@opentelemetry/sdk-metrics");
@@ -32,9 +34,12 @@ var ActivityListenerSet = class {
32
34
  clone.#listeners = new Map(this.#listeners);
33
35
  return clone;
34
36
  }
35
- add(type, listener) {
37
+ add(type, listener, options = {}) {
36
38
  if (this.#listeners.has(type)) throw new TypeError("Listener already set for this type.");
37
- this.#listeners.set(type, listener);
39
+ this.#listeners.set(type, {
40
+ listener,
41
+ options: { ...options }
42
+ });
38
43
  }
39
44
  dispatchWithClass(activity) {
40
45
  let cls = activity.constructor;
@@ -44,10 +49,11 @@ var ActivityListenerSet = class {
44
49
  cls = globalThis.Object.getPrototypeOf(cls);
45
50
  }
46
51
  if (cls == null) return null;
47
- const listener = this.#listeners.get(cls);
52
+ const { listener, options } = this.#listeners.get(cls);
48
53
  return {
49
54
  class: cls,
50
- listener
55
+ listener,
56
+ options
51
57
  };
52
58
  }
53
59
  dispatch(activity) {
@@ -635,8 +641,8 @@ var FederationBuilderImpl = class {
635
641
  }
636
642
  const listeners = this.inboxListeners = new ActivityListenerSet();
637
643
  const setters = {
638
- on(type, listener) {
639
- listeners.add(type, listener);
644
+ on(type, listener, options) {
645
+ listeners.add(type, listener, options);
640
646
  return setters;
641
647
  },
642
648
  onError: (handler) => {
@@ -2805,6 +2811,172 @@ async function isInAudience(object, actor, options = {}) {
2805
2811
  return false;
2806
2812
  }
2807
2813
  //#endregion
2814
+ //#region src/federation/inbox-authentication.ts
2815
+ /**
2816
+ * The evidence of an activity that has not been authenticated by Fedify.
2817
+ */
2818
+ const EMPTY_EVIDENCE = Object.freeze({
2819
+ verified: false,
2820
+ requestSigners: Object.freeze([]),
2821
+ documentSigners: Object.freeze([]),
2822
+ originAuthenticated: Object.freeze([]),
2823
+ expandedActivity: Object.freeze([]),
2824
+ baseUrl: null
2825
+ });
2826
+ /**
2827
+ * Gets the evidence of an activity that has not been authenticated.
2828
+ * @internal
2829
+ */
2830
+ function getEmptyAuthenticationEvidence() {
2831
+ return EMPTY_EVIDENCE;
2832
+ }
2833
+ /**
2834
+ * Gets the principals that an authorization decision should rely on.
2835
+ * @param evidence The evidence.
2836
+ * @param scope See {@link IsActorAuthenticatedOptions.scope}.
2837
+ * @returns The principals.
2838
+ * @internal
2839
+ */
2840
+ function getEffectivePrincipals(evidence, scope = "effective") {
2841
+ if (scope === "document" || evidence.documentSigners.length > 0) return evidence.documentSigners;
2842
+ if (evidence.requestSigners.length > 0) return evidence.requestSigners;
2843
+ return evidence.originAuthenticated;
2844
+ }
2845
+ /**
2846
+ * Normalizes an actor ID for comparison: portable IDs and compatible
2847
+ * identifiers to their canonical portable ID, other IDs to their `href`.
2848
+ * @returns The normalized ID, or `null` for a malformed portable ID.
2849
+ */
2850
+ function normalizeActorId(id) {
2851
+ return require_http.isPortableId(id) ? require_http.getCanonicalPortableId(id) : id.href;
2852
+ }
2853
+ /**
2854
+ * Checks whether the given principal authenticates the given actor.
2855
+ * @param principal The principal.
2856
+ * @param actorId The actor ID.
2857
+ * @returns `true` if the principal authenticates the actor.
2858
+ * @internal
2859
+ */
2860
+ function principalAuthenticates(principal, actorId) {
2861
+ if (principal.kind === "portableAuthority") {
2862
+ if (!require_http.isPortableId(actorId)) return false;
2863
+ const did = require_http.getPortableDid(actorId);
2864
+ return did != null && did === require_http.getPortableDid(principal.id);
2865
+ }
2866
+ const expected = normalizeActorId(actorId);
2867
+ return expected != null && normalizeActorId(principal.id) === expected;
2868
+ }
2869
+ /**
2870
+ * Checks whether the given evidence authenticates the given actor.
2871
+ *
2872
+ * Actor IDs are compared exactly, except that [FEP-ef61] portable IDs and
2873
+ * compatible identifiers are compared by their canonical portable IDs, and
2874
+ * a portable authority (a DID) authenticates the portable actors of that DID.
2875
+ *
2876
+ * @example
2877
+ * ``` typescript
2878
+ * federation
2879
+ * .setInboxListeners("/users/{identifier}/inbox", "/inbox")
2880
+ * .on(Follow, async (ctx, follow) => {
2881
+ * if (follow.actorId == null) return;
2882
+ * // The actor is always authenticated for activities that declare one,
2883
+ * // but the same check works for any actor an activity names:
2884
+ * isActorAuthenticated(ctx.authentication, follow.actorId);
2885
+ * });
2886
+ * ```
2887
+ *
2888
+ * [FEP-ef61]: https://w3id.org/fep/ef61
2889
+ * @param evidence The authentication evidence, usually
2890
+ * {@link InboxContext.authentication}.
2891
+ * @param actorId The actor ID to check.
2892
+ * @param options Options.
2893
+ * @returns `true` if the actor is authenticated.
2894
+ * @since 2.5.0
2895
+ */
2896
+ function isActorAuthenticated(evidence, actorId, options = {}) {
2897
+ return getEffectivePrincipals(evidence, options.scope).some((principal) => principalAuthenticates(principal, actorId));
2898
+ }
2899
+ function serializePrincipal(principal) {
2900
+ return {
2901
+ kind: principal.kind,
2902
+ id: principal.id.href,
2903
+ mechanism: principal.mechanism,
2904
+ keyId: principal.keyId?.href ?? null
2905
+ };
2906
+ }
2907
+ /**
2908
+ * Serializes the principals of the given evidence for a queued message.
2909
+ * @internal
2910
+ */
2911
+ function serializeAuthentication(evidence) {
2912
+ return {
2913
+ verified: evidence.verified,
2914
+ requestSigners: evidence.requestSigners.map(serializePrincipal),
2915
+ documentSigners: evidence.documentSigners.map(serializePrincipal),
2916
+ originAuthenticated: evidence.originAuthenticated.map(serializePrincipal)
2917
+ };
2918
+ }
2919
+ const KINDS = /* @__PURE__ */ new Set(["actor", "portableAuthority"]);
2920
+ const MECHANISMS = /* @__PURE__ */ new Set([
2921
+ "httpSignatures",
2922
+ "linkedDataSignatures",
2923
+ "objectIntegrityProofs",
2924
+ "origin"
2925
+ ]);
2926
+ function deserializePrincipals(value) {
2927
+ if (!Array.isArray(value)) return null;
2928
+ const principals = [];
2929
+ for (const item of value) {
2930
+ if (item == null || typeof item !== "object" || !KINDS.has(item.kind) || !MECHANISMS.has(item.mechanism) || typeof item.id !== "string" || !URL.canParse(item.id) || item.keyId != null && (typeof item.keyId !== "string" || !URL.canParse(item.keyId))) return null;
2931
+ principals.push(Object.freeze({
2932
+ kind: item.kind,
2933
+ id: new URL(item.id),
2934
+ mechanism: item.mechanism,
2935
+ keyId: item.keyId == null ? null : new URL(item.keyId)
2936
+ }));
2937
+ }
2938
+ return principals;
2939
+ }
2940
+ /**
2941
+ * Restores the evidence of a queued message.
2942
+ * @param value The serialized principals.
2943
+ * @param expandedActivity The expanded activity, restored from the queued
2944
+ * verified view.
2945
+ * @param baseUrl The base URL, restored from the queued verified view.
2946
+ * @returns The evidence, or `null` if the serialized value is malformed.
2947
+ * @internal
2948
+ */
2949
+ function deserializeAuthentication(value, expandedActivity, baseUrl) {
2950
+ if (value == null || typeof value !== "object" || typeof value.verified !== "boolean") return null;
2951
+ const record = value;
2952
+ const requestSigners = deserializePrincipals(record.requestSigners);
2953
+ const documentSigners = deserializePrincipals(record.documentSigners);
2954
+ const originAuthenticated = deserializePrincipals(record.originAuthenticated);
2955
+ if (requestSigners == null || documentSigners == null || originAuthenticated == null) return null;
2956
+ return createAuthenticationEvidence({
2957
+ verified: record.verified,
2958
+ requestSigners,
2959
+ documentSigners,
2960
+ originAuthenticated,
2961
+ expandedActivity,
2962
+ baseUrl
2963
+ });
2964
+ }
2965
+ /**
2966
+ * Creates frozen evidence.
2967
+ * @internal
2968
+ */
2969
+ function createAuthenticationEvidence(evidence) {
2970
+ return Object.freeze({
2971
+ verified: evidence.verified,
2972
+ requestSigners: Object.freeze([...evidence.requestSigners]),
2973
+ documentSigners: Object.freeze([...evidence.documentSigners]),
2974
+ originAuthenticated: Object.freeze([...evidence.originAuthenticated]),
2975
+ expandedActivity: evidence.expandedActivity,
2976
+ baseUrl: evidence.baseUrl
2977
+ });
2978
+ }
2979
+ //#endregion
2808
2980
  //#region src/federation/inbox.ts
2809
2981
  const Temporal$10 = require_http.Temporal;
2810
2982
  /**
@@ -2827,13 +2999,135 @@ function actorsHaveSameOrigin(activity) {
2827
2999
  const origin = require_http.getAuthenticationOrigin(activity.id);
2828
3000
  return origin != null && activity.actorIds.every((actor) => require_http.getAuthenticationOrigin(actor) === origin);
2829
3001
  }
2830
- async function routeActivity({ context: ctx, json, originalJson, normalizedActivity, ldSignatureVerified, activity, recipient, inboxListeners, inboxContextFactory, listenerInboxContextFactory, inboxErrorHandler, kv, kvPrefixes, queue, span, meterProvider, tracerProvider, idempotencyStrategy }) {
3002
+ /**
3003
+ * The headroom that the size check of a queued inbox message leaves for the
3004
+ * fields that grow when Fedify retries the message, e.g., its attempt count.
3005
+ */
3006
+ const QUEUED_MESSAGE_HEADROOM = 64;
3007
+ /**
3008
+ * Computes the idempotency key of an incoming activity.
3009
+ * @returns The key, or `null` if the activity is not deduplicated.
3010
+ * @internal
3011
+ */
3012
+ async function getInboxIdempotencyKey(strategy, inboxContext, activity, origin, recipient, prefix) {
3013
+ if (activity.id == null) return null;
3014
+ strategy ??= "per-inbox";
3015
+ let keyString;
3016
+ if (typeof strategy === "function") keyString = await strategy(inboxContext(), activity);
3017
+ else switch (strategy) {
3018
+ case "global":
3019
+ keyString = activity.id.href;
3020
+ break;
3021
+ case "per-origin":
3022
+ keyString = `${origin}\n${activity.id.href}`;
3023
+ break;
3024
+ case "per-inbox":
3025
+ keyString = `${origin}\n${activity.id.href}\n${recipient == null ? "sharedInbox" : `inbox\n${recipient}`}`;
3026
+ break;
3027
+ default: keyString = `${origin}\n${activity.id.href}`;
3028
+ }
3029
+ return keyString == null ? null : [...prefix, keyString];
3030
+ }
3031
+ /**
3032
+ * Checks whether an incoming activity has already been processed, by its
3033
+ * idempotency key, or by the key that queue workers of older versions of
3034
+ * Fedify recorded.
3035
+ * @internal
3036
+ */
3037
+ async function isInboxActivityProcessed(kv, cacheKey, activity, origin, prefix) {
3038
+ if (cacheKey == null) return false;
3039
+ if (await kv.get(cacheKey) === true) return true;
3040
+ if (activity.id == null) return false;
3041
+ return await kv.get([
3042
+ ...prefix,
3043
+ origin,
3044
+ activity.id.href
3045
+ ]) === true;
3046
+ }
3047
+ /**
3048
+ * The bytes added to the UTF-8 JSON length of a message for each of its JSON
3049
+ * values, to bound the size of serialization formats other than JSON, such
3050
+ * as the structured serialization of Deno KV, which spends more bytes than
3051
+ * JSON on type tags and lengths of small values, e.g., empty arrays, and up
3052
+ * to nine bytes on a fractional number that JSON writes in three.
3053
+ */
3054
+ const PER_VALUE_SERIALIZATION_OVERHEAD = 8;
3055
+ /**
3056
+ * Estimates, conservatively, how large a queued message is once a queue
3057
+ * backend serializes it: the UTF-8 length of its JSON serialization, plus
3058
+ * a fixed overhead for each JSON value in it, plus the bytes that encoding
3059
+ * strings beyond Latin-1 as UTF-16 adds.
3060
+ * @internal
3061
+ */
3062
+ function estimateQueuedMessageSize(message) {
3063
+ const encoder = new TextEncoder();
3064
+ let values = 0;
3065
+ let stringExpansion = 0;
3066
+ const countString = (value) => {
3067
+ values++;
3068
+ if (!/[\u0100-\uffff]/.test(value)) return;
3069
+ const expansion = value.length * 2 - encoder.encode(value).byteLength;
3070
+ if (expansion > 0) stringExpansion += expansion;
3071
+ };
3072
+ const count = (value) => {
3073
+ if (typeof value === "string") {
3074
+ countString(value);
3075
+ return;
3076
+ }
3077
+ values++;
3078
+ if (Array.isArray(value)) for (const item of value) count(item);
3079
+ else if (value != null && typeof value === "object") for (const [key, item] of Object.entries(value)) {
3080
+ if (item === void 0) continue;
3081
+ countString(key);
3082
+ count(item);
3083
+ }
3084
+ };
3085
+ count(message);
3086
+ return encoder.encode(JSON.stringify(message)).byteLength + stringExpansion + values * PER_VALUE_SERIALIZATION_OVERHEAD;
3087
+ }
3088
+ /**
3089
+ * Checks whether an activity that declares no actor is authenticated: at
3090
+ * least one principal must be authenticated for it, and its ID, if any, must
3091
+ * have the same [FEP-fe34] origin as one of those principals.
3092
+ *
3093
+ * [FEP-fe34]: https://w3id.org/fep/fe34
3094
+ * @returns `"ok"` if it is authenticated, or why it is not.
3095
+ * @internal
3096
+ */
3097
+ function checkActorlessActivity(activity, authentication) {
3098
+ const principals = getEffectivePrincipals(authentication);
3099
+ if (principals.length < 1) return {
3100
+ status: "noAuthenticatedPrincipal",
3101
+ principals
3102
+ };
3103
+ if (activity.id == null) return {
3104
+ status: "ok",
3105
+ principals
3106
+ };
3107
+ const origin = require_http.getAuthenticationOrigin(activity.id);
3108
+ if (origin == null || !principals.some((principal) => require_http.getAuthenticationOrigin(principal.id) === origin)) return {
3109
+ status: "activityOriginMismatch",
3110
+ principals
3111
+ };
3112
+ return {
3113
+ status: "ok",
3114
+ principals
3115
+ };
3116
+ }
3117
+ /**
3118
+ * Same as {@link checkActorlessActivity}, but returns a boolean.
3119
+ * @internal
3120
+ */
3121
+ function isActorlessActivityAuthenticated(activity, authentication) {
3122
+ return checkActorlessActivity(activity, authentication).status === "ok";
3123
+ }
3124
+ async function routeActivity({ context: ctx, json, originalJson, normalizedActivity, ldSignatureVerified, authentication, verifiedView, replayable, actorless, activity, recipient, inboxListeners, inboxContextFactory, listenerInboxContextFactory, inboxErrorHandler, kv, kvPrefixes, queue, span, meterProvider, tracerProvider, idempotencyStrategy }) {
2831
3125
  const logger = (0, _logtape_logtape.getLogger)([
2832
3126
  "fedify",
2833
3127
  "federation",
2834
3128
  "inbox"
2835
3129
  ]);
2836
- if (activity.actorId == null) {
3130
+ if (activity.actorId == null && !actorless) {
2837
3131
  logger.error("Missing actor.", { activity: json });
2838
3132
  span.setStatus({
2839
3133
  code: _opentelemetry_api.SpanStatusCode.ERROR,
@@ -2842,58 +3136,59 @@ async function routeActivity({ context: ctx, json, originalJson, normalizedActiv
2842
3136
  require_http.recordInboxActivity(meterProvider, "rejected", (0, _fedify_vocab.getTypeId)(activity).href);
2843
3137
  return "missingActor";
2844
3138
  }
2845
- let cacheKey = null;
2846
- if (activity.id != null) {
2847
- const inboxContext = inboxContextFactory(recipient, json, activity.id?.href, (0, _fedify_vocab.getTypeId)(activity).href);
2848
- const strategy = idempotencyStrategy ?? "per-inbox";
2849
- let keyString;
2850
- if (typeof strategy === "function") keyString = await strategy(inboxContext, activity);
2851
- else switch (strategy) {
2852
- case "global":
2853
- keyString = activity.id.href;
2854
- break;
2855
- case "per-origin":
2856
- keyString = `${ctx.origin}\n${activity.id.href}`;
2857
- break;
2858
- case "per-inbox":
2859
- keyString = `${ctx.origin}\n${activity.id.href}\n${recipient == null ? "sharedInbox" : `inbox\n${recipient}`}`;
2860
- break;
2861
- default: keyString = `${ctx.origin}\n${activity.id.href}`;
2862
- }
2863
- if (keyString != null) cacheKey = [...kvPrefixes.activityIdempotence, keyString];
3139
+ const contextMetadata = { authentication };
3140
+ const cacheKey = await getInboxIdempotencyKey(idempotencyStrategy, () => inboxContextFactory(recipient, json, activity.id?.href, (0, _fedify_vocab.getTypeId)(activity).href, contextMetadata), activity, ctx.origin, recipient, kvPrefixes.activityIdempotence);
3141
+ if (await isInboxActivityProcessed(kv, cacheKey, activity, ctx.origin, kvPrefixes.activityIdempotence)) {
3142
+ logger.debug("Activity {activityId} has already been processed.", {
3143
+ activityId: activity.id?.href,
3144
+ activity: json,
3145
+ recipient
3146
+ });
3147
+ span.setStatus({
3148
+ code: _opentelemetry_api.SpanStatusCode.UNSET,
3149
+ message: `Activity ${activity.id?.href} has already been processed.`
3150
+ });
3151
+ require_http.recordInboxActivity(meterProvider, "rejected", (0, _fedify_vocab.getTypeId)(activity).href);
3152
+ return "alreadyProcessed";
2864
3153
  }
2865
- if (cacheKey != null) {
2866
- if (await kv.get(cacheKey) === true) {
2867
- logger.debug("Activity {activityId} has already been processed.", {
3154
+ if (activity.actorId != null) span.setAttribute("activitypub.actor.id", activity.actorId.href);
3155
+ let message;
3156
+ if (queue != null) {
3157
+ const carrier = {};
3158
+ _opentelemetry_api.propagation.inject(_opentelemetry_api.context.active(), carrier);
3159
+ message = {
3160
+ type: "inbox",
3161
+ id: crypto.randomUUID(),
3162
+ baseUrl: ctx.origin,
3163
+ activity: originalJson ?? json,
3164
+ ...normalizedActivity == null ? {} : { normalizedActivity },
3165
+ ...ldSignatureVerified == null ? {} : { ldSignatureVerified },
3166
+ ...verifiedView == null ? {} : { verifiedView },
3167
+ ...authentication == null ? {} : { authentication: serializeAuthentication(authentication) },
3168
+ identifier: recipient,
3169
+ attempt: 0,
3170
+ started: (/* @__PURE__ */ new Date()).toISOString(),
3171
+ traceContext: carrier
3172
+ };
3173
+ const maxMessageSize = queue.maxMessageSize;
3174
+ if (replayable === false) {
3175
+ logger.warn("The incoming activity {activityId} uses a JSON-LD context that cannot be kept for the queue; processing it immediately instead.", {
2868
3176
  activityId: activity.id?.href,
2869
- activity: json,
2870
3177
  recipient
2871
3178
  });
2872
- span.setStatus({
2873
- code: _opentelemetry_api.SpanStatusCode.UNSET,
2874
- message: `Activity ${activity.id?.href} has already been processed.`
3179
+ message = void 0;
3180
+ } else if (maxMessageSize != null && estimateQueuedMessageSize(message) > maxMessageSize - QUEUED_MESSAGE_HEADROOM) {
3181
+ logger.warn("The incoming activity {activityId} is too large to enqueue; processing it immediately instead.", {
3182
+ activityId: activity.id?.href,
3183
+ recipient,
3184
+ maxMessageSize
2875
3185
  });
2876
- require_http.recordInboxActivity(meterProvider, "rejected", (0, _fedify_vocab.getTypeId)(activity).href);
2877
- return "alreadyProcessed";
3186
+ message = void 0;
2878
3187
  }
2879
3188
  }
2880
- span.setAttribute("activitypub.actor.id", activity.actorId.href);
2881
- if (queue != null) {
2882
- const carrier = {};
2883
- _opentelemetry_api.propagation.inject(_opentelemetry_api.context.active(), carrier);
3189
+ if (queue != null && message != null) {
2884
3190
  try {
2885
- await queue.enqueue({
2886
- type: "inbox",
2887
- id: crypto.randomUUID(),
2888
- baseUrl: ctx.origin,
2889
- activity: originalJson ?? json,
2890
- ...normalizedActivity == null ? {} : { normalizedActivity },
2891
- ...ldSignatureVerified == null ? {} : { ldSignatureVerified },
2892
- identifier: recipient,
2893
- attempt: 0,
2894
- started: (/* @__PURE__ */ new Date()).toISOString(),
2895
- traceContext: carrier
2896
- });
3191
+ await queue.enqueue(message);
2897
3192
  } catch (error) {
2898
3193
  logger.error("Failed to enqueue the incoming activity {activityId}:\n{error}", {
2899
3194
  error,
@@ -2943,7 +3238,7 @@ async function routeActivity({ context: ctx, json, originalJson, normalizedActiv
2943
3238
  const started = performance.now();
2944
3239
  try {
2945
3240
  const contextFactory = listenerInboxContextFactory ?? inboxContextFactory;
2946
- await listener(contextFactory(recipient, contextFactory === inboxContextFactory ? json : originalJson ?? json, activity.id?.href, activityType), activity);
3241
+ await listener(contextFactory(recipient, contextFactory === inboxContextFactory ? json : originalJson ?? json, activity.id?.href, activityType, contextMetadata), activity);
2947
3242
  } finally {
2948
3243
  require_http.getFederationMetrics(meterProvider).recordInboxProcessingDuration(activityType, require_http.getDurationMs(started));
2949
3244
  }
@@ -2984,6 +3279,382 @@ async function routeActivity({ context: ctx, json, originalJson, normalizedActiv
2984
3279
  });
2985
3280
  }
2986
3281
  //#endregion
3282
+ //#region src/federation/inbox-view.ts
3283
+ /**
3284
+ * The interpretation of an incoming activity that Fedify authenticated, kept
3285
+ * so that listeners, authorization helpers, and queue workers observe the
3286
+ * same JSON-LD meaning that signature verification observed.
3287
+ *
3288
+ * Every JSON-LD processing step of one delivery goes through a request-scoped
3289
+ * *context snapshot*, which memoizes the remote context responses so that
3290
+ * a nondeterministic or mutable remote context cannot give verification and
3291
+ * dispatch different meanings. The snapshot is serialized into queued
3292
+ * messages, so that workers and retries replay the very same responses
3293
+ * without loading contexts again.
3294
+ * @module
3295
+ */
3296
+ /**
3297
+ * Thrown when replaying an interpretation needs a context response that its
3298
+ * snapshot does not have, or has with a different content.
3299
+ * @internal
3300
+ */
3301
+ var ContextSnapshotMissError = class extends Error {
3302
+ url;
3303
+ constructor(url, reason) {
3304
+ super(`Context ${url} cannot be replayed: ${reason}.`);
3305
+ this.name = "ContextSnapshotMissError";
3306
+ this.url = url;
3307
+ }
3308
+ };
3309
+ function normalizeUrl(url) {
3310
+ return URL.canParse(url) ? new URL(url).href : url;
3311
+ }
3312
+ /**
3313
+ * Creates the context snapshot of a delivery.
3314
+ * @param base The caller's context loader.
3315
+ * @returns The snapshot.
3316
+ * @internal
3317
+ */
3318
+ function createContextSnapshot(base) {
3319
+ const underlying = {
3320
+ standard: require_http.wrapContextLoaderForJsonLd(base),
3321
+ normalization: require_http.getNormalizationContextLoader(base)
3322
+ };
3323
+ const cache = /* @__PURE__ */ new Map();
3324
+ const recorded = /* @__PURE__ */ new Map();
3325
+ const loaders = /* @__PURE__ */ new Map();
3326
+ let released = false;
3327
+ return {
3328
+ loader(policy) {
3329
+ let loader = loaders.get(policy);
3330
+ if (loader != null) return loader;
3331
+ const effective = underlying[policy];
3332
+ loader = require_http.markEffectiveContextLoader(async (url, options) => {
3333
+ if (released) return await effective(url, options);
3334
+ const normalizedUrl = normalizeUrl(url);
3335
+ const key = `${policy}\n${normalizedUrl}`;
3336
+ let promise = cache.get(key);
3337
+ if (promise == null) {
3338
+ promise = Promise.resolve(effective(url, options)).then((response) => {
3339
+ const cloned = normalizeResponse(structuredClone(response));
3340
+ recorded.set(key, {
3341
+ policy,
3342
+ url: normalizedUrl,
3343
+ response: structuredClone(cloned)
3344
+ });
3345
+ return cloned;
3346
+ });
3347
+ promise.catch(() => {});
3348
+ cache.set(key, promise);
3349
+ }
3350
+ return structuredClone(await promise);
3351
+ }, policy);
3352
+ loaders.set(policy, loader);
3353
+ return loader;
3354
+ },
3355
+ entries() {
3356
+ return [...recorded.values()];
3357
+ },
3358
+ release() {
3359
+ released = true;
3360
+ }
3361
+ };
3362
+ }
3363
+ function getBundledResponse(url) {
3364
+ if (!(url in _fedify_vocab_runtime.preloadedContexts)) return null;
3365
+ return {
3366
+ contextUrl: null,
3367
+ document: _fedify_vocab_runtime.preloadedContexts[url],
3368
+ documentUrl: url
3369
+ };
3370
+ }
3371
+ async function sha256(value) {
3372
+ return (0, byte_encodings_hex.encodeHex)(await crypto.subtle.digest("SHA-256", new TextEncoder().encode((0, json_canon.default)(value))));
3373
+ }
3374
+ function normalizeResponse(response) {
3375
+ return {
3376
+ contextUrl: response.contextUrl ?? null,
3377
+ document: response.document,
3378
+ documentUrl: response.documentUrl
3379
+ };
3380
+ }
3381
+ /**
3382
+ * Serializes the entries of a context snapshot for a queued message.
3383
+ * @param entries The entries.
3384
+ * @returns The serialized entries, which are plain JSON values.
3385
+ * @internal
3386
+ */
3387
+ async function serializeContextSnapshot(entries) {
3388
+ const result = [];
3389
+ for (const entry of entries) {
3390
+ const response = normalizeResponse(entry.response);
3391
+ const hash = await sha256(response);
3392
+ const bundled = getBundledResponse(entry.url);
3393
+ if (bundled != null && await sha256(bundled) === hash) result.push({
3394
+ policy: entry.policy,
3395
+ url: entry.url,
3396
+ sha256: hash,
3397
+ bundled: true
3398
+ });
3399
+ else result.push({
3400
+ policy: entry.policy,
3401
+ url: entry.url,
3402
+ sha256: hash,
3403
+ response: structuredClone(response)
3404
+ });
3405
+ }
3406
+ return result;
3407
+ }
3408
+ function isRemoteDocument(value) {
3409
+ if (value == null || typeof value !== "object") return false;
3410
+ const record = value;
3411
+ return typeof record.documentUrl === "string" && "document" in record && (record.contextUrl == null || typeof record.contextUrl === "string");
3412
+ }
3413
+ /**
3414
+ * Restores the entries of a serialized context snapshot, verifying the
3415
+ * hashes of stored responses. Bundled entries are reconstructed from the
3416
+ * contexts this version of Fedify bundles.
3417
+ * @param entries The serialized entries.
3418
+ * @returns The entries.
3419
+ * @throws {ContextSnapshotMissError} If an entry is malformed, or its
3420
+ * content does not match its hash.
3421
+ * @internal
3422
+ */
3423
+ async function deserializeContextSnapshot(entries) {
3424
+ if (!Array.isArray(entries)) throw new ContextSnapshotMissError("(snapshot)", "malformed snapshot");
3425
+ const result = [];
3426
+ for (const entry of entries) {
3427
+ if (entry == null || typeof entry !== "object" || entry.policy !== "standard" && entry.policy !== "normalization" || typeof entry.url !== "string" || typeof entry.sha256 !== "string") throw new ContextSnapshotMissError("(snapshot)", "malformed entry");
3428
+ let response;
3429
+ if (entry.bundled === true) {
3430
+ response = getBundledResponse(entry.url);
3431
+ if (response == null) throw new ContextSnapshotMissError(entry.url, "not bundled");
3432
+ } else if (isRemoteDocument(entry.response)) response = normalizeResponse(entry.response);
3433
+ else throw new ContextSnapshotMissError(entry.url, "malformed entry");
3434
+ if (entry.bundled !== true && await sha256(response) !== entry.sha256) throw new ContextSnapshotMissError(entry.url, "content mismatch");
3435
+ result.push({
3436
+ policy: entry.policy,
3437
+ url: entry.url,
3438
+ response: structuredClone(response)
3439
+ });
3440
+ }
3441
+ return result;
3442
+ }
3443
+ /**
3444
+ * Creates a context loader of a policy that serves only the responses of
3445
+ * a snapshot. It is marked as the effective loader of the policy.
3446
+ * @param entries The snapshot's entries.
3447
+ * @param policy The policy.
3448
+ * @returns The loader, which throws {@link ContextSnapshotMissError} for
3449
+ * anything else.
3450
+ * @internal
3451
+ */
3452
+ function createSnapshotOnlyLoader(entries, policy) {
3453
+ const responses = /* @__PURE__ */ new Map();
3454
+ for (const entry of entries) if (entry.policy === policy) responses.set(entry.url, entry.response);
3455
+ return require_http.markEffectiveContextLoader(async (url) => {
3456
+ const response = responses.get(normalizeUrl(url));
3457
+ if (response == null) throw new ContextSnapshotMissError(url, "not in the snapshot");
3458
+ return structuredClone(response);
3459
+ }, policy);
3460
+ }
3461
+ /**
3462
+ * Creates a {@link ReleasableLoader}.
3463
+ * @param strict The loader to use until release.
3464
+ * @param fallback The loader to use after release.
3465
+ * @param policy The policy both loaders apply.
3466
+ * @returns The releasable loader.
3467
+ * @internal
3468
+ */
3469
+ function createReleasableLoader(strict, fallback, policy) {
3470
+ let released = false;
3471
+ return {
3472
+ loader: require_http.markEffectiveContextLoader((url, options) => released ? fallback(url, options) : strict(url, options), policy),
3473
+ release() {
3474
+ released = true;
3475
+ }
3476
+ };
3477
+ }
3478
+ /**
3479
+ * Expands a JSON-LD document with the given context loader.
3480
+ * @param document The document.
3481
+ * @param contextLoader The context loader.
3482
+ * @returns The expanded document.
3483
+ * @internal
3484
+ */
3485
+ async function expandDocument(document, contextLoader) {
3486
+ return await _fedify_vocab_runtime_jsonld.default.expand(document, { documentLoader: contextLoader });
3487
+ }
3488
+ /**
3489
+ * Computes the digest of an expanded document.
3490
+ * @param expanded The expanded document.
3491
+ * @returns The hex-encoded SHA-256 of its canonical JSON.
3492
+ * @internal
3493
+ */
3494
+ function digestExpandedDocument(expanded) {
3495
+ return sha256(expanded);
3496
+ }
3497
+ /**
3498
+ * Deeply freezes a JSON value.
3499
+ * @param value The value.
3500
+ * @returns The same value, frozen.
3501
+ * @internal
3502
+ */
3503
+ function deepFreeze(value) {
3504
+ if (value != null && typeof value === "object" && !Object.isFrozen(value)) {
3505
+ Object.freeze(value);
3506
+ for (const child of Object.values(value)) deepFreeze(child);
3507
+ }
3508
+ return value;
3509
+ }
3510
+ const ACTOR_IRI = "https://www.w3.org/ns/activitystreams#actor";
3511
+ /**
3512
+ * Gets the single root node of an expanded document.
3513
+ * @param expanded The expanded document.
3514
+ * @returns The root node, or `null` if there is not exactly one.
3515
+ * @internal
3516
+ */
3517
+ function getRootNode(expanded) {
3518
+ if (expanded.length !== 1) return null;
3519
+ const node = expanded[0];
3520
+ if (node == null || typeof node !== "object" || Array.isArray(node)) return null;
3521
+ return node;
3522
+ }
3523
+ /**
3524
+ * Gets the values of a property of an expanded node, unwrapping a single
3525
+ * `@list` container the way generated vocabulary decoders do.
3526
+ * @param node The expanded node.
3527
+ * @param iri The property IRI.
3528
+ * @returns The values, or an empty array if the property is absent.
3529
+ * @internal
3530
+ */
3531
+ function getPropertyValues(node, iri) {
3532
+ const values = node[iri];
3533
+ if (values == null) return [];
3534
+ if (!Array.isArray(values)) return [values];
3535
+ if (values.length === 1 && values[0] != null && typeof values[0] === "object" && "@list" in values[0]) {
3536
+ const list = values[0]["@list"];
3537
+ return Array.isArray(list) ? list : [list];
3538
+ }
3539
+ return values;
3540
+ }
3541
+ /**
3542
+ * Tells whether the root node of an expanded activity declares any `actor`
3543
+ * value at all, including blank nodes, anonymous or untyped nodes, and
3544
+ * literals, which ID accessors and typed parsing may drop.
3545
+ * @param expanded The expanded activity.
3546
+ * @returns `"absent"` if the root node declares no actor; `"present"`
3547
+ * otherwise, including when there is no single root node.
3548
+ * @internal
3549
+ */
3550
+ function getActorPresence(expanded) {
3551
+ const root = getRootNode(expanded);
3552
+ if (root == null) return "present";
3553
+ return getPropertyValues(root, ACTOR_IRI).length < 1 ? "absent" : "present";
3554
+ }
3555
+ /**
3556
+ * Builds the serialized view of an authenticated activity. The document is
3557
+ * expanded through the snapshot, so that the snapshot holds every context
3558
+ * response the interpretation needs.
3559
+ * @param snapshot The delivery's context snapshot.
3560
+ * @param source Which document the activity was parsed from.
3561
+ * @returns The serialized view and the expanded activity.
3562
+ * @internal
3563
+ */
3564
+ async function buildVerifiedActivityView(snapshot, source) {
3565
+ const loader = snapshot.loader(source.policy);
3566
+ const used = /* @__PURE__ */ new Set();
3567
+ const expanded = await expandDocument(source.document, require_http.markEffectiveContextLoader((url, options) => {
3568
+ used.add(normalizeUrl(url));
3569
+ return loader(url, options);
3570
+ }, source.policy));
3571
+ const entries = snapshot.entries().filter((entry) => entry.policy === source.policy && used.has(entry.url));
3572
+ const contexts = await serializeContextSnapshot(entries);
3573
+ const references = /* @__PURE__ */ new Set();
3574
+ collectContextReferences(source.document, source.baseUrl?.href, references);
3575
+ for (const entry of entries) {
3576
+ const { contextUrl, document } = entry.response;
3577
+ collectContextReferences(document, entry.url, references);
3578
+ if (contextUrl != null) collectContextReferences({ "@context": contextUrl }, entry.url, references);
3579
+ }
3580
+ return {
3581
+ replayable: [...references].every((url) => used.has(url)),
3582
+ view: {
3583
+ version: 1,
3584
+ policy: source.policy,
3585
+ source: source.source,
3586
+ baseUrl: source.baseUrl?.href ?? null,
3587
+ contexts,
3588
+ expandedDigest: await digestExpandedDocument(expanded)
3589
+ },
3590
+ expanded
3591
+ };
3592
+ }
3593
+ /**
3594
+ * Collects the remote context references of a JSON-LD document or context
3595
+ * document: every IRI given as a value of `@context` at any depth, including
3596
+ * scoped contexts of term definitions, and of `@import`.
3597
+ * @param value The document.
3598
+ * @param base The URL to resolve relative references against.
3599
+ * @param references The set to add the normalized references to.
3600
+ */
3601
+ function collectContextReferences(value, base, references) {
3602
+ const add = (reference) => {
3603
+ let url = reference;
3604
+ if (base != null && URL.canParse(reference, base)) url = new URL(reference, base).href;
3605
+ references.add(normalizeUrl(url));
3606
+ };
3607
+ const collectContext = (context) => {
3608
+ if (typeof context === "string") add(context);
3609
+ else if (Array.isArray(context)) for (const item of context) collectContext(item);
3610
+ else if (context != null && typeof context === "object") {
3611
+ const imported = context["@import"];
3612
+ if (typeof imported === "string") add(imported);
3613
+ walk(context);
3614
+ }
3615
+ };
3616
+ const walk = (node) => {
3617
+ if (Array.isArray(node)) for (const item of node) walk(item);
3618
+ else if (node != null && typeof node === "object") for (const [key, item] of Object.entries(node)) if (key === "@context") collectContext(item);
3619
+ else walk(item);
3620
+ };
3621
+ walk(value);
3622
+ }
3623
+ /**
3624
+ * Validates the shape of a serialized view taken from a queued message.
3625
+ * @param value The value of the message's `verifiedView` field.
3626
+ * @returns The view, or `null` if it is malformed or of an unsupported
3627
+ * version.
3628
+ * @internal
3629
+ */
3630
+ function parseSerializedVerifiedActivityView(value) {
3631
+ if (value == null || typeof value !== "object") return null;
3632
+ const view = value;
3633
+ if (view.version !== 1 || view.policy !== "standard" && view.policy !== "normalization" || view.source !== "activity" && view.source !== "normalizedActivity" || view.baseUrl !== null && (typeof view.baseUrl !== "string" || !URL.canParse(view.baseUrl)) || !Array.isArray(view.contexts) || typeof view.expandedDigest !== "string") return null;
3634
+ return view;
3635
+ }
3636
+ /**
3637
+ * Restores a view from a queued message without loading any context.
3638
+ * @param view The serialized view.
3639
+ * @param message The fields of the message the view refers to.
3640
+ * @returns The restored view.
3641
+ * @throws {ContextSnapshotMissError} If the view cannot be replayed exactly.
3642
+ * @internal
3643
+ */
3644
+ async function restoreVerifiedActivityView(view, message) {
3645
+ const loader = createSnapshotOnlyLoader(await deserializeContextSnapshot(view.contexts), view.policy);
3646
+ if (view.source === "normalizedActivity" && message.normalizedActivity == null) throw new ContextSnapshotMissError("(normalizedActivity)", "the normalized activity is missing");
3647
+ const document = require_http.detachSignature(view.source === "activity" ? message.activity : message.normalizedActivity);
3648
+ const expanded = await expandDocument(document, loader);
3649
+ if (await digestExpandedDocument(expanded) !== view.expandedDigest) throw new ContextSnapshotMissError("(activity)", "the interpretation does not match the authenticated one");
3650
+ return {
3651
+ document,
3652
+ loader,
3653
+ expanded,
3654
+ baseUrl: view.baseUrl == null ? null : new URL(view.baseUrl)
3655
+ };
3656
+ }
3657
+ //#endregion
2987
3658
  //#region src/federation/keycache.ts
2988
3659
  const Temporal$9 = require_http.Temporal;
2989
3660
  const KEY_CACHE_GENERATION = "3";
@@ -5405,11 +6076,12 @@ async function handleInbox(request, options) {
5405
6076
  }, async (span) => {
5406
6077
  if (options.recipient != null) span.setAttribute("fedify.inbox.recipient", options.recipient);
5407
6078
  const spanContext = span.spanContext();
6079
+ const snapshot = createContextSnapshot(options.context.contextLoader);
5408
6080
  try {
5409
6081
  return await (0, _logtape_logtape.withContext)({
5410
6082
  traceId: spanContext.traceId,
5411
6083
  spanId: spanContext.spanId
5412
- }, () => handleInboxInternal(request, options, span));
6084
+ }, () => handleInboxInternal(request, options, span, snapshot));
5413
6085
  } catch (e) {
5414
6086
  options.observation.hasException = true;
5415
6087
  span.setStatus({
@@ -5418,6 +6090,7 @@ async function handleInbox(request, options) {
5418
6090
  });
5419
6091
  throw e;
5420
6092
  } finally {
6093
+ snapshot.release();
5421
6094
  options.observation.project(span);
5422
6095
  span.end();
5423
6096
  }
@@ -5429,9 +6102,10 @@ async function handleInbox(request, options) {
5429
6102
  * @param request The HTTP request.
5430
6103
  * @param options The parameters for handling the inbox.
5431
6104
  * @param span The OpenTelemetry span for tracing.
6105
+ * @param snapshot The context snapshot of the delivery.
5432
6106
  * @returns A promise that resolves to an HTTP response.
5433
6107
  */
5434
- async function handleInboxInternal(request, parameters, span) {
6108
+ async function handleInboxInternal(request, parameters, span, snapshot) {
5435
6109
  const { recipient, context: ctx, inboxContextFactory, kv, kvPrefixes, publicKeyTtl, queue, actorDispatcher, inboxListeners, inboxErrorHandler, unverifiedActivityHandler, onNotFound, signatureTimeWindow, maxHttpSignatures, skipSignatureVerification, inboxChallengePolicy, meterProvider, tracerProvider, portableInbox } = parameters;
5436
6110
  const observation = parameters.observation;
5437
6111
  const signatureObservation = { [require_http.verificationObservation]: observation.verification };
@@ -5545,6 +6219,8 @@ async function handleInboxInternal(request, parameters, span) {
5545
6219
  tracerProvider,
5546
6220
  keyTtl: publicKeyTtl
5547
6221
  });
6222
+ const standardLoader = snapshot.loader("standard");
6223
+ const normalizationLoader = snapshot.loader("normalization");
5548
6224
  const jsonWithoutSig = require_http.detachSignature(json);
5549
6225
  const hasLdSignature = require_http.hasSignature(json);
5550
6226
  const canAttemptAlternateAuthAfterLdSignatureFailure = skipSignatureVerification || hasHttpSignatureHeaders(request) || hasObjectIntegrityProof(jsonWithoutSig);
@@ -5580,9 +6256,10 @@ async function handleInboxInternal(request, parameters, span) {
5580
6256
  let compactedJson = json;
5581
6257
  let compactedJsonWithoutSig = jsonWithoutSig;
5582
6258
  let ldSigVerified = false;
6259
+ let ldSigKey = null;
5583
6260
  if (hasLdSignature) {
5584
6261
  try {
5585
- compactedJson = await require_http.compactJsonLd(json, ctx.contextLoader);
6262
+ compactedJson = await require_http.compactJsonLd(json, normalizationLoader);
5586
6263
  } catch (error) {
5587
6264
  if (isInvalidJsonLdError(error)) {
5588
6265
  logger.error("Failed to parse JSON-LD:\n{error}", {
@@ -5618,16 +6295,18 @@ async function handleInboxInternal(request, parameters, span) {
5618
6295
  if (compactedJson !== json) {
5619
6296
  compactedJsonWithoutSig = require_http.detachSignature(compactedJson);
5620
6297
  try {
5621
- ldSigVerified = await require_http.verifyCompactJsonLd(compactedJson, {
6298
+ const ldResult = await require_http.verifyCompactJsonLdDetailed(compactedJson, {
5622
6299
  ...signatureObservation,
5623
- contextLoader: ctx.contextLoader,
6300
+ contextLoader: normalizationLoader,
5624
6301
  documentLoader: ctx.documentLoader,
5625
6302
  keyCache,
5626
6303
  meterProvider,
5627
6304
  tracerProvider
5628
6305
  });
6306
+ ldSigVerified = ldResult.verified;
6307
+ ldSigKey = ldResult.key;
5629
6308
  } catch (error) {
5630
- if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(compactedJsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(error);
6309
+ if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(compactedJsonWithoutSig, normalizationLoader)) return await respondInvalidActivity(error);
5631
6310
  if (isInvalidJsonLdError(error)) {
5632
6311
  logger.error("Failed to parse JSON-LD:\n{error}", {
5633
6312
  recipient,
@@ -5645,12 +6324,12 @@ async function handleInboxInternal(request, parameters, span) {
5645
6324
  if (!canAttemptAlternateAuthAfterLdSignatureFailure) throw error;
5646
6325
  if (!skipSignatureVerification) try {
5647
6326
  await _fedify_vocab.Object.fromJsonLd(compactedJson, {
5648
- contextLoader: require_http.getNormalizationContextLoader(ctx.contextLoader),
6327
+ contextLoader: normalizationLoader,
5649
6328
  documentLoader: ctx.documentLoader,
5650
6329
  tracerProvider
5651
6330
  });
5652
6331
  } catch (parseError) {
5653
- if (parseError instanceof RangeError && await hasMalformedKnownTemporalLiteral(compactedJsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(parseError);
6332
+ if (parseError instanceof RangeError && await hasMalformedKnownTemporalLiteral(compactedJsonWithoutSig, normalizationLoader)) return await respondInvalidActivity(parseError);
5654
6333
  if (isInvalidJsonLdError(parseError)) {
5655
6334
  logger.error("Failed to parse JSON-LD:\n{error}", {
5656
6335
  recipient,
@@ -5674,6 +6353,33 @@ async function handleInboxInternal(request, parameters, span) {
5674
6353
  let activity = null;
5675
6354
  let activityVerified = false;
5676
6355
  let proofVerified = false;
6356
+ let documentSigners = [];
6357
+ let requestSigners = [];
6358
+ let viewSource;
6359
+ const rawViewSource = {
6360
+ policy: "standard",
6361
+ source: "activity",
6362
+ document: jsonWithoutSig,
6363
+ baseUrl: null
6364
+ };
6365
+ const isActorlessCandidate = async (candidate, source) => {
6366
+ if (skipSignatureVerification) return false;
6367
+ if (inboxListeners?.dispatchWithClass(candidate)?.options.allowActorless !== true) return false;
6368
+ try {
6369
+ return getActorPresence(await expandDocument(source.document, snapshot.loader(source.policy))) === "absent";
6370
+ } catch {
6371
+ return false;
6372
+ }
6373
+ };
6374
+ const verifyProofs = (contextLoader = standardLoader) => require_http.verifyObjectDetailed(_fedify_vocab.Activity, jsonWithoutSig, {
6375
+ ...signatureObservation,
6376
+ contextLoader,
6377
+ documentLoader: ctx.documentLoader,
6378
+ keyCache,
6379
+ meterProvider,
6380
+ tracerProvider,
6381
+ verifyPortableObject: ctx.verifyPortableObject
6382
+ });
5677
6383
  if (ldSigVerified) {
5678
6384
  logger.debug("Linked Data Signatures are verified.", {
5679
6385
  recipient,
@@ -5682,18 +6388,49 @@ async function handleInboxInternal(request, parameters, span) {
5682
6388
  try {
5683
6389
  activity = observation.activity = await _fedify_vocab.Activity.fromJsonLd(compactedJsonWithoutSig, {
5684
6390
  ...ctx,
5685
- contextLoader: require_http.getNormalizationContextLoader(ctx.contextLoader)
6391
+ contextLoader: normalizationLoader
5686
6392
  });
5687
6393
  } catch (error) {
5688
- if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(compactedJsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(error);
6394
+ if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(compactedJsonWithoutSig, normalizationLoader)) return await respondInvalidActivity(error);
5689
6395
  if (!isPermanentActivityParseError(error)) throw error;
5690
6396
  return await respondInvalidActivity(error);
5691
6397
  }
5692
6398
  activityVerified = true;
6399
+ viewSource = {
6400
+ policy: "normalization",
6401
+ source: "normalizedActivity",
6402
+ document: compactedJsonWithoutSig,
6403
+ baseUrl: null
6404
+ };
6405
+ if (ldSigKey?.ownerId != null) documentSigners = [{
6406
+ kind: "actor",
6407
+ id: ldSigKey.ownerId,
6408
+ mechanism: "linkedDataSignatures",
6409
+ keyId: ldSigKey.id
6410
+ }];
6411
+ if (await isActorlessCandidate(activity, viewSource)) {
6412
+ let result = null;
6413
+ try {
6414
+ result = await verifyProofs();
6415
+ } catch (error) {
6416
+ if (!isPermanentActivityParseError(error)) throw error;
6417
+ logger.debug("Failed to verify the Object Integrity Proofs of the activity without an actor:\n{error}", {
6418
+ recipient,
6419
+ error
6420
+ });
6421
+ }
6422
+ if (result?.status === "unavailable") throw result.error;
6423
+ if (result?.status === "verified" && result.signers.length > 0) {
6424
+ activity = observation.activity = result.object;
6425
+ documentSigners = toProofPrincipals(result.signers);
6426
+ viewSource = rawViewSource;
6427
+ proofVerified = true;
6428
+ }
6429
+ }
5693
6430
  if (!skipSignatureVerification && isPortableActivity(activity)) try {
5694
6431
  proofVerified = await require_http.verifyObject(_fedify_vocab.Activity, jsonWithoutSig, {
5695
6432
  ...signatureObservation,
5696
- contextLoader: require_http.wrapContextLoaderForJsonLd(ctx.contextLoader),
6433
+ contextLoader: standardLoader,
5697
6434
  documentLoader: ctx.documentLoader,
5698
6435
  keyCache,
5699
6436
  meterProvider,
@@ -5711,18 +6448,11 @@ async function handleInboxInternal(request, parameters, span) {
5711
6448
  recipient,
5712
6449
  json
5713
6450
  });
6451
+ let proofResult;
5714
6452
  try {
5715
- activity = await require_http.verifyObject(_fedify_vocab.Activity, jsonWithoutSig, {
5716
- ...signatureObservation,
5717
- contextLoader: require_http.wrapContextLoaderForJsonLd(ctx.contextLoader),
5718
- documentLoader: ctx.documentLoader,
5719
- keyCache,
5720
- meterProvider,
5721
- tracerProvider,
5722
- verifyPortableObject: ctx.verifyPortableObject
5723
- });
6453
+ proofResult = await verifyProofs();
5724
6454
  } catch (error) {
5725
- if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(jsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(error);
6455
+ if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(jsonWithoutSig, standardLoader)) return await respondInvalidActivity(error);
5726
6456
  if (deferredLdSignatureError != null) {
5727
6457
  logger.debug("Object Integrity Proof fallback did not supersede a deferred Linked Data Signature failure:\n{error}", {
5728
6458
  recipient,
@@ -5758,6 +6488,22 @@ async function handleInboxInternal(request, parameters, span) {
5758
6488
  headers: { "Content-Type": "text/plain; charset=utf-8" }
5759
6489
  });
5760
6490
  }
6491
+ if (proofResult.status === "verified") if (proofResult.signers.length < 1 && await isActorlessCandidate(proofResult.object, rawViewSource)) activity = null;
6492
+ else {
6493
+ activity = proofResult.object;
6494
+ documentSigners = toProofPrincipals(proofResult.signers);
6495
+ viewSource = rawViewSource;
6496
+ }
6497
+ else if (proofResult.status === "unavailable") {
6498
+ let parsed = null;
6499
+ try {
6500
+ parsed = await _fedify_vocab.Activity.fromJsonLd(jsonWithoutSig, {
6501
+ ...ctx,
6502
+ contextLoader: standardLoader
6503
+ });
6504
+ } catch {}
6505
+ if (parsed != null && await isActorlessCandidate(parsed, rawViewSource)) throw proofResult.error;
6506
+ }
5761
6507
  if (activity == null) logger.debug("Object Integrity Proofs are not verified.", {
5762
6508
  recipient,
5763
6509
  activity: json
@@ -5887,14 +6633,28 @@ async function handleInboxInternal(request, parameters, span) {
5887
6633
  try {
5888
6634
  activity = observation.activity = await _fedify_vocab.Activity.fromJsonLd(jsonWithoutSig, {
5889
6635
  ...ctx,
5890
- contextLoader: require_http.wrapContextLoaderForJsonLd(ctx.contextLoader)
6636
+ contextLoader: standardLoader
5891
6637
  });
5892
6638
  } catch (error) {
5893
6639
  if (!isPermanentActivityParseError(error)) throw error;
5894
6640
  return await respondInvalidActivity(error);
5895
6641
  }
6642
+ viewSource = rawViewSource;
5896
6643
  }
5897
6644
  observation.activity = activity;
6645
+ let view;
6646
+ try {
6647
+ view = await buildVerifiedActivityView(snapshot, viewSource ?? rawViewSource);
6648
+ } catch (error) {
6649
+ if (!isPermanentActivityParseError(error)) throw error;
6650
+ return await respondInvalidActivity(error);
6651
+ }
6652
+ const actorless = !skipSignatureVerification && getActorPresence(view.expanded) === "absent" && inboxListeners?.dispatchWithClass(activity)?.options.allowActorless === true;
6653
+ span.setAttribute("activitypub.activity.actorless", actorless);
6654
+ if (actorless && httpSigKey != null) {
6655
+ const principal = getHttpKeyPrincipal(httpSigKey);
6656
+ if (principal != null) requestSigners = [principal];
6657
+ }
5898
6658
  observation.stage = "policy";
5899
6659
  observation.result = {
5900
6660
  disposition: "rejected",
@@ -5938,7 +6698,7 @@ async function handleInboxInternal(request, parameters, span) {
5938
6698
  const rejection = await require_http.observeAttempt(signatureObservation, "objectIntegrity", async (verification) => {
5939
6699
  const rejection = await verifyPortableActivityId(activity.id, jsonWithoutSig, {
5940
6700
  [require_http.verificationObservation]: verification,
5941
- contextLoader: require_http.wrapContextLoaderForJsonLd(ctx.contextLoader),
6701
+ contextLoader: standardLoader,
5942
6702
  documentLoader: ctx.documentLoader,
5943
6703
  keyCache,
5944
6704
  meterProvider,
@@ -5976,7 +6736,7 @@ async function handleInboxInternal(request, parameters, span) {
5976
6736
  });
5977
6737
  }
5978
6738
  }
5979
- if (httpSigKey != null && !await require_portable_collection.doesActorOwnKey(activity, httpSigKey, ctx)) {
6739
+ if (httpSigKey != null && !actorless && !await require_portable_collection.doesActorOwnKey(activity, httpSigKey, ctx)) {
5980
6740
  if (deferredLdSignatureError != null) throw deferredLdSignatureError;
5981
6741
  observation.authentication = {
5982
6742
  status: "rejected",
@@ -6002,6 +6762,66 @@ async function handleInboxInternal(request, parameters, span) {
6002
6762
  headers: { "Content-Type": "text/plain; charset=utf-8" }
6003
6763
  });
6004
6764
  }
6765
+ if (httpSigKey != null && !actorless && activity.actorId != null) requestSigners = [{
6766
+ kind: "actor",
6767
+ id: activity.actorId,
6768
+ mechanism: "httpSignatures",
6769
+ keyId: httpSigKey.id
6770
+ }];
6771
+ if (actorless) {
6772
+ const check = checkActorlessActivity(activity, {
6773
+ verified: true,
6774
+ requestSigners,
6775
+ documentSigners,
6776
+ originAuthenticated: [],
6777
+ expandedActivity: [],
6778
+ baseUrl: null
6779
+ });
6780
+ if (check.status === "noAuthenticatedPrincipal") {
6781
+ if (deferredLdSignatureError != null) throw deferredLdSignatureError;
6782
+ observation.authentication = {
6783
+ status: "rejected",
6784
+ reason: { type: "noAuthenticatedPrincipal" }
6785
+ };
6786
+ logger.error("The activity {activityId} declares no actor, and no principal is authenticated for it.", {
6787
+ activity: json,
6788
+ recipient,
6789
+ activityId: activity.id?.href
6790
+ });
6791
+ span.setStatus({
6792
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
6793
+ message: "No principal is authenticated for the activity."
6794
+ });
6795
+ return new Response("No principal is authenticated for the activity.", {
6796
+ status: 401,
6797
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
6798
+ });
6799
+ } else if (check.status === "activityOriginMismatch") {
6800
+ if (deferredLdSignatureError != null) throw deferredLdSignatureError;
6801
+ observation.authentication = {
6802
+ status: "rejected",
6803
+ reason: {
6804
+ type: "activityOriginMismatch",
6805
+ activityId: new URL(activity.id.href),
6806
+ actorIds: check.principals.map((principal) => principal.id)
6807
+ }
6808
+ };
6809
+ logger.error("The activity ({activityId}) does not have the same origin as the principals that authenticated it.", {
6810
+ activity: json,
6811
+ recipient,
6812
+ activityId: activity.id?.href,
6813
+ principals: check.principals.map((principal) => principal.id.href)
6814
+ });
6815
+ span.setStatus({
6816
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
6817
+ message: `The activity (${activity.id?.href}) does not have the same origin as the principals that authenticated it.`
6818
+ });
6819
+ return new Response("The activity and its signers do not have the same origin.", {
6820
+ status: 401,
6821
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
6822
+ });
6823
+ }
6824
+ }
6005
6825
  if (!skipSignatureVerification) {
6006
6826
  const compoundJson = Array.isArray(json) ? json : jsonWithoutSig;
6007
6827
  if (inspectCompoundPortableObjectApplicability(compoundJson, INBOX_COMPOUND_PROOF_LIMITS) !== "absent") {
@@ -6061,7 +6881,7 @@ async function handleInboxInternal(request, parameters, span) {
6061
6881
  return await getFailedSignatureResponse(inboxChallengePolicy, kv, kvPrefixes);
6062
6882
  }
6063
6883
  }
6064
- if (!skipSignatureVerification && activity.id != null && !actorsHaveSameOrigin(activity)) {
6884
+ if (!skipSignatureVerification && !actorless && activity.id != null && !actorsHaveSameOrigin(activity)) {
6065
6885
  observation.authentication = {
6066
6886
  status: "rejected",
6067
6887
  reason: {
@@ -6090,7 +6910,7 @@ async function handleInboxInternal(request, parameters, span) {
6090
6910
  attempts: observation.verification.attempts.filter((attempt) => attempt.status === "verified")
6091
6911
  };
6092
6912
  observation.stage = "dispatch";
6093
- if (activity.actorId == null) {
6913
+ if (!actorless && activity.actorId == null) {
6094
6914
  observation.result = {
6095
6915
  disposition: "rejected",
6096
6916
  reason: "missingActor"
@@ -6114,6 +6934,17 @@ async function handleInboxInternal(request, parameters, span) {
6114
6934
  originalJson: json,
6115
6935
  normalizedActivity: hasLdSignature && compactedJson !== json ? compactedJson : void 0,
6116
6936
  ldSignatureVerified: hasLdSignature ? ldSigVerified : void 0,
6937
+ authentication: createAuthenticationEvidence({
6938
+ verified: !skipSignatureVerification,
6939
+ requestSigners,
6940
+ documentSigners,
6941
+ originAuthenticated: [],
6942
+ expandedActivity: deepFreeze(view.expanded),
6943
+ baseUrl: null
6944
+ }),
6945
+ verifiedView: view.view,
6946
+ replayable: view.replayable,
6947
+ actorless,
6117
6948
  activity,
6118
6949
  recipient,
6119
6950
  inboxListeners,
@@ -6838,6 +7669,42 @@ const MIN_COMPONENTS = [
6838
7669
  "@authority"
6839
7670
  ];
6840
7671
  /**
7672
+ * Turns the signers of verified Object Integrity Proofs into principals.
7673
+ */
7674
+ function toProofPrincipals(signers) {
7675
+ return signers.map((signer) => ({
7676
+ kind: signer.kind,
7677
+ id: signer.id,
7678
+ mechanism: "objectIntegrityProofs",
7679
+ keyId: signer.keyId
7680
+ }));
7681
+ }
7682
+ /**
7683
+ * Gets the principal of an HTTP Signature key, whose owner was confirmed by
7684
+ * the owner's own document, or by the signed document of a portable actor,
7685
+ * when the key was fetched.
7686
+ */
7687
+ function getHttpKeyPrincipal(key) {
7688
+ const evidence = require_http.getVerifiedKeyOwnerEvidence(key);
7689
+ if (evidence != null) try {
7690
+ return {
7691
+ kind: "actor",
7692
+ id: (0, _fedify_vocab_runtime.parseIri)(evidence.ownerId),
7693
+ mechanism: "httpSignatures",
7694
+ keyId: key.id
7695
+ };
7696
+ } catch {
7697
+ return null;
7698
+ }
7699
+ if (key.ownerId == null || require_http.isPortableId(key.ownerId)) return null;
7700
+ return {
7701
+ kind: "actor",
7702
+ id: key.ownerId,
7703
+ mechanism: "httpSignatures",
7704
+ keyId: key.id
7705
+ };
7706
+ }
7707
+ /**
6841
7708
  * Checks whether an HTTP Signature key is a key of an FEP-ef61 portable
6842
7709
  * actor, i.e., its ID is a compatible identifier or an `ap:` or `ap+ef61:`
6843
7710
  * URI, and it is owned by a portable actor, which only the portable actor's
@@ -9429,24 +10296,102 @@ var FederationImpl = class extends FederationBuilderImpl {
9429
10296
  let dispatched;
9430
10297
  let parseInput = void 0;
9431
10298
  let parseContextLoader = context.contextLoader;
10299
+ let authentication = getEmptyAuthenticationEvidence();
10300
+ let actorPresence = "present";
10301
+ if (message.verifiedView !== void 0) {
10302
+ const view = parseSerializedVerifiedActivityView(message.verifiedView);
10303
+ let restored;
10304
+ let restoredAuthentication = null;
10305
+ let failure;
10306
+ if (view == null) failure = /* @__PURE__ */ new TypeError("The queued activity has a malformed verified view.");
10307
+ else try {
10308
+ if (view.source === "normalizedActivity") require_http.assertSafeJsonLd(message.normalizedActivity);
10309
+ restored = await restoreVerifiedActivityView(view, message);
10310
+ } catch (error) {
10311
+ if (!(error instanceof ContextSnapshotMissError) && !isPermanentInboxParseError(error)) {
10312
+ await handleRetriableFailure(error);
10313
+ return;
10314
+ }
10315
+ failure = error;
10316
+ }
10317
+ if (restored != null) {
10318
+ const expandedActivity = deepFreeze(restored.expanded);
10319
+ restoredAuthentication = message.authentication === void 0 ? createAuthenticationEvidence({
10320
+ ...getEmptyAuthenticationEvidence(),
10321
+ expandedActivity,
10322
+ baseUrl: restored.baseUrl
10323
+ }) : deserializeAuthentication(message.authentication, expandedActivity, restored.baseUrl);
10324
+ if (restoredAuthentication == null) failure = /* @__PURE__ */ new TypeError("The queued activity has malformed authentication evidence.");
10325
+ }
10326
+ if (failure != null || restored == null || restoredAuthentication == null) {
10327
+ await reportInboxError(failure);
10328
+ logger.error("Failed to replay the queued incoming activity:\n{error}", {
10329
+ error: failure,
10330
+ trial: message.attempt,
10331
+ activity: message.activity,
10332
+ recipient: message.identifier
10333
+ });
10334
+ listenerSpan.setStatus({
10335
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
10336
+ message: String(failure)
10337
+ });
10338
+ listenerSpan.end();
10339
+ return;
10340
+ }
10341
+ authentication = restoredAuthentication;
10342
+ actorPresence = getActorPresence(restored.expanded);
10343
+ const policy = view.policy;
10344
+ const replayLoader = createReleasableLoader(restored.loader, policy === "normalization" ? require_http.getNormalizationContextLoader(context.contextLoader) : require_http.wrapContextLoaderForJsonLd(context.contextLoader), policy);
10345
+ parseInput = restored.document;
10346
+ parseContextLoader = restored.loader;
10347
+ try {
10348
+ activity = await _fedify_vocab.Activity.fromJsonLd(restored.document, {
10349
+ ...context,
10350
+ contextLoader: replayLoader.loader,
10351
+ ...restored.baseUrl == null ? {} : { baseUrl: restored.baseUrl }
10352
+ });
10353
+ } catch (error) {
10354
+ if (isPermanentInboxParseError(error)) {
10355
+ await reportInboxError(error);
10356
+ logger.error("Failed to parse the queued incoming activity:\n{error}", {
10357
+ error,
10358
+ trial: message.attempt,
10359
+ activity: message.activity,
10360
+ recipient: message.identifier
10361
+ });
10362
+ listenerSpan.setStatus({
10363
+ code: _opentelemetry_api.SpanStatusCode.ERROR,
10364
+ message: String(error)
10365
+ });
10366
+ listenerSpan.end();
10367
+ return;
10368
+ }
10369
+ await handleRetriableFailure(error);
10370
+ return;
10371
+ } finally {
10372
+ replayLoader.release();
10373
+ }
10374
+ }
9432
10375
  try {
9433
- const hasSignatureField = require_http.hasSignature(message.activity);
9434
- const shouldParseFromNormalizedSignedPayload = message.ldSignatureVerified === true || message.normalizedActivity != null || message.ldSignatureVerified == null && hasSignatureField;
9435
- const parseContext = hasSignatureField ? {
9436
- ...context,
9437
- contextLoader: require_http.getNormalizationContextLoader(context.contextLoader)
9438
- } : {
9439
- ...context,
9440
- contextLoader: require_http.wrapContextLoaderForJsonLd(context.contextLoader)
9441
- };
9442
- parseContextLoader = parseContext.contextLoader;
9443
- let normalizedActivity;
9444
- if (shouldParseFromNormalizedSignedPayload) {
9445
- normalizedActivity = message.normalizedActivity ?? await require_http.compactJsonLd(message.activity, context.contextLoader);
9446
- require_http.assertSafeJsonLd(normalizedActivity);
10376
+ if (activity == null) {
10377
+ const hasSignatureField = require_http.hasSignature(message.activity);
10378
+ const shouldParseFromNormalizedSignedPayload = message.ldSignatureVerified === true || message.normalizedActivity != null || message.ldSignatureVerified == null && hasSignatureField;
10379
+ const parseContext = hasSignatureField ? {
10380
+ ...context,
10381
+ contextLoader: require_http.getNormalizationContextLoader(context.contextLoader)
10382
+ } : {
10383
+ ...context,
10384
+ contextLoader: require_http.wrapContextLoaderForJsonLd(context.contextLoader)
10385
+ };
10386
+ parseContextLoader = parseContext.contextLoader;
10387
+ let normalizedActivity;
10388
+ if (shouldParseFromNormalizedSignedPayload) {
10389
+ normalizedActivity = message.normalizedActivity ?? await require_http.compactJsonLd(message.activity, context.contextLoader);
10390
+ require_http.assertSafeJsonLd(normalizedActivity);
10391
+ }
10392
+ parseInput = shouldParseFromNormalizedSignedPayload ? require_http.detachSignature(normalizedActivity) : hasSignatureField ? require_http.detachSignature(message.activity) : message.activity;
10393
+ activity = await _fedify_vocab.Activity.fromJsonLd(parseInput, parseContext);
9447
10394
  }
9448
- parseInput = shouldParseFromNormalizedSignedPayload ? require_http.detachSignature(normalizedActivity) : hasSignatureField ? require_http.detachSignature(message.activity) : message.activity;
9449
- activity = await _fedify_vocab.Activity.fromJsonLd(parseInput, parseContext);
9450
10395
  activityType = (0, _fedify_vocab.getTypeId)(activity).href;
9451
10396
  span.setAttribute("activitypub.activity.type", activityType);
9452
10397
  listenerSpan.setAttribute("activitypub.activity.type", activityType);
@@ -9455,7 +10400,8 @@ var FederationImpl = class extends FederationBuilderImpl {
9455
10400
  span.setAttribute("activitypub.activity.id", activity.id.href);
9456
10401
  listenerSpan.setAttribute("activitypub.activity.id", activity.id.href);
9457
10402
  }
9458
- if (activity.actorId == null) {
10403
+ const actorless = actorPresence === "absent" && !this.skipSignatureVerification && authentication.verified && this.inboxListeners?.dispatchWithClass(activity)?.options.allowActorless === true && isActorlessActivityAuthenticated(activity, authentication);
10404
+ if (activity.actorId == null && !actorless) {
9459
10405
  logger.error("Missing actor in queued incoming activity.", {
9460
10406
  activityId: activity.id?.href,
9461
10407
  activity: message.activity,
@@ -9469,7 +10415,7 @@ var FederationImpl = class extends FederationBuilderImpl {
9469
10415
  listenerSpan.end();
9470
10416
  return;
9471
10417
  }
9472
- if (!this.skipSignatureVerification && activity.id != null && !actorsHaveSameOrigin(activity)) {
10418
+ if (!this.skipSignatureVerification && !actorless && activity.id != null && !actorsHaveSameOrigin(activity)) {
9473
10419
  logger.error("The queued activity ({activityId}) and its actors do not have the same origin.", {
9474
10420
  activityId: activity.id.href,
9475
10421
  actorIds: activity.actorIds.map((actor) => actor.href),
@@ -9484,22 +10430,18 @@ var FederationImpl = class extends FederationBuilderImpl {
9484
10430
  listenerSpan.end();
9485
10431
  return;
9486
10432
  }
9487
- cacheKey = activity.id == null ? null : [
9488
- ...this.kvPrefixes.activityIdempotence,
9489
- context.origin,
9490
- activity.id.href
9491
- ];
9492
- if (cacheKey != null) {
9493
- if (await this.kv.get(cacheKey) === true) {
9494
- logger.debug("Activity {activityId} has already been processed.", {
9495
- activityId: activity.id?.href,
9496
- activity: message.activity,
9497
- recipient: message.identifier
9498
- });
9499
- require_http.recordInboxActivity(this.meterProvider, "rejected", activityType);
9500
- listenerSpan.end();
9501
- return;
9502
- }
10433
+ const parsedActivity = activity;
10434
+ const parsedActivityType = activityType;
10435
+ cacheKey = await getInboxIdempotencyKey(this.idempotencyStrategy, () => context.toInboxContext(message.identifier, message.activity, parsedActivity.id?.href, parsedActivityType, { authentication }), activity, context.origin, message.identifier, this.kvPrefixes.activityIdempotence);
10436
+ if (await isInboxActivityProcessed(this.kv, cacheKey, activity, context.origin, this.kvPrefixes.activityIdempotence)) {
10437
+ logger.debug("Activity {activityId} has already been processed.", {
10438
+ activityId: activity.id?.href,
10439
+ activity: message.activity,
10440
+ recipient: message.identifier
10441
+ });
10442
+ require_http.recordInboxActivity(this.meterProvider, "rejected", activityType);
10443
+ listenerSpan.end();
10444
+ return;
9503
10445
  }
9504
10446
  dispatched = this.inboxListeners?.dispatchWithClass(activity);
9505
10447
  } catch (error) {
@@ -9558,7 +10500,7 @@ var FederationImpl = class extends FederationBuilderImpl {
9558
10500
  try {
9559
10501
  const started = performance.now();
9560
10502
  try {
9561
- await listener(context.toInboxContext(message.identifier, message.activity, activity.id?.href, activityType), activity);
10503
+ await listener(context.toInboxContext(message.identifier, message.activity, activity.id?.href, activityType, { authentication }), activity);
9562
10504
  } finally {
9563
10505
  this.metrics.recordInboxProcessingDuration(activityType, require_http.getDurationMs(started));
9564
10506
  }
@@ -10865,7 +11807,7 @@ var ContextImpl = class ContextImpl {
10865
11807
  invokedFromActorKeyPairsDispatcher: this.invokedFromActorKeyPairsDispatcher
10866
11808
  });
10867
11809
  }
10868
- toInboxContext(recipient, activity, activityId, activityType) {
11810
+ toInboxContext(recipient, activity, activityId, activityType, metadata) {
10869
11811
  return new InboxContextImpl(recipient, activity, activityId, activityType, {
10870
11812
  url: this.url,
10871
11813
  federation: this.federation,
@@ -10873,7 +11815,7 @@ var ContextImpl = class ContextImpl {
10873
11815
  documentLoader: this.documentLoader,
10874
11816
  contextLoader: this.contextLoader,
10875
11817
  invokedFromActorKeyPairsDispatcher: this.invokedFromActorKeyPairsDispatcher
10876
- });
11818
+ }, metadata?.authentication);
10877
11819
  }
10878
11820
  toOutboxContext(identifier, activity, activityId, activityType) {
10879
11821
  return new OutboxContextImpl(identifier, activity, activityId, activityType, {
@@ -11488,12 +12430,20 @@ var ContextImpl = class ContextImpl {
11488
12430
  });
11489
12431
  }
11490
12432
  async routeActivityInternal(recipient, activity, options = {}, span) {
12433
+ const snapshot = createContextSnapshot(options.contextLoader ?? this.contextLoader);
12434
+ try {
12435
+ return await this.#routeActivityWithSnapshot(recipient, activity, options, span, snapshot);
12436
+ } finally {
12437
+ snapshot.release();
12438
+ }
12439
+ }
12440
+ async #routeActivityWithSnapshot(recipient, activity, options, span, snapshot) {
11491
12441
  const logger = (0, _logtape_logtape.getLogger)([
11492
12442
  "fedify",
11493
12443
  "federation",
11494
12444
  "inbox"
11495
12445
  ]);
11496
- const contextLoader = options.contextLoader ?? this.contextLoader;
12446
+ const contextLoader = snapshot.loader("standard");
11497
12447
  let json = await activity.toJsonLd({ contextLoader });
11498
12448
  const keyCache = new KvKeyCache(this.federation.kv, this.federation.kvPrefixes.publicKey, {
11499
12449
  documentLoader: this.documentLoader,
@@ -11501,13 +12451,19 @@ var ContextImpl = class ContextImpl {
11501
12451
  tracerProvider: this.tracerProvider,
11502
12452
  keyTtl: this.federation.publicKeyTtl
11503
12453
  });
11504
- if (await require_http.verifyObject(_fedify_vocab.Activity, json, {
12454
+ let documentSigners = [];
12455
+ let originAuthenticated = [];
12456
+ let viewSource;
12457
+ let fetchedView;
12458
+ const document = require_http.detachSignature(json);
12459
+ const verified = await require_http.verifyObjectDetailed(_fedify_vocab.Activity, document, {
11505
12460
  contextLoader,
11506
12461
  documentLoader: options.documentLoader ?? this.documentLoader,
11507
12462
  meterProvider: this.meterProvider,
11508
12463
  tracerProvider: options.tracerProvider ?? this.tracerProvider,
11509
12464
  keyCache
11510
- }) == null) {
12465
+ });
12466
+ if (verified.status !== "verified") {
11511
12467
  logger.debug("Object Integrity Proofs are not verified.", {
11512
12468
  recipient,
11513
12469
  activity: json
@@ -11519,8 +12475,29 @@ var ContextImpl = class ContextImpl {
11519
12475
  });
11520
12476
  return false;
11521
12477
  }
11522
- const fetched = await this.lookupObject(activity.id, options);
11523
- if (fetched == null) {
12478
+ let candidate;
12479
+ let candidateView;
12480
+ const verifications = /* @__PURE__ */ new WeakMap();
12481
+ const fetched = await this.lookupObject(activity.id, {
12482
+ ...options,
12483
+ contextLoader,
12484
+ verifyPortableObject: async (document, verifierOptions) => {
12485
+ const result = await this.verifyPortableObject(document, verifierOptions);
12486
+ if (document != null && typeof document === "object") verifications.set(document, result);
12487
+ return result;
12488
+ },
12489
+ [_fedify_vocab.lookupObserver]: { accept: async (accepted) => {
12490
+ candidate = accepted;
12491
+ candidateView = await buildVerifiedActivityView(createContextSnapshot(accepted.contextLoader), {
12492
+ policy: "standard",
12493
+ source: "activity",
12494
+ document: require_http.detachSignature(accepted.document),
12495
+ baseUrl: accepted.baseUrl
12496
+ });
12497
+ return true;
12498
+ } }
12499
+ });
12500
+ if (fetched == null || candidate == null || candidateView == null) {
11524
12501
  logger.debug("Failed to fetch the remote activity object {activityId}.", {
11525
12502
  recipient,
11526
12503
  activity: json,
@@ -11556,11 +12533,50 @@ var ContextImpl = class ContextImpl {
11556
12533
  }
11557
12534
  logger.debug("Successfully fetched the remote activity object {activityId}; ignore the original activity and use the fetched one, which is trustworthy.");
11558
12535
  activity = fetched;
11559
- json = await fetched.toJsonLd({ contextLoader });
11560
- } else logger.debug("Object Integrity Proofs are verified.", {
11561
- recipient,
11562
- activity: json
11563
- });
12536
+ json = candidate.document;
12537
+ fetchedView = candidateView;
12538
+ viewSource = {
12539
+ policy: "standard",
12540
+ source: "activity",
12541
+ document: require_http.detachSignature(candidate.document),
12542
+ baseUrl: candidate.baseUrl
12543
+ };
12544
+ if (require_http.isPortableId(fetched.id)) {
12545
+ const result = verifications.get(candidate.document);
12546
+ if (result?.verified === true && result.method === "proof") documentSigners = result.keys.flatMap((key) => {
12547
+ const signer = require_http.getProofSigner(key);
12548
+ return signer == null ? [] : [{
12549
+ kind: signer.kind,
12550
+ id: signer.id,
12551
+ mechanism: "objectIntegrityProofs",
12552
+ keyId: signer.keyId
12553
+ }];
12554
+ });
12555
+ } else originAuthenticated = fetched.actorIds.map((actorId) => ({
12556
+ kind: "actor",
12557
+ id: actorId,
12558
+ mechanism: "origin",
12559
+ keyId: null
12560
+ }));
12561
+ } else {
12562
+ logger.debug("Object Integrity Proofs are verified.", {
12563
+ recipient,
12564
+ activity: json
12565
+ });
12566
+ activity = verified.object;
12567
+ documentSigners = verified.signers.map((signer) => ({
12568
+ kind: signer.kind,
12569
+ id: signer.id,
12570
+ mechanism: "objectIntegrityProofs",
12571
+ keyId: signer.keyId
12572
+ }));
12573
+ viewSource = {
12574
+ policy: "standard",
12575
+ source: "activity",
12576
+ document,
12577
+ baseUrl: null
12578
+ };
12579
+ }
11564
12580
  if (activity.actorId == null) {
11565
12581
  logger.debug("Activity is missing an actor; unable to verify.", {
11566
12582
  recipient,
@@ -11575,10 +12591,21 @@ var ContextImpl = class ContextImpl {
11575
12591
  });
11576
12592
  return false;
11577
12593
  }
12594
+ const view = fetchedView ?? await buildVerifiedActivityView(snapshot, viewSource);
11578
12595
  const routeResult = await routeActivity({
11579
12596
  context: this,
11580
12597
  json,
11581
12598
  ldSignatureVerified: false,
12599
+ authentication: createAuthenticationEvidence({
12600
+ verified: true,
12601
+ requestSigners: [],
12602
+ documentSigners,
12603
+ originAuthenticated,
12604
+ expandedActivity: deepFreeze(view.expanded),
12605
+ baseUrl: viewSource.baseUrl
12606
+ }),
12607
+ verifiedView: view.view,
12608
+ replayable: view.replayable,
11582
12609
  activity,
11583
12610
  recipient,
11584
12611
  inboxListeners: this.federation.inboxListeners,
@@ -11929,12 +12956,14 @@ var InboxContextImpl = class InboxContextImpl extends ContextImpl {
11929
12956
  activity;
11930
12957
  activityId;
11931
12958
  activityType;
11932
- constructor(recipient, activity, activityId, activityType, options) {
12959
+ authentication;
12960
+ constructor(recipient, activity, activityId, activityType, options, authentication) {
11933
12961
  super(options);
11934
12962
  this.recipient = recipient;
11935
12963
  this.activity = activity;
11936
12964
  this.activityId = activityId;
11937
12965
  this.activityType = activityType;
12966
+ this.authentication = authentication ?? getEmptyAuthenticationEvidence();
11938
12967
  }
11939
12968
  clone(data) {
11940
12969
  return new InboxContextImpl(this.recipient, this.activity, this.activityId, this.activityType, {
@@ -11944,7 +12973,7 @@ var InboxContextImpl = class InboxContextImpl extends ContextImpl {
11944
12973
  documentLoader: this.documentLoader,
11945
12974
  contextLoader: this.contextLoader,
11946
12975
  invokedFromActorKeyPairsDispatcher: this.invokedFromActorKeyPairsDispatcher
11947
- });
12976
+ }, this.authentication);
11948
12977
  }
11949
12978
  forwardActivity(forwarder, recipients, options) {
11950
12979
  return forwardActivity(this, "inbox", forwarder, recipients, options).then(() => void 0);
@@ -12237,6 +13266,11 @@ var ParallelMessageQueue = class ParallelMessageQueue {
12237
13266
  * @since 2.4.0
12238
13267
  */
12239
13268
  atomicEnqueueMany;
13269
+ /**
13270
+ * Inherits the message size limit from the wrapped queue.
13271
+ * @since 2.5.0
13272
+ */
13273
+ maxMessageSize;
12240
13274
  getDepth;
12241
13275
  /**
12242
13276
  * Tracks which ordering keys are currently being processed to ensure
@@ -12262,6 +13296,7 @@ var ParallelMessageQueue = class ParallelMessageQueue {
12262
13296
  this.workers = workers;
12263
13297
  this.nativeRetrial = queue.nativeRetrial;
12264
13298
  this.nativeDeduplication = queue.nativeDeduplication;
13299
+ this.maxMessageSize = queue.maxMessageSize;
12265
13300
  this.atomicEnqueueMany = queue.enqueueMany == null ? false : queue.atomicEnqueueMany;
12266
13301
  if (queue.getDepth != null) this.getDepth = () => queue.getDepth();
12267
13302
  }
@@ -12567,6 +13602,12 @@ Object.defineProperty(exports, "handleWebFinger", {
12567
13602
  return handleWebFinger;
12568
13603
  }
12569
13604
  });
13605
+ Object.defineProperty(exports, "isActorAuthenticated", {
13606
+ enumerable: true,
13607
+ get: function() {
13608
+ return isActorAuthenticated;
13609
+ }
13610
+ });
12570
13611
  Object.defineProperty(exports, "normalizeCircuitBreakerOptions", {
12571
13612
  enumerable: true,
12572
13613
  get: function() {