@fedify/fedify 2.4.0-dev.2084 → 2.4.0-dev.2093

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 (75) hide show
  1. package/dist/{builder-BGlpUocK.mjs → builder-Ld4Y9ymR.mjs} +34 -3
  2. package/dist/compat/transformers.test.mjs +1 -1
  3. package/dist/{compound-proof-B6Aj2kGY.mjs → compound-proof-CTx_lewQ.mjs} +191 -13
  4. package/dist/{deno-Dn-py9Zy.mjs → deno-8t8k_6Yv.mjs} +1 -1
  5. package/dist/{docloader-BUq5bYJr.mjs → docloader-DJKcsAiF.mjs} +2 -2
  6. package/dist/federation/builder.test.mjs +1 -1
  7. package/dist/federation/compound-fanout.test.mjs +3 -3
  8. package/dist/federation/handler-compound-proof.test.mjs +3 -3
  9. package/dist/federation/handler.test.mjs +5 -5
  10. package/dist/federation/hashlink-media.test.mjs +2 -2
  11. package/dist/federation/idempotency.test.mjs +2 -2
  12. package/dist/federation/metrics.test.mjs +1 -1
  13. package/dist/federation/middleware.test.mjs +7 -7
  14. package/dist/federation/mod.cjs +1 -1
  15. package/dist/federation/mod.js +1 -1
  16. package/dist/federation/outgoing-proof.test.mjs +254 -5
  17. package/dist/federation/portable-gateway-keys.test.mjs +72 -11
  18. package/dist/federation/portable-inbox.test.mjs +116 -4
  19. package/dist/federation/portable.test.mjs +182 -4
  20. package/dist/federation/send.test.mjs +3 -3
  21. package/dist/federation/tasks/enqueue.test.mjs +1 -1
  22. package/dist/federation/tasks/tasks.test.mjs +2 -2
  23. package/dist/federation/temporal.test.mjs +1 -1
  24. package/dist/federation/webfinger.test.mjs +89 -1
  25. package/dist/{federation-BLGx8JUH.cjs → federation-aSHO04ob.cjs} +360 -65
  26. package/dist/{http-B0VRNipE.mjs → http-B2ftY0Ha.mjs} +4 -4
  27. package/dist/{http-B5q1CHtL.cjs → http-DSaSAN7X.cjs} +1609 -1457
  28. package/dist/{http-4PCCvhsU.js → http-DyT7DP1Y.js} +1518 -1390
  29. package/dist/{key-BJrp524f.mjs → key-Dwm4VswT.mjs} +39 -99
  30. package/dist/{kv-cache-Dhhhrryv.cjs → kv-cache-BSJAlP4C.cjs} +1 -1
  31. package/dist/{kv-cache-C4C3LyYW.mjs → kv-cache-D0yYukqk.mjs} +1 -1
  32. package/dist/{kv-cache-1KUmAPH7.js → kv-cache-DmroAz3M.js} +1 -1
  33. package/dist/{ld-BmFz5NLA.mjs → ld-BYZOoDzZ.mjs} +3 -3
  34. package/dist/{metrics-Beod6tzO.mjs → metrics-B7YwKTFr.mjs} +1 -1
  35. package/dist/{middleware-B7CNsyqt.mjs → middleware-BNeqKSsL.mjs} +1 -1
  36. package/dist/{middleware-D_9pNYe9.js → middleware-Blq0882d.js} +368 -73
  37. package/dist/{middleware-BKeQYPm0.mjs → middleware-E_0IH72m.mjs} +166 -71
  38. package/dist/{mod-HDSvS-ww.d.cts → mod-C8LieBzF.d.cts} +16 -3
  39. package/dist/{mod-C9WSpZWm.d.ts → mod-Coacostr.d.ts} +16 -3
  40. package/dist/mod.cjs +5 -5
  41. package/dist/mod.d.cts +1 -1
  42. package/dist/mod.d.ts +1 -1
  43. package/dist/mod.js +5 -5
  44. package/dist/nodeinfo/handler.test.mjs +1 -1
  45. package/dist/{owner-CsKVgMzk.mjs → owner-Cgx2Dc7I.mjs} +5 -4
  46. package/dist/{owner-_pbbgLJK.js → owner-DAGSC5gX.js} +2 -2
  47. package/dist/{owner-BQFZtFV4.cjs → owner-szIZl4qI.cjs} +2 -2
  48. package/dist/{portable-key-B4R-CiIV.mjs → portable-key-Cn1WJSN1.mjs} +60 -41
  49. package/dist/portable-key-id-Bva5iT3W.mjs +136 -0
  50. package/dist/{proof-ozG4HfcQ.mjs → proof-DVXC6nYz.mjs} +59 -18
  51. package/dist/{send-ClPt8nYu.mjs → send-D-hOvYKy.mjs} +3 -3
  52. package/dist/sig/compound-proof-discovery.test.mjs +1 -1
  53. package/dist/sig/compound-proof-producer.test.mjs +1 -1
  54. package/dist/sig/compound-proof-verification.test.mjs +160 -3
  55. package/dist/sig/compound-proof.test.mjs +2 -2
  56. package/dist/sig/http.test.mjs +2 -2
  57. package/dist/sig/key.test.mjs +1 -1
  58. package/dist/sig/ld.test.mjs +2 -2
  59. package/dist/sig/mod.cjs +3 -3
  60. package/dist/sig/mod.d.cts +1 -1
  61. package/dist/sig/mod.d.ts +1 -1
  62. package/dist/sig/mod.js +3 -3
  63. package/dist/sig/owner.test.mjs +1 -1
  64. package/dist/sig/portable-collection.test.mjs +69 -5
  65. package/dist/sig/portable-key.test.mjs +176 -10
  66. package/dist/sig/proof.test.mjs +107 -4
  67. package/dist/{sig-DhdomAN0.js → sig-B5Ell7pO.js} +19 -3
  68. package/dist/{sig-k3C8sF7g.cjs → sig-CXOKN4Br.cjs} +19 -3
  69. package/dist/{temporal-Ba0EAZAI.mjs → temporal-CqpKGEPk.mjs} +1 -1
  70. package/dist/testing/kv.test.mjs +1 -1
  71. package/dist/utils/docloader.test.mjs +2 -2
  72. package/dist/utils/kv-cache.test.mjs +1 -1
  73. package/dist/utils/mod.cjs +1 -1
  74. package/dist/utils/mod.js +1 -1
  75. package/package.json +7 -7
