@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
@@ -2,19 +2,20 @@ import { Temporal } from "temporal-polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
3
  import { t as __exportAll } from "./rolldown-runtime-BC-Eo5Mj.js";
4
4
  import { r as getDefaultActivityTransformers } from "./transformers-DAtXl_Kc.js";
5
- import { $ as getFederationMetrics, A as detachSignature, At as isPortableId, B as wrapContextLoaderForJsonLd, Ct as getAuthenticationOrigin, Dt as getRawCanonicalPortableId, E as assertSafeJsonLd, Et as getPortableDid, F as isInvalidUrlTypeError, Ft as name, I as signJsonLd, It as version, K as importJwk, L as verifyCompactJsonLd, M as hasSignature, Mt as isPortableUri, N as hasSignatureLike, Nt as isSameObjectId, O as compactJsonLd, Ot as hasPortableActor, P as isClearlyMalformedContextReference, Q as getDurationMs, S as verifyPreparedPortableObjectProofPolicy, St as verificationObservation, T as InvalidContextReferenceError, Tt as getGatewayKeyBase, V as exportJwk, Y as MAX_PORTABLE_KEY_TTL, _ as verifyMapLocalProof, at as recordCollectionPageItems, b as verifyPortableObjectProofPolicy, bt as observeAttempt, c as verifyRequestDetailed, d as InvalidPortableObjectIdError, dt as recordOutboxActivity, et as getRemoteHost, f as classifyFep2277CoreType, ft as recordOutboxEnqueue, g as signObject, gt as formatAcceptSignature, h as preparePortableObjectProofPolicy, ht as preloadedOnlyDocumentLoader, i as selectRequestSignature, it as recordCollectionDispatchDuration, j as getNormalizationContextLoader, jt as isPortableKeyId, kt as isCompatibleKeyId, lt as recordFanoutRecipients, m as hasProofLike, mt as registerQueueDepthGauge, n as listRequestSignatures, nt as isAbortError, o as validateMaxSignatures, ot as recordCollectionRequest, pt as recordWebFingerHandle, q as validateCryptoKey, r as parseRfc9421SignatureInput, rt as recordCircuitBreakerStateChange, s as verifyRequest, st as recordCollectionTotalItems, t as doubleKnock, tt as instrumentDocumentLoader, ut as recordInboxActivity, v as verifyObject, w as normalizeOutgoingActivityJsonLd, wt as getCanonicalPortableId, x as verifyPortableObjectProofWithRoot, xt as snapshotKey, y as verifyPortableObjectProof } from "./http-DlNxxW_5.js";
5
+ import { $ as getVerifiedKeyOwnerEvidence, A as compactJsonLd, At as getRawCanonicalPortableId, B as verifyCompactJsonLdDetailed, C as verifyPortableObjectProofWithRoot, Ct as observeAttempt, D as InvalidContextReferenceError, Dt as getCanonicalPortableId, E as normalizeOutgoingActivityJsonLd, Et as getAuthenticationOrigin, F as hasSignatureLike, Ft as isPortableUri, I as isClearlyMalformedContextReference, It as isSameObjectId, L as isInvalidUrlTypeError, M as detachSignature, Mt as isCompatibleKeyId, N as getNormalizationContextLoader, Nt as isPortableId, O as assertSafeJsonLd, Ot as getGatewayKeyBase, P as hasSignature, Pt as isPortableKeyId, Q as MAX_PORTABLE_KEY_TTL, R as markEffectiveContextLoader, Rt as name, S as verifyPortableObjectProofPolicy, Tt as verificationObservation, U as wrapContextLoaderForJsonLd, W as exportJwk, X as validateCryptoKey, Y as importJwk, _ as signObject, _t as registerQueueDepthGauge, at as isAbortError, b as verifyObjectDetailed, c as verifyRequestDetailed, ct as recordCollectionPageItems, d as InvalidPortableObjectIdError, f as classifyFep2277CoreType, ft as recordFanoutRecipients, g as preparePortableObjectProofPolicy, gt as recordWebFingerHandle, h as hasProofLike, ht as recordOutboxEnqueue, i as selectRequestSignature, it as instrumentDocumentLoader, jt as hasPortableActor, kt as getPortableDid, lt as recordCollectionRequest, m as getProofSigner, mt as recordOutboxActivity, n as listRequestSignatures, nt as getFederationMetrics, o as validateMaxSignatures, ot as recordCircuitBreakerStateChange, pt as recordInboxActivity, r as parseRfc9421SignatureInput, rt as getRemoteHost, s as verifyRequest, st as recordCollectionDispatchDuration, t as doubleKnock, tt as getDurationMs, ut as recordCollectionTotalItems, v as verifyMapLocalProof, vt as preloadedOnlyDocumentLoader, w as verifyPreparedPortableObjectProofPolicy, wt as snapshotKey, x as verifyPortableObjectProof, y as verifyObject, yt as formatAcceptSignature, z as signJsonLd, zt as version } from "./http-BBr-VLil.js";
6
6
  import { a as MAX_BODY_SIZE, i as BodyTooLargeError, n as getNodeInfo, o as readBoundedText, t as nodeInfoToJson } from "./types-_S2_MeLF.js";
7
- import { n as doesActorOwnKey, r as getKeyOwner, t as verifyPortableObject } from "./portable-collection-CC2zNAje.js";
8
- import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-7015lbrx.js";
7
+ import { n as doesActorOwnKey, r as getKeyOwner, t as verifyPortableObject } from "./portable-collection-DUlQ3s2M.js";
8
+ import { n as getAuthenticatedDocumentLoader, t as kvCache } from "./kv-cache-U7uz5nx4.js";
9
9
  import { getLogger, withContext } from "@logtape/logtape";
10
10
  import { Router, RouterError, assertPath } from "@fedify/uri-template";
11
- import { Activity, Collection, CollectionPage, CryptographicKey, Link, Multikey, Object as Object$1, OrderedCollection, OrderedCollectionPage, PUBLIC_COLLECTION, Tombstone, getTypeId, isActor, lookupObject, traverseCollection } from "@fedify/vocab";
11
+ import { Activity, Collection, CollectionPage, CryptographicKey, Link, Multikey, Object as Object$1, OrderedCollection, OrderedCollectionPage, PUBLIC_COLLECTION, Tombstone, getTypeId, isActor, lookupObject, lookupObserver, traverseCollection } from "@fedify/vocab";
12
12
  import { SpanKind, SpanStatusCode, context, metrics, propagation, trace } from "@opentelemetry/api";
13
13
  import { FetchError, UrlError, canonicalizePortableUri, decodeMultibase, formatIri, fromCompatibleEf61Id, getDocumentLoader, getFe34Origin, isGatewayUrl, parseDigestMultibase, parseHashlink, parseIri, preloadedContexts, resolveDocumentLoaderTimeout, toCompatibleEf61Id, validatePublicUrl } from "@fedify/vocab-runtime";
14
14
  import { getPortableGatewayCandidates, isCompatibleEf61Iri } from "@fedify/vocab-runtime/internal/portable-dereference";
15
15
  import { encodeHex } from "byte-encodings/hex";
16
16
  import jsonld from "@fedify/vocab-runtime/jsonld";
17
17
  import { uniq } from "es-toolkit";
18
+ import serialize from "json-canon";
18
19
  import { ATTR_HTTP_REQUEST_HEADER, ATTR_HTTP_REQUEST_METHOD, ATTR_HTTP_RESPONSE_HEADER, ATTR_HTTP_RESPONSE_STATUS_CODE, ATTR_URL_FULL } from "@opentelemetry/semantic-conventions";
19
20
  import { lookupWebFinger } from "@fedify/webfinger";
20
21
  import { DataPointType, MeterProvider as MeterProvider$1, MetricReader } from "@opentelemetry/sdk-metrics";
@@ -32,9 +33,12 @@ var ActivityListenerSet = class {
32
33
  clone.#listeners = new Map(this.#listeners);
33
34
  return clone;
34
35
  }
35
- add(type, listener) {
36
+ add(type, listener, options = {}) {
36
37
  if (this.#listeners.has(type)) throw new TypeError("Listener already set for this type.");
37
- this.#listeners.set(type, listener);
38
+ this.#listeners.set(type, {
39
+ listener,
40
+ options: { ...options }
41
+ });
38
42
  }
39
43
  dispatchWithClass(activity) {
40
44
  let cls = activity.constructor;
@@ -44,10 +48,11 @@ var ActivityListenerSet = class {
44
48
  cls = globalThis.Object.getPrototypeOf(cls);
45
49
  }
46
50
  if (cls == null) return null;
47
- const listener = this.#listeners.get(cls);
51
+ const { listener, options } = this.#listeners.get(cls);
48
52
  return {
49
53
  class: cls,
50
- listener
54
+ listener,
55
+ options
51
56
  };
52
57
  }
