@calimero-network/mero-js 24.6.0 → 25.0.0

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.
package/dist/index.cjs CHANGED
@@ -61,6 +61,7 @@ __export(index_exports, {
61
61
  NotForAccountError: () => NotForAccountError,
62
62
  OWNER_OP_KIND: () => OWNER_OP_KIND,
63
63
  PRESENCE_DOMAIN: () => PRESENCE_DOMAIN,
64
+ PRODUCTION_PROFILE: () => PRODUCTION_PROFILE,
64
65
  RELAY_NODE_KEY_RETRY_MS: () => RELAY_NODE_KEY_RETRY_MS,
65
66
  REQUEST_PROOF_HEADER: () => REQUEST_PROOF_HEADER,
66
67
  RelayClient: () => RelayClient,
@@ -166,6 +167,7 @@ __export(index_exports, {
166
167
  fetchNodeReleaseVersion: () => fetchNodeReleaseVersion,
167
168
  fetchRegistryBundles: () => fetchRegistryBundles,
168
169
  forgetMethodKinds: () => forgetMethodKinds,
170
+ forgetRelaySealing: () => forgetRelaySealing,
169
171
  foundDelegatedNamespace: () => foundDelegatedNamespace,
170
172
  foundedNamespaceId: () => foundedNamespaceId,
171
173
  generateAccountRoot: () => generateAccountRoot,
@@ -226,6 +228,7 @@ __export(index_exports, {
226
228
  readRelayMap: () => readRelayMap,
227
229
  redeemInvitation: () => redeemInvitation,
228
230
  relayForContext: () => relayForContext,
231
+ relayTransportFetch: () => relayTransportFetch,
229
232
  rememberGroupNamespace: () => rememberGroupNamespace,
230
233
  rememberRelay: () => rememberRelay,
231
234
  reportDataOf: () => reportDataOf,
@@ -6844,6 +6847,9 @@ function createQuoteVerifier(options) {
6844
6847
  throw new Error(`The platform's TCB status is ${verified.status}, which is not accepted`);
6845
6848
  }
6846
6849
  const td = tdReport(verified.report);
6850
+ if (td.tdAttributes?.length !== 8 || (td.tdAttributes[0] & 1) !== 0) {
6851
+ throw new Error("The quote is from a debug TD, or its TD attributes cannot be read");
6852
+ }
6847
6853
  const measured = [td.mrTd, td.rtMr0, td.rtMr1, td.rtMr2, td.rtMr3].map((register) => hex(register));
6848
6854
  if (allowedImages.length > 0 && !allowedImages.includes(measured.join())) {
6849
6855
  throw new Error(
@@ -6896,7 +6902,8 @@ var TCB_STATUSES = [
6896
6902
  "OutOfDate",
6897
6903
  "OutOfDateConfigurationNeeded"
6898
6904
  ];
6899
- function trustedMeasurementsFromReleases(releases, { profile }) {
6905
+ var PRODUCTION_PROFILE = "locked-read-only";
6906
+ function trustedMeasurementsFromReleases(releases, { profile, allowDebugProfile = false }) {
6900
6907
  if (releases.length === 0) {
6901
6908
  throw new Error("No release given, so no image would be trusted");
6902
6909
  }
@@ -6911,6 +6918,11 @@ function trustedMeasurementsFromReleases(releases, { profile }) {
6911
6918
  const known = Object.keys(release?.profiles ?? {}).join(", ") || "none";
6912
6919
  throw new Error(`${name} has no profile ${JSON.stringify(profile)} (it has: ${known})`);
6913
6920
  }
6921
+ if (profile !== PRODUCTION_PROFILE && !allowDebugProfile) {
6922
+ throw new Error(
6923
+ `${JSON.stringify(profile)} is not a production profile: its image is readable by whoever operates the node. Trust ${JSON.stringify(PRODUCTION_PROFILE)}, or pass allowDebugProfile to test`
6924
+ );
6925
+ }
6914
6926
  const accepted = (image.allowed_tcb_statuses ?? ["uptodate"]).map((status) => tcbStatus(status, name));
6915
6927
  statuses = statuses === void 0 ? accepted : statuses.filter((status) => accepted.includes(status));
6916
6928
  return {
@@ -7399,7 +7411,10 @@ async function trustSignedRelease(options) {
7399
7411
  if (compareReleaseVersions(version, minimum) < 0) {
7400
7412
  throw new Error(`Release ${version} is older than the minimum trusted, ${minimum}`);
7401
7413
  }
7402
- return trustedMeasurementsFromReleases([published], { profile: options.profile });
7414
+ return trustedMeasurementsFromReleases([published], {
7415
+ profile: options.profile,
7416
+ allowDebugProfile: options.allowDebugProfile
7417
+ });
7403
7418
  }
7404
7419
  function createSignedReleaseVerifier(options) {
7405
7420
  releaseVersion(options.minReleaseVersion, "minReleaseVersion");
@@ -7407,6 +7422,7 @@ function createSignedReleaseVerifier(options) {
7407
7422
  const trusted = await trustSignedRelease({
7408
7423
  release: await options.release(),
7409
7424
  profile: options.profile,
7425
+ allowDebugProfile: options.allowDebugProfile,
7410
7426
  minReleaseVersion: options.minReleaseVersion,
7411
7427
  identity: options.identity,
7412
7428
  issuer: options.issuer
@@ -9118,7 +9134,7 @@ function missingNodeKey(surface) {
9118
9134
  `this relay-transport client cannot open ${surface}: it was given no relay node key. A relay is an ordinary node and does serve /sse and /ws \u2014 but the login statement must be signed against the node's device signing key, learned out of band (never read from the node, and never derived from its peerId). Pass it as \`observe: { nodeKey }\`. For a cloud-hosted relay the cloud does not publish it yet \u2014 see mdma #312.`
9119
9135
  );
9120
9136
  }
9121
- function buildObserver(relay, observe) {
9137
+ function buildObserver(relay, observe, fetchImpl) {
9122
9138
  const nodeKey = observe?.nodeKey;
9123
9139
  if (!nodeKey) return null;
9124
9140
  return new RelayObserver({
@@ -9129,7 +9145,7 @@ function buildObserver(relay, observe) {
9129
9145
  signer: observe?.signer ? async () => observe.signer : () => relay.authorSigner(),
9130
9146
  clientName: observe?.clientName,
9131
9147
  ttlSeconds: observe?.ttlSeconds,
9132
- fetch: observe?.fetch,
9148
+ fetch: observe?.fetch ?? fetchImpl,
9133
9149
  timeoutMs: observe?.timeoutMs
9134
9150
  });
9135
9151
  }
@@ -9150,12 +9166,16 @@ var MeroClient = class {
9150
9166
  this.proofAuthorizer = null;
9151
9167
  this.relayUrl = null;
9152
9168
  this.proofEvents = null;
9169
+ /** The relay transport's `fetch`, if one was given. */
9170
+ this.relayFetch = void 0;
9153
9171
  this.relayPresence = null;
9154
9172
  if (config.transport === "relay") {
9155
9173
  this.transport = "relay";
9156
9174
  this.nodeClient = null;
9157
- const relay = config.relay instanceof RelayClient ? config.relay : new RelayClient(config.relay);
9158
- this.relayObserver = buildObserver(relay, config.observe);
9175
+ const fetchImpl = config.fetch;
9176
+ this.relayFetch = fetchImpl;
9177
+ const relay = config.relay instanceof RelayClient ? config.relay : new RelayClient({ ...config.relay, fetch: config.relay.fetch ?? fetchImpl });
9178
+ this.relayObserver = buildObserver(relay, config.observe, fetchImpl);
9159
9179
  this.rpcTransport = new RelayTransport(relay, this.relayObserver);
9160
9180
  this.relayUrl = relay.relayUrl;
9161
9181
  if (config.proof) {
@@ -9165,13 +9185,14 @@ var MeroClient = class {
9165
9185
  const token = config.session;
9166
9186
  this.relayAdmin = createAdminApiClient({
9167
9187
  baseUrl: relay.relayUrl,
9168
- getAuthToken: typeof token === "string" ? async () => token : token
9188
+ getAuthToken: typeof token === "string" ? async () => token : token,
9189
+ fetch: fetchImpl
9169
9190
  });
9170
9191
  } else if (this.proofAuthorizer) {
9171
9192
  const authorizeRequest = this.proofAuthorizer;
9172
9193
  this.relayAdmin = createAdminApiClientFromHttpClient(
9173
9194
  createHttpClient({
9174
- fetch: (url, init) => globalThis.fetch(url, init),
9195
+ fetch: (url, init) => fetchImpl ? fetchImpl(url, init) : globalThis.fetch(url, init),
9175
9196
  baseUrl: relay.relayUrl,
9176
9197
  authorizeRequest
9177
9198
  }),
@@ -9285,7 +9306,8 @@ var MeroClient = class {
9285
9306
  if (this.proofAuthorizer && this.relayUrl) {
9286
9307
  this.proofEvents ?? (this.proofEvents = new SseClient({
9287
9308
  baseUrl: this.relayUrl,
9288
- authorize: this.proofAuthorizer
9309
+ authorize: this.proofAuthorizer,
9310
+ fetch: this.relayFetch
9289
9311
  }));
9290
9312
  return this.proofEvents;
9291
9313
  }
@@ -9535,8 +9557,8 @@ function relayQuoteVerifier(relayUrl, fetchRelay) {
9535
9557
  };
9536
9558
  return Object.assign(verify, { includeCollateral: true });
9537
9559
  }
9538
- async function attemptRelayNodeKey(relayUrl) {
9539
- const pinned = readPinnedRelayNodeKey(relayUrl);
9560
+ async function attemptRelayNodeKey(relayUrl, options = {}) {
9561
+ const pinned = options.fresh ? null : readPinnedRelayNodeKey(relayUrl);
9540
9562
  if (pinned) return { kind: "learned", nodeKey: pinned };
9541
9563
  const marked = { failure: null };
9542
9564
  const fetchRelay = relayFetch(marked);
@@ -9600,6 +9622,54 @@ function jwtExpiryMs2(token) {
9600
9622
  return null;
9601
9623
  }
9602
9624
  }
9625
+ var sealedFetches = /* @__PURE__ */ new Map();
9626
+ function forgetRelaySealing() {
9627
+ sealedFetches.clear();
9628
+ }
9629
+ async function isUnguardedRefusal(response) {
9630
+ if (response.status !== 403) return false;
9631
+ try {
9632
+ const body = await response.clone().json();
9633
+ return body.error?.code === "sealed_route_unguarded";
9634
+ } catch {
9635
+ return false;
9636
+ }
9637
+ }
9638
+ function needsSession(request) {
9639
+ return request.headers.has("authorization") || new URL(request.url).pathname.startsWith("/auth/");
9640
+ }
9641
+ function sealedRelayFetch(relayUrl) {
9642
+ const plain = (input, init) => globalThis.fetch(input, init);
9643
+ const sealed = createAttestedSealedFetch({
9644
+ baseUrl: relayUrl,
9645
+ verify: relayQuoteVerifier(relayUrl, plain),
9646
+ fetch: plain
9647
+ });
9648
+ let unguarded = false;
9649
+ return async (input, init) => {
9650
+ const request = new Request(input, init);
9651
+ if (unguarded && needsSession(request)) return plain(request);
9652
+ const response = await sealed(request.clone());
9653
+ if (!await isUnguardedRefusal(response)) return response;
9654
+ if (!unguarded) {
9655
+ unguarded = true;
9656
+ console.warn(
9657
+ `[mero-js] ${relayOrigin(relayUrl)} cannot guard logged-in calls sealed (no forward_auth); they go over its TLS until it does`
9658
+ );
9659
+ }
9660
+ return plain(request);
9661
+ };
9662
+ }
9663
+ function relayTransportFetch(relayUrl) {
9664
+ if (isLoopback(relayUrl)) return void 0;
9665
+ const origin2 = relayOrigin(relayUrl);
9666
+ let found = sealedFetches.get(origin2);
9667
+ if (!found) {
9668
+ found = sealedRelayFetch(relayUrl);
9669
+ sealedFetches.set(origin2, found);
9670
+ }
9671
+ return found;
9672
+ }
9603
9673
  function relaySession(s, nodeKey) {
9604
9674
  let token = null;
9605
9675
  let inflight = null;
@@ -9609,6 +9679,8 @@ function relaySession(s, nodeKey) {
9609
9679
  if (!inflight) {
9610
9680
  inflight = login({
9611
9681
  nodeUrl: s.relayUrl,
9682
+ // Sealed: the token is the session, and the TLS ends outside the TD.
9683
+ fetch: relayTransportFetch(s.relayUrl),
9612
9684
  node: nodeKey,
9613
9685
  deviceSecret: s.deviceSecret,
9614
9686
  accountProof: s.credential,
@@ -9704,6 +9776,9 @@ function buildDelegatedClient(s, _contextId) {
9704
9776
  const nodeKey = readPinnedRelayNodeKey(s.relayUrl);
9705
9777
  const client2 = createMeroClient({
9706
9778
  transport: "relay",
9779
+ // Every request sealed to the relay's attested TD: intents, admin reads,
9780
+ // queries, the login and events (see `relayTransportFetch`).
9781
+ fetch: relayTransportFetch(s.relayUrl),
9707
9782
  relay: {
9708
9783
  relayUrl: s.relayUrl,
9709
9784
  authorAccount: s.account,
@@ -10793,7 +10868,7 @@ function client(s, relayUrl, nonces, fetch2) {
10793
10868
  authorProof: s.credential,
10794
10869
  deviceSecret: s.deviceSecret,
10795
10870
  nonces,
10796
- fetch: fetch2
10871
+ fetch: fetch2 ?? relayTransportFetch(relayUrl)
10797
10872
  });
10798
10873
  }
10799
10874
  async function createDelegatedContext(s, req, deps = {}) {
@@ -10880,10 +10955,10 @@ async function foundingExecutor(s, relay, fetch2) {
10880
10955
  return { executorAccount: described.executorAccount, executorKey };
10881
10956
  }
10882
10957
  async function attestedNodeKey(relay) {
10883
- const key = await attemptRelayNodeKey(relay);
10958
+ const key = await attemptRelayNodeKey(relay, { fresh: true });
10884
10959
  if (key.kind === "learned") return key.nodeKey;
10885
10960
  throw new Error(
10886
- key.kind === "unavailable" ? "the relay's signing key could not be learned: its attestation did not answer, try again" : "the relay's attestation was refused, so founding through it is not possible (only a key from its operator, pinned with pinRelayNodeKey, can stand in)"
10961
+ key.kind === "unavailable" ? "the relay's signing key could not be learned: its attestation did not answer, try again" : "the relay's attestation was refused, so founding through it is not possible: it is not a signed locked-read-only release"
10887
10962
  );
10888
10963
  }
10889
10964
  var HA_ACCOUNT_NOT_LINKED_MESSAGE = "link this account to your cloud user in the wallet so invitees can find this namespace";