@@ -1,105 +1,13 @@
1
1
  import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-Dn-py9Zy.mjs";
5
- import { _ as recordKeyLookup, n as getDurationMs, t as classifyFetchError } from "./metrics-Beod6tzO.mjs";
4
+ import { n as version, t as name } from "./deno-8t8k_6Yv.mjs";
5
+ import { _ as recordKeyLookup, n as getDurationMs, t as classifyFetchError } from "./metrics-B7YwKTFr.mjs";
6
+ import { a as isCompatibleKeyId, o as isPortableActorDocument, s as isPortableId } from "./portable-key-id-Bva5iT3W.mjs";
6
7
  import { getLogger } from "@logtape/logtape";
7
8
  import { CryptographicKey, Multikey, Object as Object$1, isActor } from "@fedify/vocab";
8
9
  import { SpanKind, SpanStatusCode, trace } from "@opentelemetry/api";
9
- import { FetchError, canonicalizePortableUri, formatIri, fromCompatibleEf61Id, getDocumentLoader, parseDidKeyVerificationMethod, toCompatibleEf61Id } from "@fedify/vocab-runtime";
10
- //#region src/sig/portable-key-id.ts
11
- const COMPATIBLE_KEY_ID_PATH_PATTERN = /^\/\.well-known\/apgateway\/did(?::|%3A)/i;
12
- /**
13
- * Checks whether the URL is an `ap:` or `ap+ef61:` URI.
14
- * @internal
15
- */
16
- function isPortableUri(url) {
17
- return url.protocol === "ap:" || url.protocol === "ap+ef61:";
18
- }
19
- /**
20
- * Checks whether an activity is performed by an FEP-ef61 portable actor,
21
- * i.e., any of its actors has an `ap:` or `ap+ef61:` ID.
22
- * @internal
23
- */
24
- function hasPortableActor(activity) {
25
- return activity.actorIds.some(isPortableUri);
26
- }
27
- /**
28
- * Checks whether a key ID looks like an [FEP-ef61] compatible identifier,
29
- * i.e., an HTTP(S) URL under a gateway's `/.well-known/apgateway/did:` path.
30
- * Such a key ID names a key that a gateway holds for a portable actor, so it
31
- * is resolved only as a gateway key, and never cached in a key cache.
32
- *
33
- * [FEP-ef61]: https://w3id.org/fep/ef61
34
- * @internal
35
- */
36
- function isCompatibleKeyId(keyId) {
37
- return (keyId.protocol === "http:" || keyId.protocol === "https:") && COMPATIBLE_KEY_ID_PATH_PATTERN.test(keyId.pathname);
38
- }
39
- /**
40
- * Gets the canonical portable ID of an actor ID that is either an `ap:` or
41
- * `ap+ef61:` URI or a compatible identifier.
42
- * @returns The canonical portable ID, or `null` if the ID is neither, or is
43
- * malformed.
44
- * @internal
45
- */
46
- function getCanonicalPortableId(id) {
47
- try {
48
- if (isPortableUri(id)) return canonicalizePortableUri(formatIri(id));
49
- const portable = fromCompatibleEf61Id(id);
50
- if (portable == null) return null;
51
- return canonicalizePortableUri(formatIri(portable));
52
- } catch (error) {
53
- if (error instanceof TypeError) return null;
54
- throw error;
55
- }
56
- }
57
- /**
58
- * Builds the base of the key IDs of a gateway's keys for a portable actor:
59
- * the actor's compatible identifier on the gateway.
60
- * @param actorId The portable actor's ID, either an `ap:` or `ap+ef61:` URI
61
- * or a compatible identifier. It must not have a fragment.
62
- * @param gateway The gateway's origin, e.g., `https://example.com`.
63
- * @returns The compatible identifier without query and fragment.
64
- * @throws {TypeError} If the actor ID is not a portable ID, or has
65
- * a fragment.
66
- * @internal
67
- */
68
- function getGatewayKeyBase(actorId, gateway) {
69
- let portable;
70
- if (isPortableUri(actorId)) portable = actorId;
71
- else try {
72
- portable = fromCompatibleEf61Id(actorId);
73
- } catch (error) {
74
- if (!(error instanceof TypeError)) throw error;
75
- portable = null;
76
- }
77
- if (portable == null) throw new TypeError(`The portable actor ID ${actorId.href} must be an ap: or ap+ef61: URI, or an FEP-ef61 compatible identifier.`);
78
- if (portable.hash !== "") throw new TypeError(`The portable actor ID ${actorId.href} must not have a fragment.`);
79
- const base = toCompatibleEf61Id(portable, gateway);
80
- base.search = "";
81
- base.hash = "";
82
- return base;
83
- }
84
- /**
85
- * Tells whether a parsed document at a compatible key ID is a portable actor,
86
- * which makes the key a gateway key that only
87
- * {@link verifyPortableGatewayKeyDocument} can vouch for.
88
- * @internal
89
- */
90
- function isPortableActorDocument(object) {
91
- return isActor(object) && object.id != null && isPortableUri(object.id);
92
- }
93
- /**
94
- * Compares two public keys by their key material.
95
- * @internal
96
- */
97
- async function isSamePublicKey(a, b) {
98
- if (a.algorithm.name !== b.algorithm.name) return false;
99
- const [x, y] = await Promise.all([crypto.subtle.exportKey("jwk", a), crypto.subtle.exportKey("jwk", b)]);
100
- return x.kty === y.kty && x.crv === y.crv && x.n === y.n && x.e === y.e && x.x === y.x && x.y === y.y;
101
- }
102
- //#endregion
10
+ import { FetchError, getDocumentLoader, parseDidKeyVerificationMethod } from "@fedify/vocab-runtime";
103
11
  //#region src/sig/key.ts
