@fedify/fedify 2.4.0-dev.1964 → 2.4.0-dev.1995

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 (94) hide show
  1. package/dist/{builder-B4zpKIFU.mjs → builder-D0DxgOV5.mjs} +2 -2
  2. package/dist/{client-CtxvmazV.mjs → client-DpnpgjyX.mjs} +78 -4
  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-CJnWc6c-.mjs +495 -0
  7. package/dist/{context-COLwHogJ.d.cts → context-BeDnDuF3.d.cts} +4 -3
  8. package/dist/{context-DiiCt4S7.d.ts → context-uyRUWoZQ.d.ts} +4 -3
  9. package/dist/{deno-CtG93ohj.mjs → deno-CABhzCb_.mjs} +1 -1
  10. package/dist/{docloader-dk_k3S8d.mjs → docloader-BbGSL0Xq.mjs} +2 -2
  11. package/dist/federation/builder.test.mjs +1 -1
  12. package/dist/federation/handler-compound-proof.test.d.mts +2 -0
  13. package/dist/federation/handler-compound-proof.test.mjs +152 -0
  14. package/dist/federation/handler.test.mjs +122 -4
  15. package/dist/federation/idempotency.test.mjs +2 -2
  16. package/dist/federation/keycache.test.mjs +52 -9
  17. package/dist/federation/metrics.test.mjs +1 -1
  18. package/dist/federation/middleware.test.mjs +111 -15
  19. package/dist/federation/mod.cjs +1 -1
  20. package/dist/federation/mod.d.cts +2 -2
  21. package/dist/federation/mod.d.ts +2 -2
  22. package/dist/federation/mod.js +1 -1
  23. package/dist/federation/send.test.mjs +139 -4
  24. package/dist/federation/tasks/enqueue.test.mjs +1 -1
  25. package/dist/federation/tasks/tasks.test.mjs +2 -2
  26. package/dist/federation/temporal.test.mjs +1 -1
  27. package/dist/federation/webfinger.test.mjs +1 -1
  28. package/dist/{federation-Drjlrrax.cjs → federation-DCEHQk4G.cjs} +632 -57
  29. package/dist/{http-6TI0tI03.d.cts → http-65PYi9jY.d.ts} +34 -2
  30. package/dist/{http-Dj4s1UQ0.js → http-9WyuldYh.js} +170 -8
  31. package/dist/{http-CdrSn2ip.d.ts → http-CP-7OdSq.d.cts} +34 -2
  32. package/dist/{http-B3skRvGo.cjs → http-CRFns-UE.cjs} +181 -7
  33. package/dist/{http-D7LX-BL9.mjs → http-D9urSO2D.mjs} +3 -3
  34. package/dist/{key-BVjgbs_9.mjs → key-Ccs8cnd7.mjs} +169 -7
  35. package/dist/{keycache-ZLqIfrkL.mjs → keycache-BUDHB5rY.mjs} +12 -4
  36. package/dist/{kv-cache-DtsjH_bY.cjs → kv-cache-DXT9vmIg.cjs} +2 -2
  37. package/dist/{kv-cache-tGNbvDCF.js → kv-cache-DtmXhPXG.js} +2 -2
  38. package/dist/{kv-cache-xRcC_xxP.mjs → kv-cache-lKyFn_Dn.mjs} +1 -1
  39. package/dist/{ld-OuCeQFny.mjs → ld-qup__MAW.mjs} +3 -3
  40. package/dist/map-local-context-conflict-DOYEEJ3u.mjs +204 -0
  41. package/dist/map-local-create-note-B0SeXJms.mjs +214 -0
  42. package/dist/{metrics-B9N27cCB.mjs → metrics-CISXCvB1.mjs} +1 -1
  43. package/dist/{middleware-TeLjzoxn.mjs → middleware-CzvJvPHK.mjs} +58 -15
  44. package/dist/{middleware-Cd_vPqDH.mjs → middleware-DkrU2WhY.mjs} +1 -1
  45. package/dist/{middleware-CPTDFMmJ.js → middleware-DsfWajaD.js} +593 -18
  46. package/dist/{mod-DypxPS9H.d.ts → mod-C0BYc0OU.d.ts} +1 -1
  47. package/dist/{mod-CnGLNoVw.d.cts → mod-CbKDOnCt.d.cts} +1 -1
  48. package/dist/{mod-CLHkCwLm.d.cts → mod-CyiSjRkk.d.cts} +1 -1
  49. package/dist/{mod-CSH1Ee-I.d.ts → mod-D882_Xsg.d.ts} +1 -1
  50. package/dist/{mod-DzRXACM5.d.ts → mod-UDwMjuHM.d.ts} +1 -1
  51. package/dist/{mod-BZldCEU0.d.cts → mod-x9W0d0Pk.d.cts} +1 -1
  52. package/dist/mod.cjs +20 -20
  53. package/dist/mod.d.cts +6 -6
  54. package/dist/mod.d.ts +6 -6
  55. package/dist/mod.js +5 -5
  56. package/dist/nodeinfo/client.test.mjs +28 -1
  57. package/dist/nodeinfo/handler.test.mjs +1 -1
  58. package/dist/nodeinfo/mod.cjs +1 -1
  59. package/dist/nodeinfo/mod.js +1 -1
  60. package/dist/{owner-DGSfntI-.d.ts → owner-BF0L9cMt.d.cts} +12 -1
  61. package/dist/{proof-BO_esQii.js → owner-BIPEZGiU.js} +1255 -1098
  62. package/dist/{owner-r_waTiQj.d.cts → owner-BSzr5-I8.d.ts} +12 -1
  63. package/dist/owner-CRoq8Spb.mjs +175 -0
  64. package/dist/{proof-Dua0d906.cjs → owner-CVA7L9MA.cjs} +1266 -1097
  65. package/dist/{proof-D5Ajk4DJ.mjs → proof-BDxNVZ3E.mjs} +186 -83
  66. package/dist/{send-DVXyUqhO.mjs → send-BzM5GLyn.mjs} +68 -11
  67. package/dist/sig/compound-proof-discovery.test.d.mts +2 -0
  68. package/dist/sig/compound-proof-discovery.test.mjs +331 -0
  69. package/dist/sig/compound-proof-verification.test.d.mts +2 -0
  70. package/dist/sig/compound-proof-verification.test.mjs +465 -0
  71. package/dist/sig/compound-proof.test.d.mts +2 -0
  72. package/dist/sig/compound-proof.test.mjs +324 -0
  73. package/dist/sig/http.test.mjs +2 -2
  74. package/dist/sig/key.test.mjs +169 -2
  75. package/dist/sig/ld.test.mjs +55 -2
  76. package/dist/sig/mod.cjs +17 -17
  77. package/dist/sig/mod.d.cts +3 -3
  78. package/dist/sig/mod.d.ts +3 -3
  79. package/dist/sig/mod.js +2 -2
  80. package/dist/sig/owner.test.mjs +103 -4
  81. package/dist/sig/proof.test.mjs +120 -1
  82. package/dist/{temporal-BoDw51A-.mjs → temporal-Dv6Qv-8k.mjs} +1 -1
  83. package/dist/testing/kv.test.mjs +1 -1
  84. package/dist/testing/mod.d.mts +2 -1
  85. package/dist/{types-RXm5YxpC.cjs → types-Be2zbyeG.cjs} +94 -2
  86. package/dist/{types-Dtyf8lVj.js → types-C_cNlNVK.js} +78 -4
  87. package/dist/utils/docloader.test.mjs +27 -3
  88. package/dist/utils/kv-cache.test.mjs +1 -1
  89. package/dist/utils/mod.cjs +1 -1
  90. package/dist/utils/mod.d.cts +1 -1
  91. package/dist/utils/mod.d.ts +1 -1
  92. package/dist/utils/mod.js +1 -1
  93. package/package.json +6 -6
  94. package/dist/owner-DTEqTzSr.mjs +0 -120
