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

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 (141) hide show
  1. package/README.md +3 -0
  2. package/dist/{activity-listener-C-BSbKI7.mjs → activity-listener-CELFAodc.mjs} +8 -4
  3. package/dist/{builder-Da8b9O6v.mjs → builder-DphK9v7f.mjs} +5 -5
  4. package/dist/{client-D-XP_Ccb.d.ts → client-3InDZbba.d.cts} +0 -1
  5. package/dist/{client-D-XP_Ccb.d.cts → client-3InDZbba.d.ts} +0 -1
  6. package/dist/{client-CEnNz8O7.mjs → client-5SIFzW3q.mjs} +113 -64
  7. package/dist/compat/mod.d.cts +1 -1
  8. package/dist/compat/mod.d.ts +1 -1
  9. package/dist/compat/transformers.test.mjs +1 -1
  10. package/dist/{compound-proof-D1djO5fa.mjs → compound-proof-cYtvlTgF.mjs} +2 -2
  11. package/dist/{context-8Tu9kea0.mjs → context-COHd1Rsh.mjs} +2 -0
  12. package/dist/{context-BdB54V-A.d.ts → context-DQJE9YfH.d.ts} +332 -6
  13. package/dist/{context-CQ9H8vNW.d.cts → context-DSuL3YWx.d.cts} +332 -6
  14. package/dist/{deno-eNpfj4oR.mjs → deno-iOAA7iRN.mjs} +1 -1
  15. package/dist/docloader-DCZkiTeF.mjs +136 -0
  16. package/dist/{exporter-DThFfe7P.mjs → exporter-DVG68ozH.mjs} +36 -30
  17. package/dist/federation/builder.test.mjs +1 -1
  18. package/dist/federation/compound-fanout.test.mjs +6 -3
  19. package/dist/federation/handler-compound-proof.test.mjs +5 -5
  20. package/dist/federation/handler.test.mjs +22 -7
  21. package/dist/federation/hashlink-media.test.mjs +2 -2
  22. package/dist/federation/idempotency.test.mjs +2 -2
  23. package/dist/federation/inbox-actorless.test.d.mts +2 -0
  24. package/dist/federation/inbox-actorless.test.mjs +487 -0
  25. package/dist/federation/inbox-authentication.test.d.mts +2 -0
  26. package/dist/federation/inbox-authentication.test.mjs +116 -0
  27. package/dist/federation/inbox-report.test.mjs +5 -5
  28. package/dist/federation/inbox-view.test.d.mts +2 -0
  29. package/dist/federation/inbox-view.test.mjs +369 -0
  30. package/dist/federation/inbox.test.mjs +22 -12
  31. package/dist/federation/metrics.test.mjs +1 -1
  32. package/dist/federation/middleware.test.mjs +470 -35
  33. package/dist/federation/mod.cjs +2 -1
  34. package/dist/federation/mod.d.cts +4 -4
  35. package/dist/federation/mod.d.ts +4 -4
  36. package/dist/federation/mod.js +8 -2
  37. package/dist/federation/mq.test.mjs +36 -1
  38. package/dist/federation/outgoing-proof.test.mjs +9 -6
  39. package/dist/federation/portable-actor.test.mjs +3 -3
  40. package/dist/federation/portable-chain.test.mjs +3 -3
  41. package/dist/federation/portable-collection.test.mjs +4 -4
  42. package/dist/federation/portable-delivery.test.mjs +2 -2
  43. package/dist/federation/portable-gateway-keys.test.mjs +13 -7
  44. package/dist/federation/portable-inbox.test.mjs +4 -4
  45. package/dist/federation/portable-parse-uri.test.mjs +1 -1
  46. package/dist/federation/portable-route-activity.test.mjs +2 -2
  47. package/dist/federation/portable.test.mjs +3 -3
  48. package/dist/federation/send.test.mjs +502 -93
  49. package/dist/federation/tasks/enqueue.test.mjs +3 -3
  50. package/dist/federation/tasks/tasks.test.mjs +4 -4
  51. package/dist/federation/temporal.test.mjs +1 -1
  52. package/dist/federation/webfinger.test.mjs +2 -2
  53. package/dist/{federation-BVH7drJA.cjs → federation-D0AZw_aw.cjs} +1415 -236
  54. package/dist/{http-BUJ5SFh6.mjs → http-BoJCMnjY.mjs} +204 -106
  55. package/dist/{http-DlNxxW_5.js → http-ChXUU_3c.js} +319 -123
  56. package/dist/{http-BfW3wyQ4.cjs → http-kv1LZw-r.cjs} +339 -125
  57. package/dist/inbox-BjihTKLo.mjs +312 -0
  58. package/dist/inbox-authentication-DflPYlLL.mjs +171 -0
  59. package/dist/inbox-view-D-skypg-.mjs +385 -0
  60. package/dist/{key-nYGY4QB9.d.cts → key-DbLNT-vA.d.ts} +1 -2
  61. package/dist/{key-DhsBxxZN.d.ts → key-DlHT4R6k.d.cts} +1 -2
  62. package/dist/{kv-cache-D_m3A241.mjs → kv-cache-Cmsyke4d.mjs} +1 -1
  63. package/dist/{kv-cache-7015lbrx.js → kv-cache-DQcte7ES.js} +73 -28
  64. package/dist/{kv-cache-kkBB0vbI.cjs → kv-cache-Ddh5sMxH.cjs} +72 -27
  65. package/dist/ld-CWVpo_NM.d.cts +250 -0
  66. package/dist/{ld-CD2CsPxq.mjs → ld-CdvcOEXQ.mjs} +52 -9
  67. package/dist/ld-Inagww4R.d.ts +250 -0
  68. package/dist/{metrics-D3LHk6Z4.mjs → metrics-CpxvUrBz.mjs} +1 -1
  69. package/dist/{middleware-N9eM16XE.js → middleware-BHb5toqA.js} +1413 -247
  70. package/dist/{middleware-CCDXvXOG.mjs → middleware-BKs2DLUF.mjs} +1 -1
  71. package/dist/{middleware-CzoWvrdZ.mjs → middleware-CoBLrTKX.mjs} +533 -132
  72. package/dist/{mod-B0dGgQsg.d.cts → mod-6TQ4UQZb.d.cts} +1 -1
  73. package/dist/mod-B4kBw1-8.d.ts +154 -0
  74. package/dist/{mod-DkhfZC1u.d.ts → mod-B7j30IQf.d.ts} +2 -9
  75. package/dist/mod-C1nT74IG.d.cts +154 -0
  76. package/dist/{mod-D0bxB8jS.d.ts → mod-CIeBgsOC.d.ts} +1 -1
  77. package/dist/{mod-B5-wdzmJ.d.cts → mod-YvA4Rk8J.d.cts} +2 -9
  78. package/dist/mod.cjs +6 -5
  79. package/dist/mod.d.cts +9 -9
  80. package/dist/mod.d.ts +9 -9
  81. package/dist/mod.js +6 -6
  82. package/dist/{mq-Dm1Wmtm-.mjs → mq-eF96QxUT.mjs} +6 -0
  83. package/dist/nodeinfo/client.test.mjs +337 -19
  84. package/dist/nodeinfo/handler.test.mjs +2 -2
  85. package/dist/nodeinfo/mod.cjs +1 -1
  86. package/dist/nodeinfo/mod.d.cts +1 -1
  87. package/dist/nodeinfo/mod.d.ts +1 -1
  88. package/dist/nodeinfo/mod.js +1 -1
  89. package/dist/otel/exporter.test.mjs +257 -1
  90. package/dist/otel/mod.cjs +36 -30
  91. package/dist/otel/mod.d.cts +13 -0
  92. package/dist/otel/mod.d.ts +13 -0
  93. package/dist/otel/mod.js +36 -30
  94. package/dist/{owner-C5-FVX6t.mjs → owner-DTnVpUKA.mjs} +3 -3
  95. package/dist/{portable-collection-Cony3_LC.mjs → portable-collection-CJeYTL61.mjs} +2 -2
  96. package/dist/{portable-collection-C-Z1SKDy.cjs → portable-collection-DciwBIia.cjs} +1 -1
  97. package/dist/{portable-collection-CC2zNAje.js → portable-collection-TmtpzD4E.js} +1 -1
  98. package/dist/{portable-key-DNc_flH-.mjs → portable-key-DcYok4ki.mjs} +1 -1
  99. package/dist/{proof-Cryb1c70.mjs → proof-GH7Poos_.mjs} +98 -10
  100. package/dist/{send-AF25pWaJ.mjs → send-BgGJh6pv.mjs} +174 -76
  101. package/dist/sig/compound-proof-discovery.test.mjs +1 -1
  102. package/dist/sig/compound-proof-producer.test.mjs +1 -1
  103. package/dist/sig/compound-proof-verification.test.mjs +2 -2
  104. package/dist/sig/compound-proof.test.mjs +2 -2
  105. package/dist/sig/http.test.mjs +553 -2
  106. package/dist/sig/key.test.mjs +1 -1
  107. package/dist/sig/ld.test.mjs +1 -1
  108. package/dist/sig/mitra-receive-interop.test.mjs +4 -4
  109. package/dist/sig/mitra-send-interop.test.mjs +3 -3
  110. package/dist/sig/mod.cjs +2 -2
  111. package/dist/sig/mod.d.cts +3 -3
  112. package/dist/sig/mod.d.ts +3 -3
  113. package/dist/sig/mod.js +2 -2
  114. package/dist/sig/owner.test.mjs +1 -1
  115. package/dist/sig/portable-collection.test.mjs +1 -1
  116. package/dist/sig/portable-interop.test.mjs +3 -3
  117. package/dist/sig/portable-key.test.mjs +5 -5
  118. package/dist/sig/proof.test.mjs +45 -5
  119. package/dist/sig/tootik-request-interop.test.mjs +4 -4
  120. package/dist/sig/tootik-send-interop.test.mjs +3 -3
  121. package/dist/sig/verification.test.mjs +3 -3
  122. package/dist/{temporal-BsAeuUJj.mjs → temporal-gJINzcL4.mjs} +1 -1
  123. package/dist/testing/kv.test.mjs +1 -1
  124. package/dist/testing/mod.d.mts +288 -2
  125. package/dist/testing/mod.mjs +2 -2
  126. package/dist/{testing-t8xDeJZ5.mjs → testing-Dbr21vZk.mjs} +1 -1
  127. package/dist/{types-_S2_MeLF.js → types-DUjZb4i6.js} +112 -63
  128. package/dist/{types-poIao40f.cjs → types-hKCCjii0.cjs} +111 -62
  129. package/dist/utils/docloader.test.mjs +344 -36
  130. package/dist/utils/kv-cache.test.mjs +1 -1
  131. package/dist/utils/mod.cjs +1 -1
  132. package/dist/utils/mod.d.cts +1 -1
  133. package/dist/utils/mod.d.ts +1 -1
  134. package/dist/utils/mod.js +1 -1
  135. package/package.json +7 -7
  136. package/dist/docloader-B-0nqOB1.mjs +0 -91
  137. package/dist/inbox-CHvMaIFX.mjs +0 -188
  138. package/dist/mod-BRpER-B0.d.ts +0 -320
  139. package/dist/mod-xAfaLS4T.d.cts +0 -320
  140. package/dist/owner-BF0L9cMt.d.cts +0 -78
  141. package/dist/owner-BSzr5-I8.d.ts +0 -78