104
12
  /**
105
13
  * Checks if the given key is valid and supported. No-op if the key is valid,
@@ -200,6 +108,10 @@ async function fetchActorDocument(actorId, options = {}) {
200
108
  "sig",
201
109
  "key"
202
110
  ]);
111
+ if (isPortableId(actorId)) {
112
+ logger.debug("The actor {actorId} is a portable actor, which is not authenticated by the web origin that serves it.", { actorId: actorId.href });
113
+ return null;
114
+ }
203
115
  const documentLoader = options.documentLoader ?? getDocumentLoader();
204
116
  const contextLoader = options.contextLoader ?? getDocumentLoader();
205
117
  const { tracerProvider } = options;
@@ -233,6 +145,13 @@ async function fetchActorDocument(actorId, options = {}) {
233
145
  return null;
234
146
  }
235
147
  if (!isActor(object)) return null;
148
+ if (object.id != null && isPortableId(object.id)) {
149
+ logger.debug("The document served at {documentUrl} claims to be the portable actor {actorId}, which is not authenticated by the web origin that serves it.", {
150
+ documentUrl: documentUrl.href,
151
+ actorId: object.id?.href
152
+ });
153
+ return null;
154
+ }
236
155
  if (object.id == null || object.id.origin !== documentUrl.origin) {
237
156
  logger.debug("The document served at {documentUrl} claims to be the actor {actorId}, which belongs to another origin; refusing to treat it as that actor's document.", {
238
157
  documentUrl: documentUrl.href,
@@ -567,7 +486,7 @@ async function resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, {
567
486
  let ownerDocument = null;
568
487
  if (object instanceof cls && (object.id == null || object.id.href === keyId)) key = object.id == null ? object.clone({ id: cacheKey }) : object;
569
488
  else if (isActor(object)) {
570
- if (object.id == null || object.id.origin !== documentUrl.origin) {
489
+ if (object.id == null || isPortableId(object.id) || object.id.origin !== documentUrl.origin) {
571
490
  logger.debug("Failed to verify; the document served at {documentUrl} claims to be the actor {actorId}, which belongs to another origin.", {
572
491
  keyId,
573
492
  documentUrl: documentUrl.href,
@@ -643,6 +562,18 @@ async function resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, {
643
562
  }
644
563
  const claimedOwnerId = key instanceof CryptographicKey ? key.ownerId : key.controllerId;
645
564
  if (!(keyIdBoundByCaller && claimedOwnerId != null && claimedOwnerId.href === stripFragment(cacheKey)?.href)) {
565
+ if (claimedOwnerId != null && isPortableId(claimedOwnerId)) {
566
+ logger.debug("Failed to verify; key {keyId} claims the portable actor {claimedOwnerId} as its owner, but is not its gateway key.", {
567
+ keyId,
568
+ claimedOwnerId: claimedOwnerId.href
569
+ });
570
+ await keyCache?.set(cacheKey, null);
571
+ await clearFetchErrorMetadata(cacheKey, keyCache);
572
+ return {
573
+ key: null,
574
+ cached: false
575
+ };
576
+ }
646
577
  if (ownerDocument != null && claimedOwnerId == null) key = key instanceof CryptographicKey ? key.clone({ owner: ownerDocument.id }) : key.clone({ controller: ownerDocument.id });
647
578
  else if (claimedOwnerId != null && claimedOwnerId.href !== ownerDocument?.id?.href) {
648
579
  if (await verifyKeyOwnership(key, {
@@ -672,6 +603,15 @@ async function resolveFetchedKey(document, cacheKey, documentUrl, keyId, cls, {
672
603
  cached: false
673
604
  };
674
605
  }
606
+ /**
607
+ * Tells whether a key claims a portable actor as its owner. Such a key can
608
+ * only be a gateway key, which is never cached, so a cached one was cached
609
+ * by an older version that trusted web origins, and is fetched again.
610
+ */
611
+ function claimsPortableOwner(key) {
612
+ const ownerId = key instanceof CryptographicKey ? key.ownerId : key?.controllerId;
613
+ return ownerId != null && isPortableId(ownerId);
614
+ }
675
615
  async function fetchKeyWithResult(cacheKey, cls, options, onCachedUnavailable, onFetchError) {
676
616
  const start = performance.now();
677
617
  let outcome = { result: "error" };
@@ -698,7 +638,7 @@ async function fetchKeyWithResult(cacheKey, cls, options, onCachedUnavailable, o
698
638
  outcome = { result: "hit" };
699
639
  return cachedUnavailable;
700
640
  }
701
- if (cached != null) {
641
+ if (cached != null && !claimsPortableOwner(cached.key)) {
702
642
  outcome = { result: "hit" };
703
643
  return cached;
704
644
  }
@@ -752,4 +692,4 @@ async function fetchKeyInternal(keyId, cls, options = {}) {
752
692
  });
753
693
  }