@@ -1,12 +1,12 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-CtG93ohj.mjs";
5
- import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-B9N27cCB.mjs";
6
- import { n as fetchKey, o as validateCryptoKey } from "./key-BVjgbs_9.mjs";
4
+ import { n as version, t as name } from "./deno-CABhzCb_.mjs";
5
+ import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-CISXCvB1.mjs";
6
+ import { r as fetchKey, s as validateCryptoKey } from "./key-Ccs8cnd7.mjs";
7
7
  import { n as preloadedOnlyDocumentLoader } from "./public-audience-Cc2YJ3ip.mjs";
8
8
  import { r as normalizeOutgoingActivityJsonLd } from "./outgoing-jsonld-B5Ne7JoL.mjs";
9
- import { c as getNormalizationContextLoader } from "./ld-OuCeQFny.mjs";
9
+ import { c as getNormalizationContextLoader } from "./ld-qup__MAW.mjs";
10
10
  import { getLogger } from "@logtape/logtape";
11
11
  import { Activity, DataIntegrityProof, Multikey, getTypeId } from "@fedify/vocab";
12
12
  import { SpanStatusCode, trace } from "@opentelemetry/api";
@@ -164,7 +164,31 @@ async function signObject(object, privateKey, keyId, options = {}) {
164
164
  async function verifyProof(jsonLd, proof, options = {}) {
165
165
  return await verifyProofWithMessageDigestCache(jsonLd, proof, options);
166
166
  }
167
- async function verifyProofWithMessageDigestCache(jsonLd, proof, options, messageDigestCache = {}, rawProofCandidate) {
167
+ /**
168
+ * Verifies the one direct literal proof on a map-local secured document.
169
+ *
170
+ * Unlike {@link verifyProof}, this removes only the literal `proof` member
171
+ * from the message digest. JSON-LD aliases remain part of the signed input,
172
+ * and the document's received `@context` is not replaced by the proof
173
+ * configuration context.
174
+ *
175
+ * @internal
176
+ */
177
+ async function verifyMapLocalProof(jsonLd, options = {}) {
178
+ if (!isJsonLdNode(jsonLd) || !globalThis.Object.hasOwn(jsonLd, "proof") || !isJsonLdNode(jsonLd.proof)) return null;
179
+ const proofContextLoader = getNormalizationContextLoader(preloadedOnlyDocumentLoader);
180
+ try {
181
+ const [candidate] = await parseRawProofCandidates(jsonLd, [jsonLd.proof], options, proofContextLoader);
182
+ if (candidate.proof == null) return null;
183
+ return await verifyProofWithMessageDigestCache(jsonLd, candidate.proof, options, {
184
+ proofContextLoader,
185
+ proofPropertyMode: "literal"
186
+ }, candidate);
187
+ } catch {
188
+ return null;
189
+ }
190
+ }
191
+ async function verifyProofWithMessageDigestCache(jsonLd, proof, options, messageDigestCache = {}, rawProofCandidate, keyIdBoundByCaller = false) {
168
192
  return await (options.tracerProvider ?? trace.getTracerProvider()).getTracer(name, version).startActiveSpan("object_integrity_proofs.verify", async (span) => {
169
193
  const start = performance.now();
170
194
  let verified = false;
@@ -176,7 +200,7 @@ async function verifyProofWithMessageDigestCache(jsonLd, proof, options, message
176
200
  if (proof.proofValue != null) span.setAttribute("object_integrity_proofs.signature", encodeHex(proof.proofValue));
177
201
  }
178
202
  try {
179
- const key = await verifyProofInternal(jsonLd, proof, options, messageDigestCache, rawProofCandidate);
203
+ const key = await verifyProofInternal(jsonLd, proof, options, messageDigestCache, rawProofCandidate, keyIdBoundByCaller);
180
204
  if (key == null) span.setStatus({ code: SpanStatusCode.ERROR });
181
205
  else verified = true;
182
206
  return key;
@@ -234,10 +258,11 @@ async function getJsonLdPropertyNames(jsonLd, propertyIri, defaults, documentLoa
234
258
  async function getProofPropertyNames(jsonLd, documentLoader = preloadedOnlyDocumentLoader) {
235
259
  return await getJsonLdPropertyNames(jsonLd, SECURITY_PROOF, ["proof", SECURITY_PROOF], documentLoader) ?? /* @__PURE__ */ new Set(["proof", SECURITY_PROOF]);
236
260
  }
237
- async function createProofMessageDigests(jsonLd, proofContextLoader, context) {
261
+ async function createProofMessageDigests(jsonLd, proofContextLoader, context, proofPropertyMode = "jsonLd") {
238
262
  const msg = { ...jsonLd };
239
- for (const property of await getProofPropertyNames(msg, proofContextLoader)) delete msg[property];
240
- if (context != null) msg["@context"] = structuredClone(context);
263
+ if (proofPropertyMode === "literal") delete msg.proof;
264
+ else for (const property of await getProofPropertyNames(msg, proofContextLoader)) delete msg[property];
265
+ if (proofPropertyMode === "jsonLd" && context != null) msg["@context"] = structuredClone(context);
241
266
  const encoder = new TextEncoder();
242
267
  const digest = async (value) => {
243
268
  const bytes = encoder.encode(serialize(value));
@@ -493,12 +518,15 @@ async function hasValidProofOptions(proofConfig, options, documentLoader) {
493
518
  if (previousProof == null || previousProof.present && typeof previousProof.value !== "string" && (!Array.isArray(previousProof.value) || previousProof.value.some((item) => typeof item !== "string"))) return false;
494
519
  return true;
495
520
  }
496
- async function verifyProofInternal(jsonLd, proof, options, messageDigestCache, rawProofCandidate) {
521
+ async function verifyProofInternal(jsonLd, proof, options, messageDigestCache, rawProofCandidate, keyIdBoundByCaller = false) {
497
522
  if (!isJsonLdNode(jsonLd) || proof.cryptosuite !== "eddsa-jcs-2022" || proof.verificationMethodId == null || proof.proofPurpose !== "assertionMethod" || proof.proofValue == null || proof.created == null) return null;
498
523
  const proofContextLoader = messageDigestCache.proofContextLoader ?? preloadedOnlyDocumentLoader;
499
524
  const proofConfiguration = await createProofConfiguration(jsonLd, proof, options, proofContextLoader, rawProofCandidate);
500
525
  if (proofConfiguration == null || !await hasValidProofOptions(proofConfiguration.value, options, proofContextLoader)) return null;
501
- const publicKeyPromise = measureSignatureKeyFetch(options.meterProvider, "object_integrity", () => fetchKey(proof.verificationMethodId, Multikey, options));
526
+ const publicKeyPromise = measureSignatureKeyFetch(options.meterProvider, "object_integrity", () => fetchKey(proof.verificationMethodId, Multikey, {
527
+ ...options,
528
+ keyIdBoundByCaller
529
+ }));
502
530
  const proofBytes = new TextEncoder().encode(serialize(proofConfiguration.value));
503
531
  const proofDigest = await crypto.subtle.digest("SHA-256", proofBytes);
504
532
  let fetchedKey;
@@ -532,7 +560,7 @@ async function verifyProofInternal(jsonLd, proof, options, messageDigestCache, r
532
560
  get: () => Promise.resolve(void 0),
533
561
  set: async (keyId, key) => await options.keyCache?.set(keyId, key)
534
562
  }
535
- }, messageDigestCache, rawProofCandidate);
563
+ }, messageDigestCache, rawProofCandidate, keyIdBoundByCaller);
536
564
  }
537
565
  logger.debug("The fetched key (verificationMethod) for the proof is not a valid Ed25519 key:\n{keyId}", {
538
566
  proof,
@@ -551,13 +579,15 @@ async function verifyProofInternal(jsonLd, proof, options, messageDigestCache, r
551
579
  const messageDigestValues = messageDigestCache.values ??= /* @__PURE__ */ new Map();
552
580
  let messageDigestsPromise = messageDigestValues.get(messageDigestKey);
553
581
  if (messageDigestsPromise == null) {
554
- messageDigestsPromise = createProofMessageDigests(jsonLd, messageDigestCache.proofContextLoader, proofConfiguration.context);
582
+ messageDigestsPromise = createProofMessageDigests(jsonLd, messageDigestCache.proofContextLoader, proofConfiguration.context, messageDigestCache.proofPropertyMode);
555
583
  messageDigestValues.set(messageDigestKey, messageDigestsPromise);
556
584
  }
557
585
  const messageDigests = await messageDigestsPromise;
558
586
  if (await verifyCandidate(messageDigests.onWire)) return publicKey;
559
- const normalizedDigest = await messageDigests.normalized();
560
- if (normalizedDigest != null && await verifyCandidate(normalizedDigest)) return publicKey;
587
+ if (messageDigestCache.proofPropertyMode !== "literal") {
588
+ const normalizedDigest = await messageDigests.normalized();
589
+ if (normalizedDigest != null && await verifyCandidate(normalizedDigest)) return publicKey;
590
+ }
561
591
  if (fetchedKey.cached) {
562
592
  logger.debug("Failed to verify the proof with the cached key {keyId}; retrying with the freshly fetched key...", {
563
593
  keyId: proof.verificationMethodId.href,
@@ -569,7 +599,7 @@ async function verifyProofInternal(jsonLd, proof, options, messageDigestCache, r
569
599
  get: () => Promise.resolve(void 0),
570
600
  set: async (keyId, key) => await options.keyCache?.set(keyId, key)
571
601
  }
572
- }, messageDigestCache, rawProofCandidate);
602
+ }, messageDigestCache, rawProofCandidate, keyIdBoundByCaller);
573
603
  }
574
604
  logger.debug("Failed to verify the proof with the fetched key {keyId}:\n{proof}", {
575
605
  keyId: proof.verificationMethodId.href,
@@ -649,59 +679,55 @@ async function expandPortableObjectRoot(jsonLd, contextLoader) {
649
679
  }
650
680
  };
651
681
  }
652
- /**
653
- * Verifies the FEP-ef61 Object Integrity Proof policy for a portable object.
654
- *
655
- * This applies the FEP-2277 core-type classification to the top-level JSON-LD
656
- * node. Portable actors, activities, and objects require proofs. A portable
657
- * collection without a proof is reported separately so a caller can apply a
658
- * gateway trust policy. Embedded portable objects are not traversed.
659
- *
660
- * Every proof must use a DID URL whose DID matches the portable object's
661
- * authority, and every proof must pass {@link verifyProof}.
662
- *
663
- * @param jsonLd The JSON-LD document to verify.
664
- * @param options Additional options. See also
665
- * {@link VerifyPortableObjectProofOptions}.
666
- * @returns The detailed portable proof-policy result.
667
- * @throws {TypeError} If the input is not a single JSON-LD object or has a
668
- * malformed portable ID.
669
- * @since 2.4.0
670
- */
671
- async function verifyPortableObjectProof(jsonLd, options = {}) {
682
+ async function preparePortableObjectProof(jsonLd, options) {
672
683
  if (isJsonLdNode(jsonLd) && typeof jsonLd["@id"] === "string" && !PORTABLE_OBJECT_ID_PATTERN.test(jsonLd["@id"])) return {
673
- verified: false,
674
- reason: { type: "notPortableObject" }
684
+ prepared: false,
685
+ result: {
686
+ verified: false,
687
+ reason: { type: "notPortableObject" }
688
+ }
675
689
  };
676
690
  const { root, proofContextLoader } = await expandPortableObjectRoot(jsonLd, options.contextLoader);
677
691
  const id = root["@id"];
678
692
  if (typeof id !== "string" || !PORTABLE_OBJECT_ID_PATTERN.test(id)) return {
679
- verified: false,
680
- reason: { type: "notPortableObject" }
693
+ prepared: false,
694
+ result: {
695
+ verified: false,
696
+ reason: { type: "notPortableObject" }
697
+ }
681
698
  };
682
699
  const objectId = parseIri(id);
683
700
  getFe34Origin(objectId);
684
701
  const objectType = classifyFep2277CoreType(root);
685
702
  if (objectType === "verificationMethod" || objectType === "publicKey" || objectType === "link") return {
686
- verified: false,
687
- reason: {
688
- type: "unsupportedObjectType",
689
- objectType
703
+ prepared: false,
704
+ result: {
705
+ verified: false,
706
+ reason: {
707
+ type: "unsupportedObjectType",
708
+ objectType
709
+ }
690
710
  }
691
711
  };
692
712
  const proofValues = root[SECURITY_PROOF];
693
- if (proofValues == null || Array.isArray(proofValues) && proofValues.length < 1) return objectType === "collection" ? {
694
- verified: false,
695
- reason: { type: "unsecuredCollection" }
696
- } : {
697
- verified: false,
698
- reason: { type: "missingProof" }
713
+ if (proofValues == null || Array.isArray(proofValues) && proofValues.length < 1) return {
714
+ prepared: false,
715
+ result: objectType === "collection" ? {
716
+ verified: false,
717
+ reason: { type: "unsecuredCollection" }
718
+ } : {
719
+ verified: false,
720
+ reason: { type: "missingProof" }
721
+ }
699
722
  };
700
723
  if (!Array.isArray(proofValues)) return {
701
- verified: false,
702
- reason: {
703
- type: "invalidProof",
704
- proofIndex: 0
724
+ prepared: false,
725
+ result: {
726
+ verified: false,
727
+ reason: {
728
+ type: "invalidProof",
729
+ proofIndex: 0
730
+ }
705
731
  }
706
732
  };
707
733
  const rawProofValues = isJsonLdNode(jsonLd) ? await getRawProofValues(jsonLd, proofContextLoader) : [];
@@ -709,10 +735,13 @@ async function verifyPortableObjectProof(jsonLd, options = {}) {
709
735
  for (let proofIndex = 0; proofIndex < proofValues.length; proofIndex++) {
710
736
  const proofValue = proofValues[proofIndex];
711
737
  if (!hasValidPortableProofShape(proofValue)) return {
712
- verified: false,
713
- reason: {
714
- type: "invalidProof",
715
- proofIndex
738
+ prepared: false,
739
+ result: {
740
+ verified: false,
741
+ reason: {
742
+ type: "invalidProof",
743
+ proofIndex
744
+ }
716
745
  }
717
746
  };
718
747
  let proof;
@@ -720,10 +749,13 @@ async function verifyPortableObjectProof(jsonLd, options = {}) {
720
749
  proof = await DataIntegrityProof.fromJsonLd(proofValue, options);
721
750
  } catch {
722
751
  return {
723
- verified: false,
724
- reason: {
725
- type: "invalidProof",
726
- proofIndex
752
+ prepared: false,
753
+ result: {
754
+ verified: false,
755
+ reason: {
756
+ type: "invalidProof",
757
+ proofIndex
758
+ }
727
759
  }
728
760
  };
729
761
  }
@@ -732,18 +764,24 @@ async function verifyPortableObjectProof(jsonLd, options = {}) {
732
764
  for (let proofIndex = 0; proofIndex < proofs.length; proofIndex++) {
733
765
  const verificationMethod = proofs[proofIndex].verificationMethodId;
734
766
  if (verificationMethod == null) return {
735
- verified: false,
736
- reason: {
737
- type: "invalidProof",
738
- proofIndex
767
+ prepared: false,
768
+ result: {
769
+ verified: false,
770
+ reason: {
771
+ type: "invalidProof",
772
+ proofIndex
773
+ }
739
774
  }
740
775
  };
741
776
  if (verificationMethod.protocol !== "did:") return {
742
- verified: false,
743
- reason: {
744
- type: "unsupportedVerificationMethod",
745
- proofIndex,
746
- verificationMethod
777
+ prepared: false,
778
+ result: {
779
+ verified: false,
780
+ reason: {
781
+ type: "unsupportedVerificationMethod",
782
+ proofIndex,
783
+ verificationMethod
784
+ }
747
785
  }
748
786
  };
749
787
  try {
@@ -751,24 +789,89 @@ async function verifyPortableObjectProof(jsonLd, options = {}) {
751
789
  } catch (error) {
752
790
  if (!(error instanceof TypeError)) throw error;
753
791
  return {
792
+ prepared: false,
793
+ result: {
794
+ verified: false,
795
+ reason: {
796
+ type: "unsupportedVerificationMethod",
797
+ proofIndex,
798
+ verificationMethod
799
+ }
800
+ }
801
+ };
802
+ }
803
+ if (!haveSameFe34Origin(objectId, verificationMethod)) return {
804
+ prepared: false,
805
+ result: {
754
806
  verified: false,
755
807
  reason: {
756
- type: "unsupportedVerificationMethod",
808
+ type: "verificationMethodMismatch",
757
809
  proofIndex,
810
+ objectId,
758
811
  verificationMethod
759
812
  }
760
- };
761
- }
762
- if (!haveSameFe34Origin(objectId, verificationMethod)) return {
763
- verified: false,
764
- reason: {
765
- type: "verificationMethodMismatch",
766
- proofIndex,
767
- objectId,
768
- verificationMethod
769
813
  }
770
814
  };
771
815
  }
816
+ return {
817
+ prepared: true,
818
+ objectId,
819
+ proofs,
820
+ rawProofValues,
821
+ proofContextLoader
822
+ };
823
+ }
824
+ /**
825
+ * Applies portable-object proof policy to one map-local cryptographic result.
826
+ *
827
+ * @internal
828
+ */
829
+ async function verifyPortableObjectProofPolicy(jsonLd, key, options = {}) {
830
+ const policyOptions = {
831
+ ...options,
832
+ contextLoader: preloadedOnlyDocumentLoader
833
+ };
834
+ const prepared = await preparePortableObjectProof(jsonLd, policyOptions);
835
+ if (!prepared.prepared) return prepared.result;
836
+ const policyProof = prepared.proofs[0];
837
+ const literalProof = isJsonLdNode(jsonLd) && isJsonLdNode(jsonLd.proof) ? (await parseRawProofCandidates(jsonLd, [jsonLd.proof], policyOptions, prepared.proofContextLoader))[0]?.proof : null;
838
+ const verificationMethod = policyProof?.verificationMethodId;
839
+ if (prepared.proofs.length !== 1 || key == null || policyProof == null || literalProof == null || !sameProof(policyProof, literalProof) || verificationMethod == null || key.id?.href !== verificationMethod.href) return {
840
+ verified: false,
841
+ reason: {
842
+ type: "invalidProof",
843
+ proofIndex: prepared.proofs.length > 1 ? 1 : 0
844
+ }
845
+ };
846
+ return {
847
+ verified: true,
848
+ keys: [key],
849
+ objectId: formatIri(prepared.objectId)
850
+ };
851
+ }
852
+ /**
853
+ * Verifies the FEP-ef61 Object Integrity Proof policy for a portable object.
854
+ *
855
+ * This applies the FEP-2277 core-type classification to the top-level JSON-LD
856
+ * node. Portable actors, activities, and objects require proofs. A portable
857
+ * collection without a proof is reported separately so a caller can apply a
858
+ * gateway trust policy. Embedded portable objects are not traversed.
859
+ *
860
+ * Every proof must use a DID URL whose DID matches the portable object's
861
+ * authority, and every proof must pass {@link verifyProof}.
862
+ *
863
+ * @param jsonLd The JSON-LD document to verify.
864
+ * @param options Additional options. See also
865
+ * {@link VerifyPortableObjectProofOptions}.
866
+ * @returns The detailed portable proof-policy result.
867
+ * @throws {TypeError} If the input is not a single JSON-LD object or has a
868
+ * malformed portable ID.
869
+ * @since 2.4.0
870
+ */
871
+ async function verifyPortableObjectProof(jsonLd, options = {}) {
872
+ const prepared = await preparePortableObjectProof(jsonLd, options);
873
+ if (!prepared.prepared) return prepared.result;
874
+ const { proofs, rawProofValues, proofContextLoader } = prepared;
772
875
  const keys = [];
773
876
  const rawProofCandidatePool = {
774
877
  candidates: await parseRawProofCandidates(jsonLd, rawProofValues, options, proofContextLoader),
@@ -784,7 +887,7 @@ async function verifyPortableObjectProof(jsonLd, options = {}) {
784
887
  proofIndex
785
888
  }
786
889
  };
787
- const key = await verifyProofWithMessageDigestCache(jsonLd, proofs[proofIndex], options, messageDigestCache, rawProofCandidate);
890
+ const key = await verifyProofWithMessageDigestCache(jsonLd, proofs[proofIndex], options, messageDigestCache, rawProofCandidate, true);
788
891
  if (key == null) return {
789
892
  verified: false,
790
893
  reason: {
@@ -887,4 +990,4 @@ function hasCryptographicOrigin(iri) {
887
990
  return /^did:/i.test(iri) || /^ap(?:\+ef61)?:\/\//i.test(iri);
888
991
  }
889
992
  //#endregion
890
- export { verifyPortableObjectProof as a, verifyObject as i, hasProofLike as n, verifyProof as o, signObject as r, createProof as t };
993
+ export { verifyObject as a, verifyProof as c, verifyMapLocalProof as i, hasProofLike as n, verifyPortableObjectProof as o, signObject as r, verifyPortableObjectProofPolicy as s, createProof as t };
@@ -1,12 +1,12 @@
1
1
  import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-CtG93ohj.mjs";
5
- import { n as getDurationMs, r as getFederationMetrics } from "./metrics-B9N27cCB.mjs";
6
- import { n as doubleKnock } from "./http-D7LX-BL9.mjs";
4
+ import { n as version, t as name } from "./deno-CABhzCb_.mjs";
5
+ import { n as getDurationMs, r as getFederationMetrics } from "./metrics-CISXCvB1.mjs";
6
+ import { n as doubleKnock } from "./http-D9urSO2D.mjs";
7
7
  import { getLogger } from "@logtape/logtape";
8
8
  import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
9
- import { FetchError } from "@fedify/vocab-runtime";
9
+ import { FetchError, UrlError, validatePublicUrl } from "@fedify/vocab-runtime";
10
10
  //#region src/federation/send.ts
11
11
  /**
12
12
  * Extracts the inbox URLs from recipients.
@@ -112,7 +112,7 @@ async function readLimitedResponseBody(response, maxBytes) {
112
112
  if (truncated) result += "… (truncated)";
113
113
  return result;
114
114
  }
115
- async function sendActivityInternal({ activity, activityId, activityType, keys, inbox, headers, specDeterminer, meterProvider, tracerProvider }, span) {
115
+ async function sendActivityInternal({ activity, activityId, activityType, keys, inbox, allowPrivateAddress, headers, specDeterminer, meterProvider, tracerProvider }, span) {
116
116
  const logger = getLogger([
117
117
  "fedify",
118
118
  "federation",
@@ -121,12 +121,25 @@ async function sendActivityInternal({ activity, activityId, activityType, keys,
121
121
  const federationMetrics = getFederationMetrics(meterProvider);
122
122
  const started = performance.now();
123
123
  let deliverySuccess = false;
124
+ async function validateUrl(url) {
125
+ if (!allowPrivateAddress) try {
126
+ await validatePublicUrl(url);
127
+ } catch (error) {
128
+ if (error instanceof UrlError) logger.error("Disallowed private URL: {url}", {
129
+ url,
130
+ error
131
+ });
132
+ throw error;
133
+ }
134
+ }
135
+ await validateUrl(inbox.href);
124
136
  headers = new Headers(headers);
125
137
  headers.set("Content-Type", "application/activity+json");
126
138
  const request = new Request(inbox, {
127
139
  method: "POST",
128
140
  headers,
129
- body: JSON.stringify(activity)
141
+ body: JSON.stringify(activity),
142
+ redirect: "manual"
130
143
  });
131
144
  let rsaKey = null;
132
145
  for (const key of keys) if (key.privateKey.algorithm.name === "RSASSA-PKCS1-v1_5") {
@@ -142,19 +155,20 @@ async function sendActivityInternal({ activity, activityId, activityType, keys,
142
155
  });
143
156
  let response;
144
157
  try {
145
- response = rsaKey == null ? await fetch(request) : await doubleKnock(request, rsaKey, {
158
+ response = rsaKey == null ? await fetchWithValidatedRedirects(request, validateUrl) : await doubleKnock(request, rsaKey, {
146
159
  tracerProvider,
147
- specDeterminer
160
+ specDeterminer,
161
+ validateRedirect: validateUrl
148
162
  });
149
163
  } catch (error) {
150
- const transportError = error instanceof FetchError ? error : createFetchError(inbox.href, error);
164
+ const failure = error instanceof FetchError || error instanceof UrlError ? error : createFetchError(inbox.href, error);
151
165
  logger.error("Failed to send activity {activityId} to {inbox}:\n{error}", {
152
166
  activityId,
153
167
  inbox: inbox.href,
154
- error: transportError
168
+ error: failure
155
169
  });
156
170
  federationMetrics.recordDelivery(inbox, getDurationMs(started), false, activityType);
157
- throw transportError;
171
+ throw failure;
158
172
  }
159
173
  try {
160
174
  if (!response.ok) {
@@ -191,6 +205,49 @@ function createFetchError(url, cause) {
191
205
  error.cause = cause;
192
206
  return error;
193
207
  }
208
+ async function fetchWithValidatedRedirects(request, validateUrl) {
209
+ let body = await request.clone().text();
210
+ for (let redirects = 0;; redirects++) {
211
+ const response = await fetch(request, { redirect: "manual" });
212
+ const location = response.headers.get("Location");
213
+ if (![
214
+ 301,
215
+ 302,
216
+ 303,
217
+ 307,
218
+ 308
219
+ ].includes(response.status) || location == null) return response;
220
+ await response.body?.cancel();
221
+ if (redirects >= 20) throw new TypeError("Too many redirects");
222
+ const url = new URL(location, request.url);
223
+ await validateUrl(url.href);
224
+ const headers = new Headers(request.headers);
225
+ if (url.origin !== new URL(request.url).origin) {
226
+ headers.delete("Authorization");
227
+ headers.delete("Proxy-Authorization");
228
+ headers.delete("Cookie");
229
+ headers.delete("Host");
230
+ }
231
+ let method = request.method;
232
+ if ((response.status === 301 || response.status === 302) && method === "POST" || response.status === 303 && method !== "GET" && method !== "HEAD") {
233
+ method = "GET";
234
+ body = void 0;
235
+ for (const name of [
236
+ "Content-Encoding",
237
+ "Content-Language",
238
+ "Content-Length",
239
+ "Content-Location",
240
+ "Content-Type"
241
+ ]) headers.delete(name);
242
+ }
243
+ request = new Request(url, {
244
+ method,
245
+ headers,
246
+ body,
247
+ redirect: "manual"
248
+ });
249
+ }
250
+ }
194
251
  /**
195
252
  * An error that is thrown when an activity fails to send to a remote inbox.
196
253
  * It contains structured information about the failure, including the HTTP
@@ -0,0 +1,2 @@
1
+ import { Temporal } from "temporal-polyfill";
2
+ import { URLPattern } from "urlpattern-polyfill";