@@ -3,7 +3,7 @@ import { URLPattern } from "urlpattern-polyfill";
3
3
  import { getLogger } from "@logtape/logtape";
4
4
  import { Activity, CryptographicKey, DataIntegrityProof, Multikey, Object as Object$1, PUBLIC_COLLECTION, getTypeId, isActor } from "@fedify/vocab";
5
5
  import { SpanKind, SpanStatusCode, metrics, trace } from "@opentelemetry/api";
6
- import { FetchError, UrlError, canonicalizePortableUri, encodeMultibase, formatIri, fromCompatibleEf61Id, getDocumentLoader, getFe34Origin, isGatewayUrl, parseDidKeyVerificationMethod, parseGatewayUrl, parseIri, preloadedContexts, toCompatibleEf61Id } from "@fedify/vocab-runtime";
6
+ import { FetchError, UrlError, canonicalizePortableUri, encodeMultibase, formatIri, fromCompatibleEf61Id, getDocumentLoader, getFe34Origin, isGatewayUrl, isTransientFetchError, parseDidKeyVerificationMethod, parseGatewayUrl, parseIri, preloadedContexts, toCompatibleEf61Id } from "@fedify/vocab-runtime";
7
7
  import { getPortableGatewayCandidates, isCompatibleEf61Iri } from "@fedify/vocab-runtime/internal/portable-dereference";