754
694
  //#endregion
755
- export { generateCryptoKeyPair as a, verifyKeyOwnership as c, hasPortableActor as d, isCompatibleKeyId as f, isSamePublicKey as h, fetchKeyDetailed as i, getCanonicalPortableId as l, isPortableUri as m, fetchActorDocument as n, importJwk as o, isPortableActorDocument as p, fetchKey as r, validateCryptoKey as s, exportJwk as t, getGatewayKeyBase as u };
695
+ export { generateCryptoKeyPair as a, verifyKeyOwnership as c, fetchKeyDetailed as i, fetchActorDocument as n, importJwk as o, fetchKey as r, validateCryptoKey as s, exportJwk as t };
@@ -1,5 +1,5 @@
1
1
  const { URLPattern } = require("urlpattern-polyfill");
2
- const require_http = require("./http-B5q1CHtL.cjs");
2
+ const require_http = require("./http-DSaSAN7X.cjs");
3
3
  let _logtape_logtape = require("@logtape/logtape");
4
4
  let _fedify_vocab_runtime = require("@fedify/vocab-runtime");
5
5
  let es_toolkit = require("es-toolkit");
@@ -1,7 +1,7 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { p as recordDocumentCache } from "./metrics-Beod6tzO.mjs";
4
+ import { p as recordDocumentCache } from "./metrics-B7YwKTFr.mjs";
5
5
  import { getLogger } from "@logtape/logtape";