53
58
  dispatch(activity) {
@@ -635,8 +640,8 @@ var FederationBuilderImpl = class {
635
640
  }
636
641
  const listeners = this.inboxListeners = new ActivityListenerSet();
637
642
  const setters = {
638
- on(type, listener) {
639
- listeners.add(type, listener);
643
+ on(type, listener, options) {
644
+ listeners.add(type, listener, options);
640
645
  return setters;
641
646
  },
642
647
  onError: (handler) => {
@@ -2804,6 +2809,172 @@ async function isInAudience(object, actor, options = {}) {
2804
2809
  return false;
2805
2810
  }
2806
2811
  //#endregion
2812
+ //#region src/federation/inbox-authentication.ts
2813
+ /**
2814
+ * The evidence of an activity that has not been authenticated by Fedify.
2815
+ */
2816
+ const EMPTY_EVIDENCE = Object.freeze({
2817
+ verified: false,
2818
+ requestSigners: Object.freeze([]),
2819
+ documentSigners: Object.freeze([]),
2820
+ originAuthenticated: Object.freeze([]),
2821
+ expandedActivity: Object.freeze([]),
2822
+ baseUrl: null
2823
+ });
2824
+ /**
2825
+ * Gets the evidence of an activity that has not been authenticated.
2826
+ * @internal
2827
+ */
2828
+ function getEmptyAuthenticationEvidence() {
2829
+ return EMPTY_EVIDENCE;
2830
+ }
2831
+ /**
2832
+ * Gets the principals that an authorization decision should rely on.
2833
+ * @param evidence The evidence.
2834
+ * @param scope See {@link IsActorAuthenticatedOptions.scope}.
2835
+ * @returns The principals.
2836
+ * @internal
2837
+ */
2838
+ function getEffectivePrincipals(evidence, scope = "effective") {
2839
+ if (scope === "document" || evidence.documentSigners.length > 0) return evidence.documentSigners;
2840
+ if (evidence.requestSigners.length > 0) return evidence.requestSigners;
2841
+ return evidence.originAuthenticated;
2842
+ }
2843
+ /**
2844
+ * Normalizes an actor ID for comparison: portable IDs and compatible
2845
+ * identifiers to their canonical portable ID, other IDs to their `href`.
2846
+ * @returns The normalized ID, or `null` for a malformed portable ID.
2847
+ */
2848
+ function normalizeActorId(id) {
2849
+ return isPortableId(id) ? getCanonicalPortableId(id) : id.href;
2850
+ }
2851
+ /**
2852
+ * Checks whether the given principal authenticates the given actor.
2853
+ * @param principal The principal.
2854
+ * @param actorId The actor ID.
2855
+ * @returns `true` if the principal authenticates the actor.
2856
+ * @internal
2857
+ */
2858
+ function principalAuthenticates(principal, actorId) {
2859
+ if (principal.kind === "portableAuthority") {
2860
+ if (!isPortableId(actorId)) return false;
2861
+ const did = getPortableDid(actorId);
2862
+ return did != null && did === getPortableDid(principal.id);
2863
+ }
2864
+ const expected = normalizeActorId(actorId);
2865
+ return expected != null && normalizeActorId(principal.id) === expected;
2866
+ }
2867
+ /**
2868
+ * Checks whether the given evidence authenticates the given actor.
2869
+ *
2870
+ * Actor IDs are compared exactly, except that [FEP-ef61] portable IDs and
2871
+ * compatible identifiers are compared by their canonical portable IDs, and
2872
+ * a portable authority (a DID) authenticates the portable actors of that DID.
2873
+ *
2874
+ * @example
2875
+ * ``` typescript
2876
+ * federation
2877
+ * .setInboxListeners("/users/{identifier}/inbox", "/inbox")
2878
+ * .on(Follow, async (ctx, follow) => {
2879
+ * if (follow.actorId == null) return;
2880
+ * // The actor is always authenticated for activities that declare one,
2881
+ * // but the same check works for any actor an activity names:
2882
+ * isActorAuthenticated(ctx.authentication, follow.actorId);
2883
+ * });
2884
+ * ```
2885
+ *
2886
+ * [FEP-ef61]: https://w3id.org/fep/ef61
2887
+ * @param evidence The authentication evidence, usually
2888
+ * {@link InboxContext.authentication}.
2889
+ * @param actorId The actor ID to check.
2890
+ * @param options Options.
2891
+ * @returns `true` if the actor is authenticated.
2892
+ * @since 2.5.0
2893
+ */
2894
+ function isActorAuthenticated(evidence, actorId, options = {}) {
2895
+ return getEffectivePrincipals(evidence, options.scope).some((principal) => principalAuthenticates(principal, actorId));
2896
+ }
2897
+ function serializePrincipal(principal) {
2898
+ return {
2899
+ kind: principal.kind,
2900
+ id: principal.id.href,
2901
+ mechanism: principal.mechanism,
2902
+ keyId: principal.keyId?.href ?? null
2903
+ };
2904
+ }
2905
+ /**
2906
+ * Serializes the principals of the given evidence for a queued message.
2907
+ * @internal
2908
+ */
2909
+ function serializeAuthentication(evidence) {
2910
+ return {
2911
+ verified: evidence.verified,
2912
+ requestSigners: evidence.requestSigners.map(serializePrincipal),
2913
+ documentSigners: evidence.documentSigners.map(serializePrincipal),
2914
+ originAuthenticated: evidence.originAuthenticated.map(serializePrincipal)
2915
+ };
2916
+ }
2917
+ const KINDS = /* @__PURE__ */ new Set(["actor", "portableAuthority"]);
2918
+ const MECHANISMS = /* @__PURE__ */ new Set([
2919
+ "httpSignatures",
2920
+ "linkedDataSignatures",
2921
+ "objectIntegrityProofs",
2922
+ "origin"
2923
+ ]);
2924
+ function deserializePrincipals(value) {
2925
+ if (!Array.isArray(value)) return null;
2926
+ const principals = [];
2927
+ for (const item of value) {
2928
+ 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;
2929
+ principals.push(Object.freeze({
2930
+ kind: item.kind,
2931
+ id: new URL(item.id),
2932
+ mechanism: item.mechanism,
2933
+ keyId: item.keyId == null ? null : new URL(item.keyId)
2934
+ }));
2935
+ }
2936
+ return principals;
2937
+ }
2938
+ /**
2939
+ * Restores the evidence of a queued message.
2940
+ * @param value The serialized principals.
2941
+ * @param expandedActivity The expanded activity, restored from the queued
2942
+ * verified view.
2943
+ * @param baseUrl The base URL, restored from the queued verified view.
2944
+ * @returns The evidence, or `null` if the serialized value is malformed.
2945
+ * @internal
2946
+ */
2947
+ function deserializeAuthentication(value, expandedActivity, baseUrl) {
2948
+ if (value == null || typeof value !== "object" || typeof value.verified !== "boolean") return null;
2949
+ const record = value;
2950
+ const requestSigners = deserializePrincipals(record.requestSigners);
2951
+ const documentSigners = deserializePrincipals(record.documentSigners);
2952
+ const originAuthenticated = deserializePrincipals(record.originAuthenticated);
2953
+ if (requestSigners == null || documentSigners == null || originAuthenticated == null) return null;
2954
+ return createAuthenticationEvidence({
2955
+ verified: record.verified,
2956
+ requestSigners,
2957
+ documentSigners,
2958
+ originAuthenticated,
2959
+ expandedActivity,
2960
+ baseUrl
2961
+ });
2962
+ }
2963
+ /**
2964
+ * Creates frozen evidence.
2965
+ * @internal
2966
+ */
2967
+ function createAuthenticationEvidence(evidence) {
2968
+ return Object.freeze({
2969
+ verified: evidence.verified,
2970
+ requestSigners: Object.freeze([...evidence.requestSigners]),
2971
+ documentSigners: Object.freeze([...evidence.documentSigners]),
2972
+ originAuthenticated: Object.freeze([...evidence.originAuthenticated]),
2973
+ expandedActivity: evidence.expandedActivity,
2974
+ baseUrl: evidence.baseUrl
2975
+ });
2976
+ }
2977
+ //#endregion
2807
2978
  //#region src/federation/inbox.ts
2808
2979
  /**
2809
2980
  * Checks whether an activity's ID and all of its actor IDs have the same
@@ -2825,13 +2996,135 @@ function actorsHaveSameOrigin(activity) {
2825
2996
  const origin = getAuthenticationOrigin(activity.id);
2826
2997
  return origin != null && activity.actorIds.every((actor) => getAuthenticationOrigin(actor) === origin);
2827
2998
  }
2828
- async function routeActivity({ context: ctx, json, originalJson, normalizedActivity, ldSignatureVerified, activity, recipient, inboxListeners, inboxContextFactory, listenerInboxContextFactory, inboxErrorHandler, kv, kvPrefixes, queue, span, meterProvider, tracerProvider, idempotencyStrategy }) {
2999
+ /**
3000
+ * The headroom that the size check of a queued inbox message leaves for the
3001
+ * fields that grow when Fedify retries the message, e.g., its attempt count.
3002
+ */
3003
+ const QUEUED_MESSAGE_HEADROOM = 64;
3004
+ /**
3005
+ * Computes the idempotency key of an incoming activity.
3006
+ * @returns The key, or `null` if the activity is not deduplicated.
3007
+ * @internal
3008
+ */
3009
+ async function getInboxIdempotencyKey(strategy, inboxContext, activity, origin, recipient, prefix) {
3010
+ if (activity.id == null) return null;
3011
+ strategy ??= "per-inbox";
3012
+ let keyString;
3013
+ if (typeof strategy === "function") keyString = await strategy(inboxContext(), activity);
3014
+ else switch (strategy) {
3015
+ case "global":
3016
+ keyString = activity.id.href;
3017
+ break;
3018
+ case "per-origin":
3019
+ keyString = `${origin}\n${activity.id.href}`;
3020
+ break;
3021
+ case "per-inbox":
3022
+ keyString = `${origin}\n${activity.id.href}\n${recipient == null ? "sharedInbox" : `inbox\n${recipient}`}`;
3023
+ break;
3024
+ default: keyString = `${origin}\n${activity.id.href}`;
3025
+ }
3026
+ return keyString == null ? null : [...prefix, keyString];
3027
+ }
3028
+ /**
3029
+ * Checks whether an incoming activity has already been processed, by its
3030
+ * idempotency key, or by the key that queue workers of older versions of
3031
+ * Fedify recorded.
3032
+ * @internal
3033
+ */
3034
+ async function isInboxActivityProcessed(kv, cacheKey, activity, origin, prefix) {
3035
+ if (cacheKey == null) return false;
3036
+ if (await kv.get(cacheKey) === true) return true;
3037
+ if (activity.id == null) return false;
3038
+ return await kv.get([
3039
+ ...prefix,
3040
+ origin,
3041
+ activity.id.href
3042
+ ]) === true;
3043
+ }
3044
+ /**
3045
+ * The bytes added to the UTF-8 JSON length of a message for each of its JSON
3046
+ * values, to bound the size of serialization formats other than JSON, such
3047
+ * as the structured serialization of Deno KV, which spends more bytes than
3048
+ * JSON on type tags and lengths of small values, e.g., empty arrays, and up
3049
+ * to nine bytes on a fractional number that JSON writes in three.
3050
+ */
3051
+ const PER_VALUE_SERIALIZATION_OVERHEAD = 8;
3052
+ /**
3053
+ * Estimates, conservatively, how large a queued message is once a queue
3054
+ * backend serializes it: the UTF-8 length of its JSON serialization, plus
3055
+ * a fixed overhead for each JSON value in it, plus the bytes that encoding
3056
+ * strings beyond Latin-1 as UTF-16 adds.
3057
+ * @internal
3058
+ */
3059
+ function estimateQueuedMessageSize(message) {
3060
+ const encoder = new TextEncoder();
3061
+ let values = 0;
3062
+ let stringExpansion = 0;
3063
+ const countString = (value) => {
3064
+ values++;
3065
+ if (!/[\u0100-\uffff]/.test(value)) return;
3066
+ const expansion = value.length * 2 - encoder.encode(value).byteLength;
3067
+ if (expansion > 0) stringExpansion += expansion;
3068
+ };
3069
+ const count = (value) => {
3070
+ if (typeof value === "string") {
3071
+ countString(value);
3072
+ return;
3073
+ }
3074
+ values++;
3075
+ if (Array.isArray(value)) for (const item of value) count(item);
3076
+ else if (value != null && typeof value === "object") for (const [key, item] of Object.entries(value)) {
3077
+ if (item === void 0) continue;
3078
+ countString(key);
3079
+ count(item);
3080
+ }
3081
+ };
3082
+ count(message);
3083
+ return encoder.encode(JSON.stringify(message)).byteLength + stringExpansion + values * PER_VALUE_SERIALIZATION_OVERHEAD;
3084
+ }
3085
+ /**
3086
+ * Checks whether an activity that declares no actor is authenticated: at
3087
+ * least one principal must be authenticated for it, and its ID, if any, must
3088
+ * have the same [FEP-fe34] origin as one of those principals.
3089
+ *
3090
+ * [FEP-fe34]: https://w3id.org/fep/fe34
3091
+ * @returns `"ok"` if it is authenticated, or why it is not.
3092
+ * @internal
3093
+ */
3094
+ function checkActorlessActivity(activity, authentication) {
3095
+ const principals = getEffectivePrincipals(authentication);
3096
+ if (principals.length < 1) return {
3097
+ status: "noAuthenticatedPrincipal",
3098
+ principals
3099
+ };
3100
+ if (activity.id == null) return {
3101
+ status: "ok",
3102
+ principals
3103
+ };
3104
+ const origin = getAuthenticationOrigin(activity.id);
3105
+ if (origin == null || !principals.some((principal) => getAuthenticationOrigin(principal.id) === origin)) return {
3106
+ status: "activityOriginMismatch",
3107
+ principals
3108
+ };
3109
+ return {
3110
+ status: "ok",
3111
+ principals
3112
+ };
3113
+ }
3114
+ /**
3115
+ * Same as {@link checkActorlessActivity}, but returns a boolean.
3116
+ * @internal
3117
+ */
3118
+ function isActorlessActivityAuthenticated(activity, authentication) {
3119
+ return checkActorlessActivity(activity, authentication).status === "ok";
3120
+ }
3121
+ 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 }) {
2829
3122
  const logger = getLogger([
2830
3123
  "fedify",
2831
3124
  "federation",
2832
3125
  "inbox"
2833
3126
  ]);
2834
- if (activity.actorId == null) {
3127
+ if (activity.actorId == null && !actorless) {
2835
3128
  logger.error("Missing actor.", { activity: json });
2836
3129
  span.setStatus({
2837
3130
  code: SpanStatusCode.ERROR,
@@ -2840,58 +3133,59 @@ async function routeActivity({ context: ctx, json, originalJson, normalizedActiv
2840
3133
  recordInboxActivity(meterProvider, "rejected", getTypeId(activity).href);
2841
3134
  return "missingActor";
2842
3135
  }
2843
- let cacheKey = null;
2844
- if (activity.id != null) {
2845
- const inboxContext = inboxContextFactory(recipient, json, activity.id?.href, getTypeId(activity).href);
2846
- const strategy = idempotencyStrategy ?? "per-inbox";
2847
- let keyString;
2848
- if (typeof strategy === "function") keyString = await strategy(inboxContext, activity);
2849
- else switch (strategy) {
2850
- case "global":
2851
- keyString = activity.id.href;
2852
- break;
2853
- case "per-origin":
2854
- keyString = `${ctx.origin}\n${activity.id.href}`;
2855
- break;
2856
- case "per-inbox":
2857
- keyString = `${ctx.origin}\n${activity.id.href}\n${recipient == null ? "sharedInbox" : `inbox\n${recipient}`}`;
2858
- break;
2859
- default: keyString = `${ctx.origin}\n${activity.id.href}`;
2860
- }
2861
- if (keyString != null) cacheKey = [...kvPrefixes.activityIdempotence, keyString];
3136
+ const contextMetadata = { authentication };
3137
+ const cacheKey = await getInboxIdempotencyKey(idempotencyStrategy, () => inboxContextFactory(recipient, json, activity.id?.href, getTypeId(activity).href, contextMetadata), activity, ctx.origin, recipient, kvPrefixes.activityIdempotence);
3138
+ if (await isInboxActivityProcessed(kv, cacheKey, activity, ctx.origin, kvPrefixes.activityIdempotence)) {
3139
+ logger.debug("Activity {activityId} has already been processed.", {
3140
+ activityId: activity.id?.href,
3141
+ activity: json,
3142
+ recipient
3143
+ });
3144
+ span.setStatus({
3145
+ code: SpanStatusCode.UNSET,
3146
+ message: `Activity ${activity.id?.href} has already been processed.`
3147
+ });
3148
+ recordInboxActivity(meterProvider, "rejected", getTypeId(activity).href);
3149
+ return "alreadyProcessed";
2862
3150
  }
2863
- if (cacheKey != null) {
2864
- if (await kv.get(cacheKey) === true) {
2865
- logger.debug("Activity {activityId} has already been processed.", {
3151
+ if (activity.actorId != null) span.setAttribute("activitypub.actor.id", activity.actorId.href);
3152
+ let message;
3153
+ if (queue != null) {
3154
+ const carrier = {};
3155
+ propagation.inject(context.active(), carrier);
3156
+ message = {
3157
+ type: "inbox",
3158
+ id: crypto.randomUUID(),
3159
+ baseUrl: ctx.origin,
3160
+ activity: originalJson ?? json,
3161
+ ...normalizedActivity == null ? {} : { normalizedActivity },
3162
+ ...ldSignatureVerified == null ? {} : { ldSignatureVerified },
3163
+ ...verifiedView == null ? {} : { verifiedView },
3164
+ ...authentication == null ? {} : { authentication: serializeAuthentication(authentication) },
3165
+ identifier: recipient,
3166
+ attempt: 0,
3167
+ started: (/* @__PURE__ */ new Date()).toISOString(),
3168
+ traceContext: carrier
3169
+ };
3170
+ const maxMessageSize = queue.maxMessageSize;
3171
+ if (replayable === false) {
3172
+ logger.warn("The incoming activity {activityId} uses a JSON-LD context that cannot be kept for the queue; processing it immediately instead.", {
2866
3173
  activityId: activity.id?.href,
2867
- activity: json,
2868
3174
  recipient
2869
3175
  });
2870
- span.setStatus({
2871
- code: SpanStatusCode.UNSET,
2872
- message: `Activity ${activity.id?.href} has already been processed.`
3176
+ message = void 0;
3177
+ } else if (maxMessageSize != null && estimateQueuedMessageSize(message) > maxMessageSize - QUEUED_MESSAGE_HEADROOM) {
3178
+ logger.warn("The incoming activity {activityId} is too large to enqueue; processing it immediately instead.", {
3179
+ activityId: activity.id?.href,
3180
+ recipient,
3181
+ maxMessageSize
2873
3182
  });
2874
- recordInboxActivity(meterProvider, "rejected", getTypeId(activity).href);
2875
- return "alreadyProcessed";
3183
+ message = void 0;
2876
3184
  }
2877
3185
  }
2878
- span.setAttribute("activitypub.actor.id", activity.actorId.href);
2879
- if (queue != null) {
2880
- const carrier = {};
2881
- propagation.inject(context.active(), carrier);
3186
+ if (queue != null && message != null) {
2882
3187
  try {
2883
- await queue.enqueue({
2884
- type: "inbox",
2885
- id: crypto.randomUUID(),
2886
- baseUrl: ctx.origin,
2887
- activity: originalJson ?? json,
2888
- ...normalizedActivity == null ? {} : { normalizedActivity },
2889
- ...ldSignatureVerified == null ? {} : { ldSignatureVerified },
2890
- identifier: recipient,
2891
- attempt: 0,
2892
- started: (/* @__PURE__ */ new Date()).toISOString(),
2893
- traceContext: carrier
2894
- });
3188
+ await queue.enqueue(message);
2895
3189
  } catch (error) {
2896
3190
  logger.error("Failed to enqueue the incoming activity {activityId}:\n{error}", {
2897
3191
  error,
@@ -2941,7 +3235,7 @@ async function routeActivity({ context: ctx, json, originalJson, normalizedActiv
2941
3235
  const started = performance.now();
2942
3236
  try {
2943
3237
  const contextFactory = listenerInboxContextFactory ?? inboxContextFactory;
2944
- await listener(contextFactory(recipient, contextFactory === inboxContextFactory ? json : originalJson ?? json, activity.id?.href, activityType), activity);
3238
+ await listener(contextFactory(recipient, contextFactory === inboxContextFactory ? json : originalJson ?? json, activity.id?.href, activityType, contextMetadata), activity);
2945
3239
  } finally {
2946
3240
  getFederationMetrics(meterProvider).recordInboxProcessingDuration(activityType, getDurationMs(started));
2947
3241
  }
@@ -2982,6 +3276,382 @@ async function routeActivity({ context: ctx, json, originalJson, normalizedActiv
2982
3276
  });
2983
3277
  }
2984
3278
  //#endregion
3279
+ //#region src/federation/inbox-view.ts
3280
+ /**
3281
+ * The interpretation of an incoming activity that Fedify authenticated, kept
3282
+ * so that listeners, authorization helpers, and queue workers observe the
3283
+ * same JSON-LD meaning that signature verification observed.
3284
+ *
3285
+ * Every JSON-LD processing step of one delivery goes through a request-scoped
3286
+ * *context snapshot*, which memoizes the remote context responses so that
3287
+ * a nondeterministic or mutable remote context cannot give verification and
3288
+ * dispatch different meanings. The snapshot is serialized into queued
3289
+ * messages, so that workers and retries replay the very same responses
3290
+ * without loading contexts again.
3291
+ * @module
3292
+ */
3293
+ /**
3294
+ * Thrown when replaying an interpretation needs a context response that its
3295
+ * snapshot does not have, or has with a different content.
3296
+ * @internal
3297
+ */
3298
+ var ContextSnapshotMissError = class extends Error {
3299
+ url;
3300
+ constructor(url, reason) {
3301
+ super(`Context ${url} cannot be replayed: ${reason}.`);
3302
+ this.name = "ContextSnapshotMissError";
3303
+ this.url = url;
3304
+ }
3305
+ };
3306
+ function normalizeUrl(url) {
3307
+ return URL.canParse(url) ? new URL(url).href : url;
3308
+ }
3309
+ /**
3310
+ * Creates the context snapshot of a delivery.
3311
+ * @param base The caller's context loader.
3312
+ * @returns The snapshot.
3313
+ * @internal
3314
+ */
3315
+ function createContextSnapshot(base) {
3316
+ const underlying = {
3317
+ standard: wrapContextLoaderForJsonLd(base),
3318
+ normalization: getNormalizationContextLoader(base)
3319
+ };
3320
+ const cache = /* @__PURE__ */ new Map();
3321
+ const recorded = /* @__PURE__ */ new Map();
3322
+ const loaders = /* @__PURE__ */ new Map();
3323
+ let released = false;
3324
+ return {
3325
+ loader(policy) {
3326
+ let loader = loaders.get(policy);
3327
+ if (loader != null) return loader;
3328
+ const effective = underlying[policy];
3329
+ loader = markEffectiveContextLoader(async (url, options) => {
3330
+ if (released) return await effective(url, options);
3331
+ const normalizedUrl = normalizeUrl(url);
3332
+ const key = `${policy}\n${normalizedUrl}`;
3333
+ let promise = cache.get(key);
3334
+ if (promise == null) {
3335
+ promise = Promise.resolve(effective(url, options)).then((response) => {
3336
+ const cloned = normalizeResponse(structuredClone(response));
3337
+ recorded.set(key, {
3338
+ policy,
3339
+ url: normalizedUrl,
3340
+ response: structuredClone(cloned)
3341
+ });
3342
+ return cloned;
3343
+ });
3344
+ promise.catch(() => {});
3345
+ cache.set(key, promise);
3346
+ }
3347
+ return structuredClone(await promise);
3348
+ }, policy);
3349
+ loaders.set(policy, loader);
3350
+ return loader;
3351
+ },
3352
+ entries() {
3353
+ return [...recorded.values()];
3354
+ },
3355
+ release() {
3356
+ released = true;
3357
+ }
3358
+ };
3359
+ }
3360
+ function getBundledResponse(url) {
3361
+ if (!(url in preloadedContexts)) return null;
3362
+ return {
3363
+ contextUrl: null,
3364
+ document: preloadedContexts[url],
3365
+ documentUrl: url
3366
+ };
3367
+ }
3368
+ async function sha256(value) {
3369
+ return encodeHex(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(serialize(value))));
3370
+ }
3371
+ function normalizeResponse(response) {
3372
+ return {
3373
+ contextUrl: response.contextUrl ?? null,
3374
+ document: response.document,
3375
+ documentUrl: response.documentUrl
3376
+ };
3377
+ }
3378
+ /**
3379
+ * Serializes the entries of a context snapshot for a queued message.
3380
+ * @param entries The entries.
3381
+ * @returns The serialized entries, which are plain JSON values.
3382
+ * @internal
3383
+ */
3384
+ async function serializeContextSnapshot(entries) {
3385
+ const result = [];
3386
+ for (const entry of entries) {
3387
+ const response = normalizeResponse(entry.response);
3388
+ const hash = await sha256(response);
3389
+ const bundled = getBundledResponse(entry.url);
3390
+ if (bundled != null && await sha256(bundled) === hash) result.push({
3391
+ policy: entry.policy,
3392
+ url: entry.url,
3393
+ sha256: hash,
3394
+ bundled: true
3395
+ });
3396
+ else result.push({
3397
+ policy: entry.policy,
3398
+ url: entry.url,
3399
+ sha256: hash,
3400
+ response: structuredClone(response)
3401
+ });
3402
+ }
3403
+ return result;
3404
+ }
3405
+ function isRemoteDocument(value) {
3406
+ if (value == null || typeof value !== "object") return false;
3407
+ const record = value;
3408
+ return typeof record.documentUrl === "string" && "document" in record && (record.contextUrl == null || typeof record.contextUrl === "string");
3409
+ }
3410
+ /**
3411
+ * Restores the entries of a serialized context snapshot, verifying the
3412
+ * hashes of stored responses. Bundled entries are reconstructed from the
3413
+ * contexts this version of Fedify bundles.
3414
+ * @param entries The serialized entries.
3415
+ * @returns The entries.
3416
+ * @throws {ContextSnapshotMissError} If an entry is malformed, or its
3417
+ * content does not match its hash.
3418
+ * @internal
3419
+ */
3420
+ async function deserializeContextSnapshot(entries) {
3421
+ if (!Array.isArray(entries)) throw new ContextSnapshotMissError("(snapshot)", "malformed snapshot");
3422
+ const result = [];
3423
+ for (const entry of entries) {
3424
+ 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");
3425
+ let response;
3426
+ if (entry.bundled === true) {
3427
+ response = getBundledResponse(entry.url);
3428
+ if (response == null) throw new ContextSnapshotMissError(entry.url, "not bundled");
3429
+ } else if (isRemoteDocument(entry.response)) response = normalizeResponse(entry.response);
3430
+ else throw new ContextSnapshotMissError(entry.url, "malformed entry");
3431
+ if (entry.bundled !== true && await sha256(response) !== entry.sha256) throw new ContextSnapshotMissError(entry.url, "content mismatch");
3432
+ result.push({
3433
+ policy: entry.policy,
3434
+ url: entry.url,
3435
+ response: structuredClone(response)
3436
+ });
3437
+ }
3438
+ return result;
3439
+ }
3440
+ /**
3441
+ * Creates a context loader of a policy that serves only the responses of
3442
+ * a snapshot. It is marked as the effective loader of the policy.
3443
+ * @param entries The snapshot's entries.
3444
+ * @param policy The policy.
3445
+ * @returns The loader, which throws {@link ContextSnapshotMissError} for
3446
+ * anything else.
3447
+ * @internal
3448
+ */
3449
+ function createSnapshotOnlyLoader(entries, policy) {
3450
+ const responses = /* @__PURE__ */ new Map();
3451
+ for (const entry of entries) if (entry.policy === policy) responses.set(entry.url, entry.response);
3452
+ return markEffectiveContextLoader(async (url) => {
3453
+ const response = responses.get(normalizeUrl(url));
3454
+ if (response == null) throw new ContextSnapshotMissError(url, "not in the snapshot");
3455
+ return structuredClone(response);
3456
+ }, policy);
3457
+ }
3458
+ /**
3459
+ * Creates a {@link ReleasableLoader}.
3460
+ * @param strict The loader to use until release.
3461
+ * @param fallback The loader to use after release.
3462
+ * @param policy The policy both loaders apply.
3463
+ * @returns The releasable loader.
3464
+ * @internal
3465
+ */
3466
+ function createReleasableLoader(strict, fallback, policy) {
3467
+ let released = false;
3468
+ return {
3469
+ loader: markEffectiveContextLoader((url, options) => released ? fallback(url, options) : strict(url, options), policy),
3470
+ release() {
3471
+ released = true;
3472
+ }
3473
+ };
3474
+ }
3475
+ /**
3476
+ * Expands a JSON-LD document with the given context loader.
3477
+ * @param document The document.
3478
+ * @param contextLoader The context loader.
3479
+ * @returns The expanded document.
3480
+ * @internal
3481
+ */
3482
+ async function expandDocument(document, contextLoader) {
3483
+ return await jsonld.expand(document, { documentLoader: contextLoader });
3484
+ }
3485
+ /**
3486
+ * Computes the digest of an expanded document.
3487
+ * @param expanded The expanded document.
3488
+ * @returns The hex-encoded SHA-256 of its canonical JSON.
3489
+ * @internal
3490
+ */
3491
+ function digestExpandedDocument(expanded) {
3492
+ return sha256(expanded);
3493
+ }
3494
+ /**
3495
+ * Deeply freezes a JSON value.
3496
+ * @param value The value.
3497
+ * @returns The same value, frozen.
3498
+ * @internal
3499
+ */
3500
+ function deepFreeze(value) {
3501
+ if (value != null && typeof value === "object" && !Object.isFrozen(value)) {
3502
+ Object.freeze(value);
3503
+ for (const child of Object.values(value)) deepFreeze(child);
3504
+ }
3505
+ return value;
3506
+ }
3507
+ const ACTOR_IRI = "https://www.w3.org/ns/activitystreams#actor";
3508
+ /**
3509
+ * Gets the single root node of an expanded document.
3510
+ * @param expanded The expanded document.
3511
+ * @returns The root node, or `null` if there is not exactly one.
3512
+ * @internal
3513
+ */
3514
+ function getRootNode(expanded) {
3515
+ if (expanded.length !== 1) return null;
3516
+ const node = expanded[0];
3517
+ if (node == null || typeof node !== "object" || Array.isArray(node)) return null;
3518
+ return node;
3519
+ }
3520
+ /**
3521
+ * Gets the values of a property of an expanded node, unwrapping a single
3522
+ * `@list` container the way generated vocabulary decoders do.
3523
+ * @param node The expanded node.
3524
+ * @param iri The property IRI.
3525
+ * @returns The values, or an empty array if the property is absent.
3526
+ * @internal
3527
+ */
3528
+ function getPropertyValues(node, iri) {
3529
+ const values = node[iri];
3530
+ if (values == null) return [];
3531
+ if (!Array.isArray(values)) return [values];
3532
+ if (values.length === 1 && values[0] != null && typeof values[0] === "object" && "@list" in values[0]) {
3533
+ const list = values[0]["@list"];
3534
+ return Array.isArray(list) ? list : [list];
3535
+ }
3536
+ return values;
3537
+ }
3538
+ /**
3539
+ * Tells whether the root node of an expanded activity declares any `actor`
3540
+ * value at all, including blank nodes, anonymous or untyped nodes, and
3541
+ * literals, which ID accessors and typed parsing may drop.
3542
+ * @param expanded The expanded activity.
3543
+ * @returns `"absent"` if the root node declares no actor; `"present"`
3544
+ * otherwise, including when there is no single root node.
3545
+ * @internal
3546
+ */
3547
+ function getActorPresence(expanded) {
3548
+ const root = getRootNode(expanded);
3549
+ if (root == null) return "present";
3550
+ return getPropertyValues(root, ACTOR_IRI).length < 1 ? "absent" : "present";
3551
+ }
3552
+ /**
3553
+ * Builds the serialized view of an authenticated activity. The document is
3554
+ * expanded through the snapshot, so that the snapshot holds every context
3555
+ * response the interpretation needs.
3556
+ * @param snapshot The delivery's context snapshot.
3557
+ * @param source Which document the activity was parsed from.
3558
+ * @returns The serialized view and the expanded activity.
3559
+ * @internal
3560
+ */
3561
+ async function buildVerifiedActivityView(snapshot, source) {
3562
+ const loader = snapshot.loader(source.policy);
3563
+ const used = /* @__PURE__ */ new Set();
3564
+ const expanded = await expandDocument(source.document, markEffectiveContextLoader((url, options) => {
3565
+ used.add(normalizeUrl(url));
3566
+ return loader(url, options);
3567
+ }, source.policy));
3568
+ const entries = snapshot.entries().filter((entry) => entry.policy === source.policy && used.has(entry.url));
3569
+ const contexts = await serializeContextSnapshot(entries);
3570
+ const references = /* @__PURE__ */ new Set();
3571
+ collectContextReferences(source.document, source.baseUrl?.href, references);
3572
+ for (const entry of entries) {
3573
+ const { contextUrl, document } = entry.response;
3574
+ collectContextReferences(document, entry.url, references);
3575
+ if (contextUrl != null) collectContextReferences({ "@context": contextUrl }, entry.url, references);
3576
+ }
3577
+ return {
3578
+ replayable: [...references].every((url) => used.has(url)),
3579
+ view: {
3580
+ version: 1,
3581
+ policy: source.policy,
3582
+ source: source.source,
3583
+ baseUrl: source.baseUrl?.href ?? null,
3584
+ contexts,
3585
+ expandedDigest: await digestExpandedDocument(expanded)
3586
+ },
3587
+ expanded
3588
+ };
3589
+ }
3590
+ /**
3591
+ * Collects the remote context references of a JSON-LD document or context
3592
+ * document: every IRI given as a value of `@context` at any depth, including
3593
+ * scoped contexts of term definitions, and of `@import`.
3594
+ * @param value The document.
3595
+ * @param base The URL to resolve relative references against.
3596
+ * @param references The set to add the normalized references to.
3597
+ */
3598
+ function collectContextReferences(value, base, references) {
3599
+ const add = (reference) => {
3600
+ let url = reference;
3601
+ if (base != null && URL.canParse(reference, base)) url = new URL(reference, base).href;
3602
+ references.add(normalizeUrl(url));
3603
+ };
3604
+ const collectContext = (context) => {
3605
+ if (typeof context === "string") add(context);
3606
+ else if (Array.isArray(context)) for (const item of context) collectContext(item);
3607
+ else if (context != null && typeof context === "object") {
3608
+ const imported = context["@import"];
3609
+ if (typeof imported === "string") add(imported);
3610
+ walk(context);
3611
+ }
3612
+ };
3613
+ const walk = (node) => {
3614
+ if (Array.isArray(node)) for (const item of node) walk(item);
3615
+ else if (node != null && typeof node === "object") for (const [key, item] of Object.entries(node)) if (key === "@context") collectContext(item);
3616
+ else walk(item);
3617
+ };
3618
+ walk(value);
3619
+ }
3620
+ /**
3621
+ * Validates the shape of a serialized view taken from a queued message.
3622
+ * @param value The value of the message's `verifiedView` field.
3623
+ * @returns The view, or `null` if it is malformed or of an unsupported
3624
+ * version.
3625
+ * @internal
3626
+ */
3627
+ function parseSerializedVerifiedActivityView(value) {
3628
+ if (value == null || typeof value !== "object") return null;
3629
+ const view = value;
3630
+ 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;
3631
+ return view;
3632
+ }
3633
+ /**
3634
+ * Restores a view from a queued message without loading any context.
3635
+ * @param view The serialized view.
3636
+ * @param message The fields of the message the view refers to.
3637
+ * @returns The restored view.
3638
+ * @throws {ContextSnapshotMissError} If the view cannot be replayed exactly.
3639
+ * @internal
3640
+ */
3641
+ async function restoreVerifiedActivityView(view, message) {
3642
+ const loader = createSnapshotOnlyLoader(await deserializeContextSnapshot(view.contexts), view.policy);
3643
+ if (view.source === "normalizedActivity" && message.normalizedActivity == null) throw new ContextSnapshotMissError("(normalizedActivity)", "the normalized activity is missing");
3644
+ const document = detachSignature(view.source === "activity" ? message.activity : message.normalizedActivity);
3645
+ const expanded = await expandDocument(document, loader);
3646
+ if (await digestExpandedDocument(expanded) !== view.expandedDigest) throw new ContextSnapshotMissError("(activity)", "the interpretation does not match the authenticated one");
3647
+ return {
3648
+ document,
3649
+ loader,
3650
+ expanded,
3651
+ baseUrl: view.baseUrl == null ? null : new URL(view.baseUrl)
3652
+ };
3653
+ }
3654
+ //#endregion
2985
3655
  //#region src/federation/keycache.ts
2986
3656
  const KEY_CACHE_GENERATION = "3";
2987
3657
  const COMPATIBLE_KEY_SEGMENT = "__compatible";
@@ -4792,13 +5462,13 @@ function recordCollectionMetrics(meterProvider, base, result, options = {}) {
4792
5462
  * @param parameters The parameters for handling the collection.
4793
5463
  * @returns A promise that resolves to an HTTP response.
4794
5464
  */
4795
- async function handleCollection(request, { name: name$2, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, portable, tracerProvider, meterProvider, onUnauthorized, onNotFound }) {
4796
- const spanName = name$2.trim().replace(/\s+/g, "_");
5465
+ async function handleCollection(request, { name: name$1, identifier, uriGetter, filter, filterPredicate, context, collectionCallbacks, portable, tracerProvider, meterProvider, onUnauthorized, onNotFound }) {
5466
+ const spanName = name$1.trim().replace(/\s+/g, "_");
4797
5467
  tracerProvider = tracerProvider ?? trace.getTracerProvider();
4798
5468
  const tracer = tracerProvider.getTracer(name, version);
4799
5469
  const cursor = new URL(request.url).searchParams.get("cursor");
4800
5470
  const metricBase = {
4801
- kind: getCollectionMetricKind(name$2),
5471
+ kind: getCollectionMetricKind(name$1),
4802
5472
  page: cursor != null,
4803
5473
  dispatcher: "built_in"
4804
5474
  };
@@ -4844,7 +5514,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
4844
5514
  span.setStatus({ code: SpanStatusCode.ERROR });
4845
5515
  return await onNotFound(request);
4846
5516
  }
4847
- const items = filterCollectionItems(page.items, name$2, filterPredicate);
5517
+ const items = filterCollectionItems(page.items, name$1, filterPredicate);
4848
5518
  itemCount = items.length;
4849
5519
  span.setAttribute("fedify.collection.items", itemCount);
4850
5520
  return items;
@@ -4886,7 +5556,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
4886
5556
  } else {
4887
5557
  const uri = new URL(portable == null ? baseUri : pageBase);
4888
5558
  uri.searchParams.set("cursor", cursor);
4889
- const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$2}`, {
5559
+ const pageOrResponse = await tracer.startActiveSpan(`activitypub.dispatch_collection_page ${name$1}`, {
4890
5560
  kind: SpanKind.SERVER,
4891
5561
  attributes: {
4892
5562
  "activitypub.collection.id": uri.href,
@@ -4902,7 +5572,7 @@ async function handleCollection(request, { name: name$2, identifier, uriGetter,
4902
5572
  span.setStatus({ code: SpanStatusCode.ERROR });
4903
5573
  return await onNotFound(request);
4904
5574
  }
4905
- const items = filterCollectionItems(page.items, name$2, filterPredicate);
5575
+ const items = filterCollectionItems(page.items, name$1, filterPredicate);
4906
5576
  itemCount = items.length;
4907
5577
  span.setAttribute("fedify.collection.items", itemCount);
4908
5578
  return {
@@ -5399,11 +6069,12 @@ async function handleInbox(request, options) {
5399
6069
  }, async (span) => {
5400
6070
  if (options.recipient != null) span.setAttribute("fedify.inbox.recipient", options.recipient);
5401
6071
  const spanContext = span.spanContext();
6072
+ const snapshot = createContextSnapshot(options.context.contextLoader);
5402
6073
  try {
5403
6074
  return await withContext({
5404
6075
  traceId: spanContext.traceId,
5405
6076
  spanId: spanContext.spanId
5406
- }, () => handleInboxInternal(request, options, span));
6077
+ }, () => handleInboxInternal(request, options, span, snapshot));
5407
6078
  } catch (e) {
5408
6079
  options.observation.hasException = true;
5409
6080
  span.setStatus({
@@ -5412,6 +6083,7 @@ async function handleInbox(request, options) {
5412
6083
  });
5413
6084
  throw e;
5414
6085
  } finally {
6086
+ snapshot.release();
5415
6087
  options.observation.project(span);
5416
6088
  span.end();
5417
6089
  }
@@ -5423,9 +6095,10 @@ async function handleInbox(request, options) {
5423
6095
  * @param request The HTTP request.
5424
6096
  * @param options The parameters for handling the inbox.
5425
6097
  * @param span The OpenTelemetry span for tracing.
6098
+ * @param snapshot The context snapshot of the delivery.
5426
6099
  * @returns A promise that resolves to an HTTP response.
5427
6100
  */
5428
- async function handleInboxInternal(request, parameters, span) {
6101
+ async function handleInboxInternal(request, parameters, span, snapshot) {
5429
6102
  const { recipient, context: ctx, inboxContextFactory, kv, kvPrefixes, publicKeyTtl, queue, actorDispatcher, inboxListeners, inboxErrorHandler, unverifiedActivityHandler, onNotFound, signatureTimeWindow, maxHttpSignatures, skipSignatureVerification, inboxChallengePolicy, meterProvider, tracerProvider, portableInbox } = parameters;
5430
6103
  const observation = parameters.observation;
5431
6104
  const signatureObservation = { [verificationObservation]: observation.verification };
@@ -5539,6 +6212,8 @@ async function handleInboxInternal(request, parameters, span) {
5539
6212
  tracerProvider,
5540
6213
  keyTtl: publicKeyTtl
5541
6214
  });
6215
+ const standardLoader = snapshot.loader("standard");
6216
+ const normalizationLoader = snapshot.loader("normalization");
5542
6217
  const jsonWithoutSig = detachSignature(json);
5543
6218
  const hasLdSignature = hasSignature(json);
5544
6219
  const canAttemptAlternateAuthAfterLdSignatureFailure = skipSignatureVerification || hasHttpSignatureHeaders(request) || hasObjectIntegrityProof(jsonWithoutSig);
@@ -5574,9 +6249,10 @@ async function handleInboxInternal(request, parameters, span) {
5574
6249
  let compactedJson = json;
5575
6250
  let compactedJsonWithoutSig = jsonWithoutSig;
5576
6251
  let ldSigVerified = false;
6252
+ let ldSigKey = null;
5577
6253
  if (hasLdSignature) {
5578
6254
  try {
5579
- compactedJson = await compactJsonLd(json, ctx.contextLoader);
6255
+ compactedJson = await compactJsonLd(json, normalizationLoader);
5580
6256
  } catch (error) {
5581
6257
  if (isInvalidJsonLdError(error)) {
5582
6258
  logger.error("Failed to parse JSON-LD:\n{error}", {
@@ -5612,16 +6288,18 @@ async function handleInboxInternal(request, parameters, span) {
5612
6288
  if (compactedJson !== json) {
5613
6289
  compactedJsonWithoutSig = detachSignature(compactedJson);
5614
6290
  try {
5615
- ldSigVerified = await verifyCompactJsonLd(compactedJson, {
6291
+ const ldResult = await verifyCompactJsonLdDetailed(compactedJson, {
5616
6292
  ...signatureObservation,
5617
- contextLoader: ctx.contextLoader,
6293
+ contextLoader: normalizationLoader,
5618
6294
  documentLoader: ctx.documentLoader,
5619
6295
  keyCache,
5620
6296
  meterProvider,
5621
6297
  tracerProvider
5622
6298
  });
6299
+ ldSigVerified = ldResult.verified;
6300
+ ldSigKey = ldResult.key;
5623
6301
  } catch (error) {
5624
- if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(compactedJsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(error);
6302
+ if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(compactedJsonWithoutSig, normalizationLoader)) return await respondInvalidActivity(error);
5625
6303
  if (isInvalidJsonLdError(error)) {
5626
6304
  logger.error("Failed to parse JSON-LD:\n{error}", {
5627
6305
  recipient,
@@ -5639,12 +6317,12 @@ async function handleInboxInternal(request, parameters, span) {
5639
6317
  if (!canAttemptAlternateAuthAfterLdSignatureFailure) throw error;
5640
6318
  if (!skipSignatureVerification) try {
5641
6319
  await Object$1.fromJsonLd(compactedJson, {
5642
- contextLoader: getNormalizationContextLoader(ctx.contextLoader),
6320
+ contextLoader: normalizationLoader,
5643
6321
  documentLoader: ctx.documentLoader,
5644
6322
  tracerProvider
5645
6323
  });
5646
6324
  } catch (parseError) {
5647
- if (parseError instanceof RangeError && await hasMalformedKnownTemporalLiteral(compactedJsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(parseError);
6325
+ if (parseError instanceof RangeError && await hasMalformedKnownTemporalLiteral(compactedJsonWithoutSig, normalizationLoader)) return await respondInvalidActivity(parseError);
5648
6326
  if (isInvalidJsonLdError(parseError)) {
5649
6327
  logger.error("Failed to parse JSON-LD:\n{error}", {
5650
6328
  recipient,
@@ -5668,6 +6346,33 @@ async function handleInboxInternal(request, parameters, span) {
5668
6346
  let activity = null;
5669
6347
  let activityVerified = false;
5670
6348
  let proofVerified = false;
6349
+ let documentSigners = [];
6350
+ let requestSigners = [];
6351
+ let viewSource;
6352
+ const rawViewSource = {
6353
+ policy: "standard",
6354
+ source: "activity",
6355
+ document: jsonWithoutSig,
6356
+ baseUrl: null
6357
+ };
6358
+ const isActorlessCandidate = async (candidate, source) => {
6359
+ if (skipSignatureVerification) return false;
6360
+ if (inboxListeners?.dispatchWithClass(candidate)?.options.allowActorless !== true) return false;
6361
+ try {
6362
+ return getActorPresence(await expandDocument(source.document, snapshot.loader(source.policy))) === "absent";
6363
+ } catch {
6364
+ return false;
6365
+ }
6366
+ };
6367
+ const verifyProofs = (contextLoader = standardLoader) => verifyObjectDetailed(Activity, jsonWithoutSig, {
6368
+ ...signatureObservation,
6369
+ contextLoader,
6370
+ documentLoader: ctx.documentLoader,
6371
+ keyCache,
6372
+ meterProvider,
6373
+ tracerProvider,
6374
+ verifyPortableObject: ctx.verifyPortableObject
6375
+ });
5671
6376
  if (ldSigVerified) {
5672
6377
  logger.debug("Linked Data Signatures are verified.", {
5673
6378
  recipient,
@@ -5676,18 +6381,49 @@ async function handleInboxInternal(request, parameters, span) {
5676
6381
  try {
5677
6382
  activity = observation.activity = await Activity.fromJsonLd(compactedJsonWithoutSig, {
5678
6383
  ...ctx,
5679
- contextLoader: getNormalizationContextLoader(ctx.contextLoader)
6384
+ contextLoader: normalizationLoader
5680
6385
  });
5681
6386
  } catch (error) {
5682
- if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(compactedJsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(error);
6387
+ if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(compactedJsonWithoutSig, normalizationLoader)) return await respondInvalidActivity(error);
5683
6388
  if (!isPermanentActivityParseError(error)) throw error;
5684
6389
  return await respondInvalidActivity(error);
5685
6390
  }
5686
6391
  activityVerified = true;
6392
+ viewSource = {
6393
+ policy: "normalization",
6394
+ source: "normalizedActivity",
6395
+ document: compactedJsonWithoutSig,
6396
+ baseUrl: null
6397
+ };
6398
+ if (ldSigKey?.ownerId != null) documentSigners = [{
6399
+ kind: "actor",
6400
+ id: ldSigKey.ownerId,
6401
+ mechanism: "linkedDataSignatures",
6402
+ keyId: ldSigKey.id
6403
+ }];
6404
+ if (await isActorlessCandidate(activity, viewSource)) {
6405
+ let result = null;
6406
+ try {
6407
+ result = await verifyProofs();
6408
+ } catch (error) {
6409
+ if (!isPermanentActivityParseError(error)) throw error;
6410
+ logger.debug("Failed to verify the Object Integrity Proofs of the activity without an actor:\n{error}", {
6411
+ recipient,
6412
+ error
6413
+ });
6414
+ }
6415
+ if (result?.status === "unavailable") throw result.error;
6416
+ if (result?.status === "verified" && result.signers.length > 0) {
6417
+ activity = observation.activity = result.object;
6418
+ documentSigners = toProofPrincipals(result.signers);
6419
+ viewSource = rawViewSource;
6420
+ proofVerified = true;
6421
+ }
6422
+ }
5687
6423
  if (!skipSignatureVerification && isPortableActivity(activity)) try {
5688
6424
  proofVerified = await verifyObject(Activity, jsonWithoutSig, {
5689
6425
  ...signatureObservation,
5690
- contextLoader: wrapContextLoaderForJsonLd(ctx.contextLoader),
6426
+ contextLoader: standardLoader,
5691
6427
  documentLoader: ctx.documentLoader,
5692
6428
  keyCache,
5693
6429
  meterProvider,
@@ -5705,18 +6441,11 @@ async function handleInboxInternal(request, parameters, span) {
5705
6441
  recipient,
5706
6442
  json
5707
6443
  });
6444
+ let proofResult;
5708
6445
  try {
5709
- activity = await verifyObject(Activity, jsonWithoutSig, {
5710
- ...signatureObservation,
5711
- contextLoader: wrapContextLoaderForJsonLd(ctx.contextLoader),
5712
- documentLoader: ctx.documentLoader,
5713
- keyCache,
5714
- meterProvider,
5715
- tracerProvider,
5716
- verifyPortableObject: ctx.verifyPortableObject
5717
- });
6446
+ proofResult = await verifyProofs();
5718
6447
  } catch (error) {
5719
- if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(jsonWithoutSig, ctx.contextLoader)) return await respondInvalidActivity(error);
6448
+ if (error instanceof RangeError && await hasMalformedKnownTemporalLiteral(jsonWithoutSig, standardLoader)) return await respondInvalidActivity(error);
5720
6449
  if (deferredLdSignatureError != null) {
5721
6450
  logger.debug("Object Integrity Proof fallback did not supersede a deferred Linked Data Signature failure:\n{error}", {
5722
6451
  recipient,
@@ -5752,6 +6481,22 @@ async function handleInboxInternal(request, parameters, span) {
5752
6481
  headers: { "Content-Type": "text/plain; charset=utf-8" }
5753
6482
  });
5754
6483
  }
6484
+ if (proofResult.status === "verified") if (proofResult.signers.length < 1 && await isActorlessCandidate(proofResult.object, rawViewSource)) activity = null;
6485
+ else {
6486
+ activity = proofResult.object;
6487
+ documentSigners = toProofPrincipals(proofResult.signers);
6488
+ viewSource = rawViewSource;
6489
+ }
6490
+ else if (proofResult.status === "unavailable") {
6491
+ let parsed = null;
6492
+ try {
6493
+ parsed = await Activity.fromJsonLd(jsonWithoutSig, {
6494
+ ...ctx,
6495
+ contextLoader: standardLoader
6496
+ });
6497
+ } catch {}
6498
+ if (parsed != null && await isActorlessCandidate(parsed, rawViewSource)) throw proofResult.error;
6499
+ }
5755
6500
  if (activity == null) logger.debug("Object Integrity Proofs are not verified.", {
5756
6501
  recipient,
5757
6502
  activity: json
@@ -5881,14 +6626,28 @@ async function handleInboxInternal(request, parameters, span) {
5881
6626
  try {
5882
6627
  activity = observation.activity = await Activity.fromJsonLd(jsonWithoutSig, {
5883
6628
  ...ctx,
5884
- contextLoader: wrapContextLoaderForJsonLd(ctx.contextLoader)
6629
+ contextLoader: standardLoader
5885
6630
  });
5886
6631
  } catch (error) {
5887
6632
  if (!isPermanentActivityParseError(error)) throw error;
5888
6633
  return await respondInvalidActivity(error);
5889
6634
  }
6635
+ viewSource = rawViewSource;
5890
6636
  }
5891
6637
  observation.activity = activity;
6638
+ let view;
6639
+ try {
6640
+ view = await buildVerifiedActivityView(snapshot, viewSource ?? rawViewSource);
6641
+ } catch (error) {
6642
+ if (!isPermanentActivityParseError(error)) throw error;
6643
+ return await respondInvalidActivity(error);
6644
+ }
6645
+ const actorless = !skipSignatureVerification && getActorPresence(view.expanded) === "absent" && inboxListeners?.dispatchWithClass(activity)?.options.allowActorless === true;
6646
+ span.setAttribute("activitypub.activity.actorless", actorless);
6647
+ if (actorless && httpSigKey != null) {
6648
+ const principal = getHttpKeyPrincipal(httpSigKey);
6649
+ if (principal != null) requestSigners = [principal];
6650
+ }
5892
6651
  observation.stage = "policy";
5893
6652
  observation.result = {
5894
6653
  disposition: "rejected",
@@ -5932,7 +6691,7 @@ async function handleInboxInternal(request, parameters, span) {
5932
6691
  const rejection = await observeAttempt(signatureObservation, "objectIntegrity", async (verification) => {
5933
6692
  const rejection = await verifyPortableActivityId(activity.id, jsonWithoutSig, {
5934
6693
  [verificationObservation]: verification,
5935
- contextLoader: wrapContextLoaderForJsonLd(ctx.contextLoader),
6694
+ contextLoader: standardLoader,
5936
6695
  documentLoader: ctx.documentLoader,
5937
6696
  keyCache,
5938
6697
  meterProvider,
@@ -5970,7 +6729,7 @@ async function handleInboxInternal(request, parameters, span) {
5970
6729
  });
5971
6730
  }
5972
6731
  }
5973
- if (httpSigKey != null && !await doesActorOwnKey(activity, httpSigKey, ctx)) {
6732
+ if (httpSigKey != null && !actorless && !await doesActorOwnKey(activity, httpSigKey, ctx)) {
5974
6733
  if (deferredLdSignatureError != null) throw deferredLdSignatureError;
5975
6734
  observation.authentication = {
5976
6735
  status: "rejected",
@@ -5996,6 +6755,66 @@ async function handleInboxInternal(request, parameters, span) {
5996
6755
  headers: { "Content-Type": "text/plain; charset=utf-8" }
5997
6756
  });
5998
6757
  }
6758
+ if (httpSigKey != null && !actorless && activity.actorId != null) requestSigners = [{
6759
+ kind: "actor",
6760
+ id: activity.actorId,
6761
+ mechanism: "httpSignatures",
6762
+ keyId: httpSigKey.id
6763
+ }];
6764
+ if (actorless) {
6765
+ const check = checkActorlessActivity(activity, {
6766
+ verified: true,
6767
+ requestSigners,
6768
+ documentSigners,
6769
+ originAuthenticated: [],
6770
+ expandedActivity: [],
6771
+ baseUrl: null
6772
+ });
6773
+ if (check.status === "noAuthenticatedPrincipal") {
6774
+ if (deferredLdSignatureError != null) throw deferredLdSignatureError;
6775
+ observation.authentication = {
6776
+ status: "rejected",
6777
+ reason: { type: "noAuthenticatedPrincipal" }
6778
+ };
6779
+ logger.error("The activity {activityId} declares no actor, and no principal is authenticated for it.", {
6780
+ activity: json,
6781
+ recipient,
6782
+ activityId: activity.id?.href
6783
+ });
6784
+ span.setStatus({
6785
+ code: SpanStatusCode.ERROR,
6786
+ message: "No principal is authenticated for the activity."
6787
+ });
6788
+ return new Response("No principal is authenticated for the activity.", {
6789
+ status: 401,
6790
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
6791
+ });
6792
+ } else if (check.status === "activityOriginMismatch") {
6793
+ if (deferredLdSignatureError != null) throw deferredLdSignatureError;
6794
+ observation.authentication = {
6795
+ status: "rejected",
6796
+ reason: {
6797
+ type: "activityOriginMismatch",
6798
+ activityId: new URL(activity.id.href),
6799
+ actorIds: check.principals.map((principal) => principal.id)
6800
+ }
6801
+ };
6802
+ logger.error("The activity ({activityId}) does not have the same origin as the principals that authenticated it.", {
6803
+ activity: json,
6804
+ recipient,
6805
+ activityId: activity.id?.href,
6806
+ principals: check.principals.map((principal) => principal.id.href)
6807
+ });
6808
+ span.setStatus({
6809
+ code: SpanStatusCode.ERROR,
6810
+ message: `The activity (${activity.id?.href}) does not have the same origin as the principals that authenticated it.`
6811
+ });
6812
+ return new Response("The activity and its signers do not have the same origin.", {
6813
+ status: 401,
6814
+ headers: { "Content-Type": "text/plain; charset=utf-8" }
6815
+ });
6816
+ }
6817
+ }
5999
6818
  if (!skipSignatureVerification) {
6000
6819
  const compoundJson = Array.isArray(json) ? json : jsonWithoutSig;
6001
6820
  if (inspectCompoundPortableObjectApplicability(compoundJson, INBOX_COMPOUND_PROOF_LIMITS) !== "absent") {
@@ -6055,7 +6874,7 @@ async function handleInboxInternal(request, parameters, span) {
6055
6874
  return await getFailedSignatureResponse(inboxChallengePolicy, kv, kvPrefixes);
6056
6875
  }
6057
6876
  }
6058
- if (!skipSignatureVerification && activity.id != null && !actorsHaveSameOrigin(activity)) {
6877
+ if (!skipSignatureVerification && !actorless && activity.id != null && !actorsHaveSameOrigin(activity)) {
6059
6878
  observation.authentication = {
6060
6879
  status: "rejected",
6061
6880
  reason: {
@@ -6084,7 +6903,7 @@ async function handleInboxInternal(request, parameters, span) {
6084
6903
  attempts: observation.verification.attempts.filter((attempt) => attempt.status === "verified")
6085
6904
  };
6086
6905
  observation.stage = "dispatch";
6087
- if (activity.actorId == null) {
6906
+ if (!actorless && activity.actorId == null) {
6088
6907
  observation.result = {
6089
6908
  disposition: "rejected",
6090
6909
  reason: "missingActor"
@@ -6108,6 +6927,17 @@ async function handleInboxInternal(request, parameters, span) {
6108
6927
  originalJson: json,
6109
6928
  normalizedActivity: hasLdSignature && compactedJson !== json ? compactedJson : void 0,
6110
6929
  ldSignatureVerified: hasLdSignature ? ldSigVerified : void 0,
6930
+ authentication: createAuthenticationEvidence({
6931
+ verified: !skipSignatureVerification,
6932
+ requestSigners,
6933
+ documentSigners,
6934
+ originAuthenticated: [],
6935
+ expandedActivity: deepFreeze(view.expanded),
6936
+ baseUrl: null
6937
+ }),
6938
+ verifiedView: view.view,
6939
+ replayable: view.replayable,
6940
+ actorless,
6111
6941
  activity,
6112
6942
  recipient,
6113
6943
  inboxListeners,
@@ -6322,8 +7152,8 @@ var CustomCollectionHandler = class {
6322
7152
  * @param CollectionPage The CollectionPage constructor.
6323
7153
  * @param filterPredicate Optional filter predicate for items.
6324
7154
  */
6325
- constructor(name$1, values, context, callbacks, tracerProvider = trace.getTracerProvider(), meterProvider, Collection, CollectionPage, filterPredicate, portable) {
6326
- this.name = name$1;
7155
+ constructor(name$2, values, context, callbacks, tracerProvider = trace.getTracerProvider(), meterProvider, Collection, CollectionPage, filterPredicate, portable) {
7156
+ this.name = name$2;
6327
7157
  this.values = values;
6328
7158
  this.context = context;
6329
7159
  this.callbacks = callbacks;
@@ -6832,6 +7662,42 @@ const MIN_COMPONENTS = [
6832
7662
  "@authority"
6833
7663
  ];
6834
7664
  /**
7665
+ * Turns the signers of verified Object Integrity Proofs into principals.
7666
+ */
7667
+ function toProofPrincipals(signers) {
7668
+ return signers.map((signer) => ({
7669
+ kind: signer.kind,
7670
+ id: signer.id,
7671
+ mechanism: "objectIntegrityProofs",
7672
+ keyId: signer.keyId
7673
+ }));
7674
+ }
7675
+ /**
7676
+ * Gets the principal of an HTTP Signature key, whose owner was confirmed by
7677
+ * the owner's own document, or by the signed document of a portable actor,
7678
+ * when the key was fetched.
7679
+ */
7680
+ function getHttpKeyPrincipal(key) {
7681
+ const evidence = getVerifiedKeyOwnerEvidence(key);
7682
+ if (evidence != null) try {
7683
+ return {
7684
+ kind: "actor",
7685
+ id: parseIri(evidence.ownerId),
7686
+ mechanism: "httpSignatures",
7687
+ keyId: key.id
7688
+ };
7689
+ } catch {
7690
+ return null;
7691
+ }
7692
+ if (key.ownerId == null || isPortableId(key.ownerId)) return null;
7693
+ return {
7694
+ kind: "actor",
7695
+ id: key.ownerId,
7696
+ mechanism: "httpSignatures",
7697
+ keyId: key.id
7698
+ };
7699
+ }
7700
+ /**
6835
7701
  * Checks whether an HTTP Signature key is a key of an FEP-ef61 portable
6836
7702
  * actor, i.e., its ID is a compatible identifier or an `ap:` or `ap+ef61:`
6837
7703
  * URI, and it is owned by a portable actor, which only the portable actor's
@@ -9332,24 +10198,102 @@ var FederationImpl = class extends FederationBuilderImpl {
9332
10198
  let dispatched;
9333
10199
  let parseInput = void 0;
9334
10200
  let parseContextLoader = context.contextLoader;
10201
+ let authentication = getEmptyAuthenticationEvidence();
10202
+ let actorPresence = "present";
10203
+ if (message.verifiedView !== void 0) {
10204
+ const view = parseSerializedVerifiedActivityView(message.verifiedView);
10205
+ let restored;
10206
+ let restoredAuthentication = null;
10207
+ let failure;
10208
+ if (view == null) failure = /* @__PURE__ */ new TypeError("The queued activity has a malformed verified view.");
10209
+ else try {
10210
+ if (view.source === "normalizedActivity") assertSafeJsonLd(message.normalizedActivity);
10211
+ restored = await restoreVerifiedActivityView(view, message);
10212
+ } catch (error) {
10213
+ if (!(error instanceof ContextSnapshotMissError) && !isPermanentInboxParseError(error)) {
10214
+ await handleRetriableFailure(error);
10215
+ return;
10216
+ }
10217
+ failure = error;
10218
+ }
10219
+ if (restored != null) {
10220
+ const expandedActivity = deepFreeze(restored.expanded);
10221
+ restoredAuthentication = message.authentication === void 0 ? createAuthenticationEvidence({
10222
+ ...getEmptyAuthenticationEvidence(),
10223
+ expandedActivity,
10224
+ baseUrl: restored.baseUrl
10225
+ }) : deserializeAuthentication(message.authentication, expandedActivity, restored.baseUrl);
10226
+ if (restoredAuthentication == null) failure = /* @__PURE__ */ new TypeError("The queued activity has malformed authentication evidence.");
10227
+ }
10228
+ if (failure != null || restored == null || restoredAuthentication == null) {
10229
+ await reportInboxError(failure);
10230
+ logger.error("Failed to replay the queued incoming activity:\n{error}", {
10231
+ error: failure,
10232
+ trial: message.attempt,
10233
+ activity: message.activity,
10234
+ recipient: message.identifier
10235
+ });
10236
+ listenerSpan.setStatus({
10237
+ code: SpanStatusCode.ERROR,
10238
+ message: String(failure)
10239
+ });
10240
+ listenerSpan.end();
10241
+ return;
10242
+ }
10243
+ authentication = restoredAuthentication;
10244
+ actorPresence = getActorPresence(restored.expanded);
10245
+ const policy = view.policy;
10246
+ const replayLoader = createReleasableLoader(restored.loader, policy === "normalization" ? getNormalizationContextLoader(context.contextLoader) : wrapContextLoaderForJsonLd(context.contextLoader), policy);
10247
+ parseInput = restored.document;
10248
+ parseContextLoader = restored.loader;
10249
+ try {
10250
+ activity = await Activity.fromJsonLd(restored.document, {
10251
+ ...context,
10252
+ contextLoader: replayLoader.loader,
10253
+ ...restored.baseUrl == null ? {} : { baseUrl: restored.baseUrl }
10254
+ });
10255
+ } catch (error) {
10256
+ if (isPermanentInboxParseError(error)) {
10257
+ await reportInboxError(error);
10258
+ logger.error("Failed to parse the queued incoming activity:\n{error}", {
10259
+ error,
10260
+ trial: message.attempt,
10261
+ activity: message.activity,
10262
+ recipient: message.identifier
10263
+ });
10264
+ listenerSpan.setStatus({
10265
+ code: SpanStatusCode.ERROR,
10266
+ message: String(error)
10267
+ });
10268
+ listenerSpan.end();
10269
+ return;
10270
+ }
10271
+ await handleRetriableFailure(error);
10272
+ return;
10273
+ } finally {
10274
+ replayLoader.release();
10275
+ }
10276
+ }
9335
10277
  try {
9336
- const hasSignatureField = hasSignature(message.activity);
9337
- const shouldParseFromNormalizedSignedPayload = message.ldSignatureVerified === true || message.normalizedActivity != null || message.ldSignatureVerified == null && hasSignatureField;
9338
- const parseContext = hasSignatureField ? {
9339
- ...context,
9340
- contextLoader: getNormalizationContextLoader(context.contextLoader)
9341
- } : {
9342
- ...context,
9343
- contextLoader: wrapContextLoaderForJsonLd(context.contextLoader)
9344
- };
9345
- parseContextLoader = parseContext.contextLoader;
9346
- let normalizedActivity;
9347
- if (shouldParseFromNormalizedSignedPayload) {
9348
- normalizedActivity = message.normalizedActivity ?? await compactJsonLd(message.activity, context.contextLoader);
9349
- assertSafeJsonLd(normalizedActivity);
10278
+ if (activity == null) {
10279
+ const hasSignatureField = hasSignature(message.activity);
10280
+ const shouldParseFromNormalizedSignedPayload = message.ldSignatureVerified === true || message.normalizedActivity != null || message.ldSignatureVerified == null && hasSignatureField;
10281
+ const parseContext = hasSignatureField ? {
10282
+ ...context,
10283
+ contextLoader: getNormalizationContextLoader(context.contextLoader)
10284
+ } : {
10285
+ ...context,
10286
+ contextLoader: wrapContextLoaderForJsonLd(context.contextLoader)
10287
+ };
10288
+ parseContextLoader = parseContext.contextLoader;
10289
+ let normalizedActivity;
10290
+ if (shouldParseFromNormalizedSignedPayload) {
10291
+ normalizedActivity = message.normalizedActivity ?? await compactJsonLd(message.activity, context.contextLoader);
10292
+ assertSafeJsonLd(normalizedActivity);
10293
+ }
10294
+ parseInput = shouldParseFromNormalizedSignedPayload ? detachSignature(normalizedActivity) : hasSignatureField ? detachSignature(message.activity) : message.activity;
10295
+ activity = await Activity.fromJsonLd(parseInput, parseContext);
9350
10296
  }
9351
- parseInput = shouldParseFromNormalizedSignedPayload ? detachSignature(normalizedActivity) : hasSignatureField ? detachSignature(message.activity) : message.activity;
9352
- activity = await Activity.fromJsonLd(parseInput, parseContext);
9353
10297
  activityType = getTypeId(activity).href;
9354
10298
  span.setAttribute("activitypub.activity.type", activityType);
9355
10299
  listenerSpan.setAttribute("activitypub.activity.type", activityType);
@@ -9358,7 +10302,8 @@ var FederationImpl = class extends FederationBuilderImpl {
9358
10302
  span.setAttribute("activitypub.activity.id", activity.id.href);
9359
10303
  listenerSpan.setAttribute("activitypub.activity.id", activity.id.href);
9360
10304
  }
9361
- if (activity.actorId == null) {
10305
+ const actorless = actorPresence === "absent" && !this.skipSignatureVerification && authentication.verified && this.inboxListeners?.dispatchWithClass(activity)?.options.allowActorless === true && isActorlessActivityAuthenticated(activity, authentication);
10306
+ if (activity.actorId == null && !actorless) {
9362
10307
  logger.error("Missing actor in queued incoming activity.", {
9363
10308
  activityId: activity.id?.href,
9364
10309
  activity: message.activity,
@@ -9372,7 +10317,7 @@ var FederationImpl = class extends FederationBuilderImpl {
9372
10317
  listenerSpan.end();
9373
10318
  return;
9374
10319
  }
9375
- if (!this.skipSignatureVerification && activity.id != null && !actorsHaveSameOrigin(activity)) {
10320
+ if (!this.skipSignatureVerification && !actorless && activity.id != null && !actorsHaveSameOrigin(activity)) {
9376
10321
  logger.error("The queued activity ({activityId}) and its actors do not have the same origin.", {
9377
10322
  activityId: activity.id.href,
9378
10323
  actorIds: activity.actorIds.map((actor) => actor.href),
@@ -9387,22 +10332,18 @@ var FederationImpl = class extends FederationBuilderImpl {
9387
10332
  listenerSpan.end();
9388
10333
  return;
9389
10334
  }
9390
- cacheKey = activity.id == null ? null : [
9391
- ...this.kvPrefixes.activityIdempotence,
9392
- context.origin,
9393
- activity.id.href
9394
- ];
9395
- if (cacheKey != null) {
9396
- if (await this.kv.get(cacheKey) === true) {
9397
- logger.debug("Activity {activityId} has already been processed.", {
9398
- activityId: activity.id?.href,
9399
- activity: message.activity,
9400
- recipient: message.identifier
9401
- });
9402
- recordInboxActivity(this.meterProvider, "rejected", activityType);
9403
- listenerSpan.end();
9404
- return;
9405
- }
10335
+ const parsedActivity = activity;
10336
+ const parsedActivityType = activityType;
10337
+ cacheKey = await getInboxIdempotencyKey(this.idempotencyStrategy, () => context.toInboxContext(message.identifier, message.activity, parsedActivity.id?.href, parsedActivityType, { authentication }), activity, context.origin, message.identifier, this.kvPrefixes.activityIdempotence);
10338
+ if (await isInboxActivityProcessed(this.kv, cacheKey, activity, context.origin, this.kvPrefixes.activityIdempotence)) {
10339
+ logger.debug("Activity {activityId} has already been processed.", {
10340
+ activityId: activity.id?.href,
10341
+ activity: message.activity,
10342
+ recipient: message.identifier
10343
+ });
10344
+ recordInboxActivity(this.meterProvider, "rejected", activityType);
10345
+ listenerSpan.end();
10346
+ return;
9406
10347
  }
9407
10348
  dispatched = this.inboxListeners?.dispatchWithClass(activity);
9408
10349
  } catch (error) {
@@ -9461,7 +10402,7 @@ var FederationImpl = class extends FederationBuilderImpl {
9461
10402
  try {
9462
10403
  const started = performance.now();
9463
10404
  try {
9464
- await listener(context.toInboxContext(message.identifier, message.activity, activity.id?.href, activityType), activity);
10405
+ await listener(context.toInboxContext(message.identifier, message.activity, activity.id?.href, activityType, { authentication }), activity);
9465
10406
  } finally {
9466
10407
  this.metrics.recordInboxProcessingDuration(activityType, getDurationMs(started));
9467
10408
  }
@@ -10768,7 +11709,7 @@ var ContextImpl = class ContextImpl {
10768
11709
  invokedFromActorKeyPairsDispatcher: this.invokedFromActorKeyPairsDispatcher
10769
11710
  });
10770
11711
  }
10771
- toInboxContext(recipient, activity, activityId, activityType) {
11712
+ toInboxContext(recipient, activity, activityId, activityType, metadata) {
10772
11713
  return new InboxContextImpl(recipient, activity, activityId, activityType, {
10773
11714
  url: this.url,
10774
11715
  federation: this.federation,
@@ -10776,7 +11717,7 @@ var ContextImpl = class ContextImpl {
10776
11717
  documentLoader: this.documentLoader,
10777
11718
  contextLoader: this.contextLoader,
10778
11719
  invokedFromActorKeyPairsDispatcher: this.invokedFromActorKeyPairsDispatcher
10779
- });
11720
+ }, metadata?.authentication);
10780
11721
  }
10781
11722
  toOutboxContext(identifier, activity, activityId, activityType) {
10782
11723
  return new OutboxContextImpl(identifier, activity, activityId, activityType, {
@@ -11391,12 +12332,20 @@ var ContextImpl = class ContextImpl {
11391
12332
  });
11392
12333
  }
11393
12334
  async routeActivityInternal(recipient, activity, options = {}, span) {
12335
+ const snapshot = createContextSnapshot(options.contextLoader ?? this.contextLoader);
12336
+ try {
12337
+ return await this.#routeActivityWithSnapshot(recipient, activity, options, span, snapshot);
12338
+ } finally {
12339
+ snapshot.release();
12340
+ }
12341
+ }
12342
+ async #routeActivityWithSnapshot(recipient, activity, options, span, snapshot) {
11394
12343
  const logger = getLogger([
11395
12344
  "fedify",
11396
12345
  "federation",
11397
12346
  "inbox"
11398
12347
  ]);
11399
- const contextLoader = options.contextLoader ?? this.contextLoader;
12348
+ const contextLoader = snapshot.loader("standard");
11400
12349
  let json = await activity.toJsonLd({ contextLoader });
11401
12350
  const keyCache = new KvKeyCache(this.federation.kv, this.federation.kvPrefixes.publicKey, {
11402
12351
  documentLoader: this.documentLoader,
@@ -11404,13 +12353,19 @@ var ContextImpl = class ContextImpl {
11404
12353
  tracerProvider: this.tracerProvider,
11405
12354
  keyTtl: this.federation.publicKeyTtl
11406
12355
  });
11407
- if (await verifyObject(Activity, json, {
12356
+ let documentSigners = [];
12357
+ let originAuthenticated = [];
12358
+ let viewSource;
12359
+ let fetchedView;
12360
+ const document = detachSignature(json);
12361
+ const verified = await verifyObjectDetailed(Activity, document, {
11408
12362
  contextLoader,
11409
12363
  documentLoader: options.documentLoader ?? this.documentLoader,
11410
12364
  meterProvider: this.meterProvider,
11411
12365
  tracerProvider: options.tracerProvider ?? this.tracerProvider,
11412
12366
  keyCache
11413
- }) == null) {
12367
+ });
12368
+ if (verified.status !== "verified") {
11414
12369
  logger.debug("Object Integrity Proofs are not verified.", {
11415
12370
  recipient,
11416
12371
  activity: json
@@ -11422,8 +12377,29 @@ var ContextImpl = class ContextImpl {
11422
12377
  });
11423
12378
  return false;
11424
12379
  }
11425
- const fetched = await this.lookupObject(activity.id, options);
11426
- if (fetched == null) {
12380
+ let candidate;
12381
+ let candidateView;
12382
+ const verifications = /* @__PURE__ */ new WeakMap();
12383
+ const fetched = await this.lookupObject(activity.id, {
12384
+ ...options,
12385
+ contextLoader,
12386
+ verifyPortableObject: async (document, verifierOptions) => {
12387
+ const result = await this.verifyPortableObject(document, verifierOptions);
12388
+ if (document != null && typeof document === "object") verifications.set(document, result);
12389
+ return result;
12390
+ },
12391
+ [lookupObserver]: { accept: async (accepted) => {
12392
+ candidate = accepted;
12393
+ candidateView = await buildVerifiedActivityView(createContextSnapshot(accepted.contextLoader), {
12394
+ policy: "standard",
12395
+ source: "activity",
12396
+ document: detachSignature(accepted.document),
12397
+ baseUrl: accepted.baseUrl
12398
+ });
12399
+ return true;
12400
+ } }
12401
+ });
12402
+ if (fetched == null || candidate == null || candidateView == null) {
11427
12403
  logger.debug("Failed to fetch the remote activity object {activityId}.", {
11428
12404
  recipient,
11429
12405
  activity: json,
@@ -11459,11 +12435,50 @@ var ContextImpl = class ContextImpl {
11459
12435
  }
11460
12436
  logger.debug("Successfully fetched the remote activity object {activityId}; ignore the original activity and use the fetched one, which is trustworthy.");
11461
12437
  activity = fetched;
11462
- json = await fetched.toJsonLd({ contextLoader });
11463
- } else logger.debug("Object Integrity Proofs are verified.", {
11464
- recipient,
11465
- activity: json
11466
- });
12438
+ json = candidate.document;
12439
+ fetchedView = candidateView;
12440
+ viewSource = {
12441
+ policy: "standard",
12442
+ source: "activity",
12443
+ document: detachSignature(candidate.document),
12444
+ baseUrl: candidate.baseUrl
12445
+ };
12446
+ if (isPortableId(fetched.id)) {
12447
+ const result = verifications.get(candidate.document);
12448
+ if (result?.verified === true && result.method === "proof") documentSigners = result.keys.flatMap((key) => {
12449
+ const signer = getProofSigner(key);
12450
+ return signer == null ? [] : [{
12451
+ kind: signer.kind,
12452
+ id: signer.id,
12453
+ mechanism: "objectIntegrityProofs",
12454
+ keyId: signer.keyId
12455
+ }];
12456
+ });
12457
+ } else originAuthenticated = fetched.actorIds.map((actorId) => ({
12458
+ kind: "actor",
12459
+ id: actorId,
12460
+ mechanism: "origin",
12461
+ keyId: null
12462
+ }));
12463
+ } else {
12464
+ logger.debug("Object Integrity Proofs are verified.", {
12465
+ recipient,
12466
+ activity: json
12467
+ });
12468
+ activity = verified.object;
12469
+ documentSigners = verified.signers.map((signer) => ({
12470
+ kind: signer.kind,
12471
+ id: signer.id,
12472
+ mechanism: "objectIntegrityProofs",
12473
+ keyId: signer.keyId
12474
+ }));
12475
+ viewSource = {
12476
+ policy: "standard",
12477
+ source: "activity",
12478
+ document,
12479
+ baseUrl: null
12480
+ };
12481
+ }
11467
12482
  if (activity.actorId == null) {
11468
12483
  logger.debug("Activity is missing an actor; unable to verify.", {
11469
12484
  recipient,
@@ -11478,10 +12493,21 @@ var ContextImpl = class ContextImpl {
11478
12493
  });
11479
12494
  return false;
11480
12495
  }
12496
+ const view = fetchedView ?? await buildVerifiedActivityView(snapshot, viewSource);
11481
12497
  const routeResult = await routeActivity({
11482
12498
  context: this,
11483
12499
  json,
11484
12500
  ldSignatureVerified: false,
12501
+ authentication: createAuthenticationEvidence({
12502
+ verified: true,
12503
+ requestSigners: [],
12504
+ documentSigners,
12505
+ originAuthenticated,
12506
+ expandedActivity: deepFreeze(view.expanded),
12507
+ baseUrl: viewSource.baseUrl
12508
+ }),
12509
+ verifiedView: view.view,
12510
+ replayable: view.replayable,
11485
12511
  activity,
11486
12512
  recipient,
11487
12513
  inboxListeners: this.federation.inboxListeners,
@@ -11832,12 +12858,14 @@ var InboxContextImpl = class InboxContextImpl extends ContextImpl {
11832
12858
  activity;
11833
12859
  activityId;
11834
12860
  activityType;
11835
- constructor(recipient, activity, activityId, activityType, options) {
12861
+ authentication;
12862
+ constructor(recipient, activity, activityId, activityType, options, authentication) {
11836
12863
  super(options);
11837
12864
  this.recipient = recipient;
11838
12865
  this.activity = activity;
11839
12866
  this.activityId = activityId;
11840
12867
  this.activityType = activityType;
12868
+ this.authentication = authentication ?? getEmptyAuthenticationEvidence();
11841
12869
  }
11842
12870
  clone(data) {
11843
12871
  return new InboxContextImpl(this.recipient, this.activity, this.activityId, this.activityType, {
@@ -11847,7 +12875,7 @@ var InboxContextImpl = class InboxContextImpl extends ContextImpl {
11847
12875
  documentLoader: this.documentLoader,
11848
12876
  contextLoader: this.contextLoader,
11849
12877
  invokedFromActorKeyPairsDispatcher: this.invokedFromActorKeyPairsDispatcher
11850
- });
12878
+ }, this.authentication);
11851
12879
  }
11852
12880
  forwardActivity(forwarder, recipients, options) {
11853
12881
  return forwardActivity(this, "inbox", forwarder, recipients, options).then(() => void 0);
@@ -11963,4 +12991,4 @@ function getRequestId(request) {
11963
12991
  return `req_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`;
11964
12992
  }
11965
12993
  //#endregion
11966
- export { respondWithObjectIfAcceptable as a, digest as c, parseCircuitBreakerKvState as d, createFederationBuilder as f, respondWithObject as i, CircuitBreaker as l, handleWebFinger as n, SendActivityError as o, createExponentialBackoffPolicy as r, buildCollectionSynchronizationHeader as s, createFederation as t, normalizeCircuitBreakerOptions as u };
12994
+ export { respondWithObjectIfAcceptable as a, buildCollectionSynchronizationHeader as c, normalizeCircuitBreakerOptions as d, parseCircuitBreakerKvState as f, respondWithObject as i, digest as l, handleWebFinger as n, SendActivityError as o, createFederationBuilder as p, createExponentialBackoffPolicy as r, isActorAuthenticated as s, createFederation as t, CircuitBreaker as u };