8
8
  import { encodeHex } from "byte-encodings/hex";
9
9
  import jsonld from "@fedify/vocab-runtime/jsonld";
@@ -15,7 +15,7 @@ import serialize from "json-canon";
15
15
  import { ATTR_HTTP_REQUEST_HEADER, ATTR_HTTP_REQUEST_METHOD, ATTR_URL_FULL } from "@opentelemetry/semantic-conventions";
16
16
  //#region deno.json
17
17
  var name = "@fedify/fedify";
18
- var version = "2.5.0-dev.2446+2014cb03";
18
+ var version = "2.5.0-dev.2492+4648c04e";
19
19
  //#endregion
20
20
  //#region src/sig/portable-key-id.ts
21
21
  const COMPATIBLE_KEY_ID_PATH_PATTERN = /^\/\.well-known\/apgateway\/did(?::|%3A)/i;
@@ -1616,6 +1616,7 @@ async function fetchActorDocumentInternal(actorId, options, propagateContextFetc
1616
1616
  actorId: actorId.href,
1617
1617
  error
1618
1618
  });
1619
+ if (propagateContextFetchErrors) throw new KeyContextFetchError(error);
1619
1620
  return null;
1620
1621
  }
1621
1622
  let object;
@@ -2562,8 +2563,26 @@ function createMemoizedDocumentLoader(documentLoader) {
2562
2563
  return cloneRemoteDocument(await remoteDocument);
2563
2564
  };
2564
2565
  }
2566
+ const effectiveContextLoaderPolicies = /* @__PURE__ */ new WeakMap();
2567
+ /**
2568
+ * Marks a context loader as the *effective* loader of a policy, i.e., one
2569
+ * that already applies what {@link wrapContextLoaderForJsonLd} (`"standard"`)
2570
+ * or {@link getNormalizationContextLoader} (`"normalization"`) would add.
2571
+ * Those functions return a marked loader as is instead of wrapping it again,
2572
+ * so that a caller can observe every response a parse actually uses,
2573
+ * including Fedify's own baseline contexts.
2574
+ * @param loader The effective context loader.
2575
+ * @param policy The policy the loader already applies.
2576
+ * @returns The given loader.
2577
+ * @internal
2578
+ */
2579
+ function markEffectiveContextLoader(loader, policy) {
2580
+ effectiveContextLoaderPolicies.set(loader, policy);
2581
+ return loader;
2582
+ }
2565
2583
  /** @internal */