6
6
  import { preloadedContexts } from "@fedify/vocab-runtime";
7
7
  //#region src/utils/kv-cache.ts
@@ -1,6 +1,6 @@
1
1
  import { Temporal } from "temporal-polyfill";
2
2
  import { URLPattern } from "urlpattern-polyfill";
3
- import { R as validateCryptoKey, nt as recordDocumentCache, t as doubleKnock } from "./http-4PCCvhsU.js";
3
+ import { B as validateCryptoKey, Q as recordDocumentCache, t as doubleKnock } from "./http-DyT7DP1Y.js";
4
4
  import { getLogger } from "@logtape/logtape";
5
5
  import { UrlError, createActivityPubRequest, getRemoteDocument, logRequest, preloadedContexts, validatePublicUrl } from "@fedify/vocab-runtime";
6
6
  import { curry } from "es-toolkit";
@@ -1,9 +1,9 @@
1
1
  import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-Dn-py9Zy.mjs";
5
- import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-Beod6tzO.mjs";
6
- import { r as fetchKey, s as validateCryptoKey } from "./key-BJrp524f.mjs";
4
+ import { n as version, t as name } from "./deno-8t8k_6Yv.mjs";
5
+ import { n as getDurationMs, r as getFederationMetrics, s as measureSignatureKeyFetch } from "./metrics-B7YwKTFr.mjs";
6
+ import { r as fetchKey, s as validateCryptoKey } from "./key-Dwm4VswT.mjs";
7
7
  import { getLogger } from "@logtape/logtape";
8
8
  import { Activity, CryptographicKey, Object as Object$1, getTypeId } from "@fedify/vocab";
9
9
  import { SpanStatusCode, trace } from "@opentelemetry/api";
@@ -1,7 +1,7 @@
1
1
  import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as version, t as name } from "./deno-Dn-py9Zy.mjs";
4
+ import { n as version, t as name } from "./deno-8t8k_6Yv.mjs";
5
5
  import { metrics } from "@opentelemetry/api";
6
6
  import { FetchError } from "@fedify/vocab-runtime";
7
7
  //#region src/federation/metrics.ts
@@ -1,5 +1,5 @@
1
1
  import "temporal-polyfill";
2
2
  import "urlpattern-polyfill";
3
3
  globalThis.addEventListener = () => {};
4
- import { n as FederationImpl } from "./middleware-BKeQYPm0.mjs";
4
+ import { n as FederationImpl } from "./middleware-E_0IH72m.mjs";
5
5
  export { FederationImpl };