2566
2584
  function wrapContextLoaderForJsonLd(contextLoader) {
2585
+ if (contextLoader != null && effectiveContextLoaderPolicies.has(contextLoader)) return contextLoader;
2567
2586
  const loader = contextLoader ?? builtInContextLoader;
2568
2587
  return async (url, options) => {
2569
2588
  try {
@@ -2577,6 +2596,7 @@ function wrapContextLoaderForJsonLd(contextLoader) {
2577
2596
  }
2578
2597
  /** @internal */
2579
2598
  function getNormalizationContextLoader(contextLoader) {
2599
+ if (contextLoader != null && effectiveContextLoaderPolicies.get(contextLoader) === "normalization") return contextLoader;
2580
2600
  const loader = wrapContextLoaderForJsonLd(contextLoader);
2581
2601
  return createMemoizedDocumentLoader(async (url, options) => {
2582
2602
  if (URL.canParse(url)) {
@@ -3029,10 +3049,17 @@ function getLdSignatureObject(jsonLd) {
3029
3049
  * @returns `true` if the document is authentic; `false` otherwise.
3030
3050
  */
3031
3051
  async function verifyJsonLd(jsonLd, options = {}) {
3032
- return await verifyJsonLdInternal(jsonLd, options, true);
3052
+ return (await verifyJsonLdInternal(jsonLd, options, true)).verified;
3033
3053
  }
3034
- /** @internal */
3035
- async function verifyCompactJsonLd(jsonLd, options = {}) {
3054
+ /**
3055
+ * Same as {@link verifyCompactJsonLd}, but also returns the key that verified
3056
+ * the signature, whose `owner` {@link fetchKey} has confirmed.
3057
+ * @param jsonLd The compacted JSON-LD document to verify.
3058
+ * @param options Options for verifying the JSON-LD document.
3059
+ * @returns The result.
3060
+ * @internal
3061
+ */
3062
+ async function verifyCompactJsonLdDetailed(jsonLd, options = {}) {
3036
3063
  return await verifyJsonLdInternal(jsonLd, options, false);
3037
3064
  }
3038
3065
  async function verifyJsonLdInternal(jsonLd, options, compact) {
@@ -3042,7 +3069,7 @@ async function verifyJsonLdInternal(jsonLd, options, compact) {
3042
3069
  }, "linkedData", (observation) => verifyJsonLdInternal(jsonLd, {
3043
3070
  ...options,
3044
3071
  [verificationObservation]: observation
3045
- }, compact), (value) => value);
3072
+ }, compact), (value) => value.verified);
3046
3073
  const observation = options[verificationObservation];
3047
3074
  return await (options.tracerProvider ?? trace.getTracerProvider()).getTracer(name, version).startActiveSpan("ld_signatures.verify", async (span) => {
3048
3075
  const start = performance.now();
@@ -3074,12 +3101,18 @@ async function verifyJsonLdInternal(jsonLd, options, compact) {
3074
3101
  const key = await verifySignature(compacted, verificationOptions);
3075
3102
  if (key == null) {
3076
3103
  if (observation?.attempt != null && !hasLdSignatureProperty(jsonLd)) observation.attempt.reason = { type: "noSignature" };
3077
- return false;
3104
+ return {
3105
+ verified: false,
3106
+ key: null
3107
+ };
3078
3108
  }
3079
3109
  if (key.ownerId == null) {
3080
3110
  if (observation?.attempt != null) observation.attempt.reason = { type: "missingOwner" };
3081
3111
  logger$4.debug("Key {keyId} has no owner.", { keyId: key.id?.href });
3082
- return false;
3112
+ return {
3113
+ verified: false,
3114
+ key: null
3115
+ };
3083
3116
  }
3084
3117
  attributions.delete(key.ownerId.href);
3085
3118
  if (attributions.size > 0) {
@@ -3088,10 +3121,16 @@ async function verifyJsonLdInternal(jsonLd, options, compact) {
3088
3121
  attributionIds: [...attributions].map((id) => new URL(id))
3089
3122
  };
3090
3123
  logger$4.debug("Some attributions are not authenticated by the Linked Data Signatures: {attributions}.", { attributions: [...attributions] });
3091
- return false;
3124
+ return {
3125
+ verified: false,
3126
+ key: null
3127
+ };
3092
3128
  }
3093
3129
  verified = true;
3094
- return true;
3130
+ return {
3131
+ verified: true,
3132
+ key
3133
+ };
3095
3134
  } catch (error) {
3096
3135
  threw = true;
3097
3136
  span.setStatus({
@@ -4172,6 +4211,12 @@ async function verifyProofInternal(jsonLd, proof, options, messageDigestCache, r
4172
4211
  keyId: proof.verificationMethodId.href,
4173
4212
  error
4174
4213
  });
4214
+ const check = options[verificationObservation]?.check;
4215
+ if (check != null && check.reason == null) check.reason = {
4216
+ type: "keyFetchError",
4217
+ keyId: new URL(proof.verificationMethodId.href),
4218
+ result: { error: error instanceof Error ? error : new Error(String(error)) }
4219
+ };
4175
4220
  return null;
4176
4221
  }
4177
4222
  const publicKey = fetchedKey.key;
@@ -4700,6 +4745,39 @@ async function verifyPreparedPortableObjectProof(jsonLd, prepared, options, mess
4700
4745
  * @since 0.10.0
4701
4746
  */
4702
4747
  async function verifyObject(cls, jsonLd, options = {}) {
4748
+ const result = await verifyObjectDetailed(cls, jsonLd, options);
4749
+ return result.status === "verified" ? result.object : null;
4750
+ }
4751
+ /**
4752
+ * Same as {@link verifyObject}, but also tells who signed the verified proofs,
4753
+ * and whether a failure was caused by a key that could not be obtained for
4754
+ * a retryable reason.
4755
+ * @param cls The class of the object to verify.
4756
+ * @param jsonLd The JSON-LD object to verify.
4757
+ * @param options Additional options.
4758
+ * @returns The result.
4759
+ * @throws {TypeError} If the object is invalid or unsupported.
4760
+ * @internal
4761
+ */
4762
+ async function verifyObjectDetailed(cls, jsonLd, options = {}) {
4763
+ const classify = (result, checks) => {
4764
+ if (result != null) return {
4765
+ status: "verified",
4766
+ object: result.object,
4767
+ signers: result.signers
4768
+ };
4769
+ for (const check of checks) {
4770
+ if (check.status === "error" && isTransientFetchError(check.error)) return {
4771
+ status: "unavailable",
4772
+ error: check.error
4773
+ };
4774
+ if (check.status === "rejected" && check.reason.type === "keyFetchError" && isRetryableKeyFetchError(check.reason.result)) return {
4775
+ status: "unavailable",
4776
+ error: "error" in check.reason.result ? check.reason.result.error : /* @__PURE__ */ new Error(`Fetching key ${check.reason.keyId.href} failed with ${check.reason.result.status}.`)
4777
+ };
4778
+ }
4779
+ return { status: "invalid" };
4780
+ };
4703
4781
  if (options[verificationObservation]?.attempt == null) {
4704
4782
  const observedOptions = {
4705
4783
  ...options,
@@ -4711,16 +4789,16 @@ async function verifyObject(cls, jsonLd, options = {}) {
4711
4789
  return await (options.tracerProvider ?? trace.getTracerProvider()).getTracer(name, version).startActiveSpan("object_integrity_proofs.verify_object", async (span) => {
4712
4790
  try {
4713
4791
  return await observeAttempt(observedOptions, "objectIntegrity", async (observation) => {
4714
- const object = await verifyObject(cls, jsonLd, {
4792
+ const result = await verifyObjectCore(cls, jsonLd, {
4715
4793
  ...options,
4716
4794
  [verificationObservation]: observation
4717
4795
  });
4718
- if (object == null) {
4796
+ if (result == null) {
4719
4797
  span.setStatus({ code: SpanStatusCode.ERROR });
4720
4798
  span.setAttribute("activitypub.verification.failure_reason", observation.attempt?.reason?.type ?? "signatureVerificationFailed");
4721
4799
  }
4722
- return object;
4723
- }, (value) => value != null);
4800
+ return classify(result, observation.attempt?.checks ?? []);
4801
+ }, (value) => value.status === "verified");
4724
4802
  } catch (error) {
4725
4803
  span.setStatus({
4726
4804
  code: SpanStatusCode.ERROR,
@@ -4732,6 +4810,15 @@ async function verifyObject(cls, jsonLd, options = {}) {
4732
4810
  }
4733
4811
  });
4734
4812
  }
4813
+ const checks = options[verificationObservation].attempt.checks;
4814
+ const before = checks.length;
4815
+ return classify(await verifyObjectCore(cls, jsonLd, options), checks.slice(before));
4816
+ }
4817
+ function isRetryableKeyFetchError(result) {
4818
+ if ("error" in result) return isTransientFetchError(result.error);
4819
+ return result.status >= 500 || result.status === 408 || result.status === 429;
4820
+ }
4821
+ async function verifyObjectCore(cls, jsonLd, options) {
4735
4822
  const observation = options[verificationObservation];
4736
4823
  const logger = getLogger([
4737
4824
  "fedify",
@@ -4744,6 +4831,7 @@ async function verifyObject(cls, jsonLd, options = {}) {
4744
4831
  const defaultDocumentLoader = getDocumentLoader();
4745
4832
  const proofContextLoader = options.contextLoader ?? defaultDocumentLoader;
4746
4833
  const attributions = new Set(object.attributionIds.map((uri) => uri.href));
4834
+ const signers = [];
4747
4835
  if (object instanceof Activity) for (const uri of object.actorIds) attributions.add(uri.href);
4748
4836
  const rawProofValues = isJsonLdNode(jsonLd) ? await getRawProofValues(jsonLd, proofContextLoader) : [];
4749
4837
  const rawProofCandidates = isJsonLdNode(jsonLd) ? await parseRawProofCandidates(jsonLd, rawProofValues, options, proofContextLoader) : [];
@@ -4802,6 +4890,11 @@ async function verifyObject(cls, jsonLd, options = {}) {
4802
4890
  continue;
4803
4891
  }
4804
4892
  deleteAuthenticatedAttribution(attributions, key.controllerId, proof.verificationMethodId);
4893
+ const signer = getAuthenticatedController(key.controllerId, proof.verificationMethodId);
4894
+ if (signer != null && !signers.some((s) => s.kind === signer.kind && s.id.href === signer.id.href && s.keyId.href === proof.verificationMethodId.href)) signers.push({
4895
+ ...signer,
4896
+ keyId: proof.verificationMethodId
4897
+ });
4805
4898
  }
4806
4899
  if (acceptedPortableProofs < loadedPortableProofs) {
4807
4900
  logger.debug("Some portable proof references could not be dereferenced.");
@@ -4815,7 +4908,41 @@ async function verifyObject(cls, jsonLd, options = {}) {
4815
4908
  logger.debug("Some attributions are not authenticated by the proofs: {attributions}.", { attributions: [...attributions] });
4816
4909
  return null;
4817
4910
  }
4818
- return object;
4911
+ return {
4912
+ object,
4913
+ signers
4914
+ };
4915
+ }
4916
+ /**
4917
+ * Tells whom a key that verified an Object Integrity Proof authenticates.
4918
+ * @param key The key, i.e., the proof's verification method.
4919
+ * @returns The signer, or `null` if the key authenticates nobody.
4920
+ * @internal
4921
+ */
4922
+ function getProofSigner(key) {
4923
+ if (key.id == null || key.controllerId == null) return null;
4924
+ const signer = getAuthenticatedController(key.controllerId, key.id);
4925
+ return signer == null ? null : {
4926
+ ...signer,
4927
+ keyId: key.id
4928
+ };
4929
+ }
4930
+ /**
4931
+ * Tells whom a verified proof authenticates: the key's controller if it is an
4932
+ * ordinary actor, or the DID of a portable controller if the verification
4933
+ * method belongs to the same DID.
4934
+ */
4935
+ function getAuthenticatedController(controllerId, verificationMethodId) {
4936
+ if (!hasCryptographicOrigin(controllerId.href) && !isPortableId(controllerId)) return {
4937
+ kind: "actor",
4938
+ id: controllerId
4939
+ };
4940
+ const did = getPortableDid(controllerId);
4941
+ if (did == null || !hasCryptographicOrigin(verificationMethodId.href) || getPortableDid(verificationMethodId) !== did || !URL.canParse(did)) return null;
4942
+ return {
4943
+ kind: "portableAuthority",
4944
+ id: new URL(did)
4945
+ };
4819
4946
  }
4820
4947
  function deleteAuthenticatedAttribution(attributions, controllerId, verificationMethodId) {
4821
4948
  if (!hasCryptographicOrigin(controllerId.href) && !isPortableId(controllerId)) {
@@ -6468,10 +6595,10 @@ async function createRedirectRequest(request, location, body, validateRedirect)
6468
6595
  cache: request.cache
6469
6596
  });
6470
6597
  }
6471
- async function fetchDoubleKnockRequest(request, signedRequest, signal) {
6598
+ async function fetchDoubleKnockRequest(session, request, signedRequest, signal) {
6472
6599
  const maxAttempts = request.method === "GET" || request.method === "HEAD" ? 2 : 1;
6473
6600
  for (let attempt = 1;; attempt++) try {
6474
- return await fetch(signedRequest, {
6601
+ return await session.fetch(signedRequest, {
6475
6602
  redirect: "manual",
6476
6603
  signal
6477
6604
  });
@@ -6522,125 +6649,194 @@ function getAbortReason(signal) {
6522
6649
  return signal.reason ?? new DOMException("The operation was aborted.", "AbortError");
6523
6650
  }
6524
6651
  /**
6525
- * Performs a double-knock request to the given URL. For the details of
6526
- * double-knocking, see
6527
- * <https://swicg.github.io/activitypub-http-signature/#how-to-upgrade-supported-versions>.
6652
+ * Performs a double-knock request like {@link doubleKnock}, opening a session
6653
+ * for every hop with the given function, and passes the final response to
6654
+ * the given function. The final session is closed after `consume` settles,
6655
+ * so `consume` must read or cancel the response body before it returns.
6656
+ * This is not exported from any entry point.
6528
6657
  * @param request The request to send.
6529
6658
  * @param identity The identity to use for signing the request.
6530
6659
  * @param options The options for double-knock requests.
6531
- * @returns The response to the request.
6532
- * @since 1.6.0
6660
+ * @param openHop The function to open a session for each hop. The session of
6661
+ * a hop is closed before the next hop is opened.
6662
+ * @param consume The function to consume the final response.
6663
+ * @returns The value returned by `consume`.
6664
+ */
6665
+ async function doubleKnockScoped(request, identity, options, openHop, consume) {
6666
+ const { response, session } = await doubleKnockCore(request, identity, options, openHop);
6667
+ try {
6668
+ return await consume(response);
6669
+ } catch (error) {
6670
+ getLogger([
6671
+ "fedify",
6672
+ "sig",
6673
+ "http"
6674
+ ]).debug("Failed to consume the response from {url}; closing the session: {error}", {
6675
+ url: request.url,
6676
+ error
6677
+ });
6678
+ throw error;
6679
+ } finally {
6680
+ await session.close();
6681
+ }
6682
+ }
6683
+ function isRedirectResponse(response) {
6684
+ return response.status >= 300 && response.status < 400 && response.headers.has("Location");
6685
+ }
6686
+ /**
6687
+ * Starts cancelling the body of a response that is not passed to the caller,
6688
+ * without waiting for it to settle. The cancellation of a branch of a tee
6689
+ * (e.g., a body cloned by a fetch mock) settles only after the other branch
6690
+ * is cancelled too, per the Streams standard, so awaiting it could block
6691
+ * forever. Failures are only logged.
6533
6692
  */
6534
- async function doubleKnock(request, identity, options = {}) {
6535
- return await doubleKnockInternal(request, identity, options);
6693
+ function cancelResponseBody(response, url) {
6694
+ response.body?.cancel().catch((error) => {
6695
+ getLogger([
6696
+ "fedify",
6697
+ "sig",
6698
+ "http"
6699
+ ]).debug("Failed to cancel the response body from {url}: {error}", {
6700
+ url,
6701
+ error
6702
+ });
6703
+ });
6536
6704
  }
6537
- async function doubleKnockInternal(request, identity, options, redirected = 0, visited = /* @__PURE__ */ new Set()) {
6705
+ async function doubleKnockCore(request, identity, options, openHop) {
6538
6706
  const { specDeterminer, log, tracerProvider, signal } = options;
6539
6707
  signal?.throwIfAborted();
6540
6708
  const maximumRedirection = options.maxRedirection ?? DEFAULT_MAX_REDIRECTION;
6541
- visited.add(request.url);
6542
- const origin = new URL(request.url).origin;
6543
- const firstTrySpec = specDeterminer == null ? "rfc9421" : await specDeterminer.determineSpec(origin);
6544
- signal?.throwIfAborted();
6709
+ const visited = /* @__PURE__ */ new Set();
6545
6710
  const body = options.body !== void 0 ? options.body : request.method !== "GET" && request.method !== "HEAD" ? await request.clone().arrayBuffer() : null;
6546
- let signedRequest = await signRequest(request, identity.privateKey, identity.keyId, {
6547
- spec: firstTrySpec,
6548
- tracerProvider,
6549
- body
6550
- });
6551
- signal?.throwIfAborted();
6552
- log?.(signedRequest);
6553
- let response = await fetchDoubleKnockRequest(request, signedRequest, signal);
6554
- if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
6555
- if (redirected >= maximumRedirection) throw new FetchError(request.url, `Too many redirections (${redirected + 1})`);
6556
- const redirectRequest = await createRedirectRequest(request, response.headers.get("Location"), body, options.validateRedirect);
6557
- if (visited.has(redirectRequest.url)) throw new FetchError(request.url, `Redirect loop detected: ${redirectRequest.url}`);
6558
- return doubleKnockInternal(redirectRequest, identity, {
6559
- ...options,
6560
- body
6561
- }, redirected + 1, visited);
6562
- } else if (response.status === 400 || response.status === 401 || response.status > 401) {
6563
- const logger = getLogger([
6564
- "fedify",
6565
- "sig",
6566
- "http"
6567
- ]);
6568
- const acceptSigHeader = response.headers.get("Accept-Signature");
6569
- if (acceptSigHeader != null) {
6570
- const entries = validateAcceptSignature(parseAcceptSignature(acceptSigHeader));
6571
- const localKeyId = identity.keyId.href;
6572
- const localAlg = "rsa-v1_5-sha256";
6573
- let fulfilled = false;
6574
- let challengeRequest;
6575
- for (const entry of entries) {
6576
- signal?.throwIfAborted();
6577
- const rfc9421 = fulfillAcceptSignature(entry, localKeyId, localAlg);
6578
- if (rfc9421 == null) continue;
6579
- logger.debug("Received Accept-Signature challenge; accumulating label {label} and components {components}.", {
6580
- label: rfc9421.label,
6581
- components: rfc9421.components
6582
- });
6583
- try {
6584
- challengeRequest = await signRequest(challengeRequest ?? request, identity.privateKey, identity.keyId, {
6585
- spec: "rfc9421",
6586
- tracerProvider,
6587
- body,
6588
- rfc9421
6711
+ let session = await openHop(request.url);
6712
+ try {
6713
+ for (let redirected = 0;; redirected++) {
6714
+ visited.add(request.url);
6715
+ const origin = new URL(request.url).origin;
6716
+ const firstTrySpec = specDeterminer == null ? "rfc9421" : await specDeterminer.determineSpec(origin);
6717
+ signal?.throwIfAborted();
6718
+ let signedRequest = await signRequest(request, identity.privateKey, identity.keyId, {
6719
+ spec: firstTrySpec,
6720
+ tracerProvider,
6721
+ body
6722
+ });
6723
+ signal?.throwIfAborted();
6724
+ log?.(signedRequest);
6725
+ let response = await fetchDoubleKnockRequest(session, request, signedRequest, signal);
6726
+ const firstRedirected = isRedirectResponse(response);
6727
+ if (!firstRedirected && (response.status === 400 || response.status === 401 || response.status > 401)) {
6728
+ const logger = getLogger([
6729
+ "fedify",
6730
+ "sig",
6731
+ "http"
6732
+ ]);
6733
+ const acceptSigHeader = response.headers.get("Accept-Signature");
6734
+ if (acceptSigHeader != null) {
6735
+ const entries = validateAcceptSignature(parseAcceptSignature(acceptSigHeader));
6736
+ const localKeyId = identity.keyId.href;
6737
+ const localAlg = "rsa-v1_5-sha256";
6738
+ let fulfilled = false;
6739
+ let challengeRequest;
6740
+ for (const entry of entries) {
6741
+ signal?.throwIfAborted();
6742
+ const rfc9421 = fulfillAcceptSignature(entry, localKeyId, localAlg);
6743
+ if (rfc9421 == null) continue;
6744
+ logger.debug("Received Accept-Signature challenge; accumulating label {label} and components {components}.", {
6745
+ label: rfc9421.label,
6746
+ components: rfc9421.components
6747
+ });
6748
+ try {
6749
+ challengeRequest = await signRequest(challengeRequest ?? request, identity.privateKey, identity.keyId, {
6750
+ spec: "rfc9421",
6751
+ tracerProvider,
6752
+ body,
6753
+ rfc9421
6754
+ });
6755
+ fulfilled = true;
6756
+ } catch (error) {
6757
+ logger.debug("Failed to fulfill Accept-Signature challenge entry {label}: {error}", {
6758
+ label: entry.label,
6759
+ error
6760
+ });
6761
+ }
6762
+ }
6763
+ if (fulfilled && challengeRequest != null) {
6764
+ signal?.throwIfAborted();
6765
+ cancelResponseBody(response, request.url);
6766
+ signedRequest = challengeRequest;
6767
+ log?.(signedRequest);
6768
+ response = await session.fetch(signedRequest, {
6769
+ redirect: "manual",
6770
+ signal
6771
+ });
6772
+ }
6773
+ if (fulfilled && !isRedirectResponse(response)) {
6774
+ if (response.status < 300) {
6775
+ await specDeterminer?.rememberSpec(origin, "rfc9421");
6776
+ return {
6777
+ response,
6778
+ session
6779
+ };
6780
+ }
6781
+ if (response.status !== 400 && response.status !== 401) return {
6782
+ response,
6783
+ session
6784
+ };
6785
+ }
6786
+ }
6787
+ if (!isRedirectResponse(response)) {
6788
+ const spec = firstTrySpec === "draft-cavage-http-signatures-12" ? "rfc9421" : "draft-cavage-http-signatures-12";
6789
+ getLogger([
6790
+ "fedify",
6791
+ "sig",
6792
+ "http"
6793
+ ]).debug("Failed to verify with the spec {spec} ({status} {statusText}); retrying with spec {secondSpec}... (double-knocking)", {
6794
+ spec: firstTrySpec,
6795
+ secondSpec: spec,
6796
+ status: response.status,
6797
+ statusText: response.statusText
6589
6798
  });
6590
- fulfilled = true;
6591
- } catch (error) {
6592
- logger.debug("Failed to fulfill Accept-Signature challenge entry {label}: {error}", {
6593
- label: entry.label,
6594
- error
6799
+ cancelResponseBody(response, request.url);
6800
+ signal?.throwIfAborted();
6801
+ signedRequest = await signRequest(request, identity.privateKey, identity.keyId, {
6802
+ spec,
6803
+ tracerProvider,
6804
+ body
6595
6805
  });
6806
+ signal?.throwIfAborted();
6807
+ log?.(signedRequest);
6808
+ response = await fetchDoubleKnockRequest(session, request, signedRequest, signal);
6809
+ if (!isRedirectResponse(response) && response.status !== 400 && response.status !== 401) await specDeterminer?.rememberSpec(origin, spec);
6596
6810
  }
6597
- }
6598
- if (fulfilled && challengeRequest != null) {
6599
- signal?.throwIfAborted();
6600
- signedRequest = challengeRequest;
6601
- log?.(signedRequest);
6602
- response = await fetch(signedRequest, {
6603
- redirect: "manual",
6604
- signal
6605
- });
6606
- if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) return doubleKnock(await createRedirectRequest(request, response.headers.get("Location"), body, options.validateRedirect), identity, {
6607
- ...options,
6608
- body
6609
- });
6610
- }
6611
- if (fulfilled && response.status < 300) {
6612
- await specDeterminer?.rememberSpec(origin, "rfc9421");
6613
- return response;
6614
- }
6615
- if (fulfilled && response.status !== 400 && response.status !== 401) return response;
6616
- }
6617
- const spec = firstTrySpec === "draft-cavage-http-signatures-12" ? "rfc9421" : "draft-cavage-http-signatures-12";
6618
- logger.debug("Failed to verify with the spec {spec} ({status} {statusText}); retrying with spec {secondSpec}... (double-knocking)", {
6619
- spec: firstTrySpec,
6620
- secondSpec: spec,
6621
- status: response.status,
6622
- statusText: response.statusText
6623
- });
6624
- signal?.throwIfAborted();
6625
- signedRequest = await signRequest(request, identity.privateKey, identity.keyId, {
6626
- spec,
6627
- tracerProvider,
6628
- body
6629
- });
6630
- signal?.throwIfAborted();
6631
- log?.(signedRequest);
6632
- response = await fetchDoubleKnockRequest(request, signedRequest, signal);
6633
- if (response.status >= 300 && response.status < 400 && response.headers.has("Location")) {
6811
+ } else if (!firstRedirected) await specDeterminer?.rememberSpec(origin, firstTrySpec);
6812
+ if (!isRedirectResponse(response)) return {
6813
+ response,
6814
+ session
6815
+ };
6816
+ cancelResponseBody(response, request.url);
6817
+ await session.close();
6634
6818
  if (redirected >= maximumRedirection) throw new FetchError(request.url, `Too many redirections (${redirected + 1})`);
6635
- const redirectRequest = await createRedirectRequest(request, response.headers.get("Location"), body, options.validateRedirect);
6819
+ const location = response.headers.get("Location");
6820
+ const redirectRequest = await createRedirectRequest(request, location, body, options.validateRedirect);
6636
6821
  if (visited.has(redirectRequest.url)) throw new FetchError(request.url, `Redirect loop detected: ${redirectRequest.url}`);
6637
- return doubleKnockInternal(redirectRequest, identity, {
6638
- ...options,
6639
- body
6640
- }, redirected + 1, visited);
6641
- } else if (response.status !== 400 && response.status !== 401) await specDeterminer?.rememberSpec(origin, spec);
6642
- } else await specDeterminer?.rememberSpec(origin, firstTrySpec);
6643
- return response;
6822
+ request = redirectRequest;
6823
+ session = await openHop(request.url);
6824
+ }
6825
+ } catch (error) {
6826
+ try {
6827
+ await session.close();
6828
+ } catch (closeError) {
6829
+ getLogger([
6830
+ "fedify",
6831
+ "sig",
6832
+ "http"
6833
+ ]).debug("Failed to close the session for {url}: {error}", {
6834
+ url: request.url,
6835
+ error: closeError
6836
+ });
6837
+ }
6838
+ throw error;
6839
+ }
6644
6840
  }
6645
6841
  /**
6646
6842
  * Performs a timing-safe equality comparison between two `Uint8Array` values.
@@ -6670,4 +6866,4 @@ function timingSafeEqual(a, b) {
6670
6866
  return result === 0;
6671
6867
  }
6672
6868
  //#endregion
6673
- export { getFederationMetrics as $, detachSignature as A, isPortableId as At, wrapContextLoaderForJsonLd as B, verifyProof as C, getAuthenticationOrigin as Ct, attachSignature as D, getRawCanonicalPortableId as Dt, assertSafeJsonLd as E, getPortableDid as Et, isInvalidUrlTypeError as F, name as Ft, generateCryptoKeyPair as G, fetchActorDocument as H, signJsonLd as I, version as It, verifyKeyOwnership as J, importJwk as K, verifyCompactJsonLd as L, hasSignature as M, isPortableUri as Mt, hasSignatureLike as N, isSameObjectId as Nt, compactJsonLd as O, hasPortableActor as Ot, isClearlyMalformedContextReference as P, isSamePublicKey as Pt, getDurationMs as Q, verifyJsonLd as R, verifyPreparedPortableObjectProofPolicy as S, verificationObservation as St, InvalidContextReferenceError as T, getGatewayKeyBase as Tt, fetchKey as U, exportJwk as V, fetchKeyDetailed as W, getVerifiedKeyOwnerEvidence as X, MAX_PORTABLE_KEY_TTL as Y, parseKeyOwnerEvidence as Z, verifyMapLocalProof as _, fulfillAcceptSignature as _t, signRequest as a, recordCollectionPageItems as at, verifyPortableObjectProofPolicy as b, observeAttempt as bt, verifyRequestDetailed as c, recordDocumentCache as ct, InvalidPortableObjectIdError as d, recordOutboxActivity as dt, getRemoteHost as et, classifyFep2277CoreType as f, recordOutboxEnqueue as ft, signObject as g, formatAcceptSignature as gt, preparePortableObjectProofPolicy as h, preloadedOnlyDocumentLoader as ht, selectRequestSignature as i, recordCollectionDispatchDuration as it, getNormalizationContextLoader as j, isPortableKeyId as jt, createSignature as k, isCompatibleKeyId as kt, fetchPortableGatewayKey as l, recordFanoutRecipients as lt, hasProofLike as m, registerQueueDepthGauge as mt, listRequestSignatures as n, isAbortError$1 as nt, validateMaxSignatures as o, recordCollectionRequest as ot, createProof as p, recordWebFingerHandle as pt, validateCryptoKey as q, parseRfc9421SignatureInput as r, recordCircuitBreakerStateChange as rt, verifyRequest as s, recordCollectionTotalItems as st, doubleKnock as t, instrumentDocumentLoader as tt, resolvePortableActorKey as u, recordInboxActivity as ut, verifyObject as v, parseAcceptSignature as vt, normalizeOutgoingActivityJsonLd as w, getCanonicalPortableId as wt, verifyPortableObjectProofWithRoot as x, snapshotKey as xt, verifyPortableObjectProof as y, validateAcceptSignature as yt, verifySignature as z };
6869
+ export { getVerifiedKeyOwnerEvidence as $, compactJsonLd as A, getRawCanonicalPortableId as At, verifyCompactJsonLdDetailed as B, verifyPortableObjectProofWithRoot as C, observeAttempt as Ct, InvalidContextReferenceError as D, getCanonicalPortableId as Dt, normalizeOutgoingActivityJsonLd as E, getAuthenticationOrigin as Et, hasSignatureLike as F, isPortableUri as Ft, fetchActorDocument as G, verifySignature as H, isClearlyMalformedContextReference as I, isSameObjectId as It, generateCryptoKeyPair as J, fetchKey as K, isInvalidUrlTypeError as L, isSamePublicKey as Lt, detachSignature as M, isCompatibleKeyId as Mt, getNormalizationContextLoader as N, isPortableId as Nt, assertSafeJsonLd as O, getGatewayKeyBase as Ot, hasSignature as P, isPortableKeyId as Pt, MAX_PORTABLE_KEY_TTL as Q, markEffectiveContextLoader as R, name as Rt, verifyPortableObjectProofPolicy as S, validateAcceptSignature as St, verifyProof as T, verificationObservation as Tt, wrapContextLoaderForJsonLd as U, verifyJsonLd as V, exportJwk as W, validateCryptoKey as X, importJwk as Y, verifyKeyOwnership as Z, signObject as _, registerQueueDepthGauge as _t, signRequest as a, isAbortError$1 as at, verifyObjectDetailed as b, fulfillAcceptSignature as bt, verifyRequestDetailed as c, recordCollectionPageItems as ct, InvalidPortableObjectIdError as d, recordDocumentCache as dt, parseKeyOwnerEvidence as et, classifyFep2277CoreType as f, recordFanoutRecipients as ft, preparePortableObjectProofPolicy as g, recordWebFingerHandle as gt, hasProofLike as h, recordOutboxEnqueue as ht, selectRequestSignature as i, instrumentDocumentLoader as it, createSignature as j, hasPortableActor as jt, attachSignature as k, getPortableDid as kt, fetchPortableGatewayKey as l, recordCollectionRequest as lt, getProofSigner as m, recordOutboxActivity as mt, listRequestSignatures as n, getFederationMetrics as nt, validateMaxSignatures as o, recordCircuitBreakerStateChange as ot, createProof as p, recordInboxActivity as pt, fetchKeyDetailed as q, parseRfc9421SignatureInput as r, getRemoteHost as rt, verifyRequest as s, recordCollectionDispatchDuration as st, doubleKnockScoped as t, getDurationMs as tt, resolvePortableActorKey as u, recordCollectionTotalItems as ut, verifyMapLocalProof as v, preloadedOnlyDocumentLoader as vt, verifyPreparedPortableObjectProofPolicy as w, snapshotKey as wt, verifyPortableObjectProof as x, parseAcceptSignature as xt, verifyObject as y, formatAcceptSignature as yt, signJsonLd as z, version as zt };