@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.d.ts CHANGED
@@ -28,7 +28,7 @@ export { signerFromSecret, signerFromCryptoKey } from "./signer/index.js";
28
28
  export type { Signer } from "./signer/index.js";
29
29
  export { generateAccountRoot, accountRootFromPhrase, accountRootFromSecret, createAccountRootSigner, accountRootSignerFromPhrase, signAccountLink, signAccountLogin, resolveRoot, resolveRootPair, } from "./account/index.js";
30
30
  export type { AccountRoot, RecoverableAccountRoot, AccountRootSigner, NewAccountRootSigner, AccountLinkInput, AccountLoginInput, RootSource, ResolvedRoot, } from "./account/index.js";
31
- export { readDelegatedSession, saveDelegatedSession, clearDelegatedSession, readDelegatedCredential, saveDelegatedCredential, clearDelegatedCredential, persistedNonces, markContextNonceSpent, pinRelayNodeKey, readPinnedRelayNodeKey, attemptRelayNodeKey, resolveRelayNodeKey, learnRelayNodeKey, RELAY_NODE_KEY_RETRY_MS, forgetMethodKinds, buildDelegatedClient, readRelayMap, rememberRelay, relayForContext, carryExecutorAccount, knownRelays, listDelegatedContexts, listDelegatedNamespaces, resolveRelayFromInvitation, normaliseAccount, joinWithNode, bootstrapFromInvitation, joinAsAccount, createDelegatedContext, createDelegatedPrivateContext, foundDelegatedNamespace, delegatedGovernance, latestPublishedVersion, HA_ACCOUNT_NOT_LINKED_MESSAGE, HA_REFUSAL_MESSAGES, governGroup, governRoot, namespaceOfGroup, rememberGroupNamespace, applicationIdForBundle, fetchRegistryBundles, resolveApplicationIdFromRegistry, selectLatestBundle, createAccountAdmin, InvitationNotClaimableError, NoRelayError, NotForAccountError, createNodeAdmin, } from "./account/index.js";
31
+ export { readDelegatedSession, saveDelegatedSession, clearDelegatedSession, readDelegatedCredential, saveDelegatedCredential, clearDelegatedCredential, persistedNonces, markContextNonceSpent, pinRelayNodeKey, readPinnedRelayNodeKey, attemptRelayNodeKey, resolveRelayNodeKey, learnRelayNodeKey, RELAY_NODE_KEY_RETRY_MS, forgetMethodKinds, forgetRelaySealing, relayTransportFetch, buildDelegatedClient, readRelayMap, rememberRelay, relayForContext, carryExecutorAccount, knownRelays, listDelegatedContexts, listDelegatedNamespaces, resolveRelayFromInvitation, normaliseAccount, joinWithNode, bootstrapFromInvitation, joinAsAccount, createDelegatedContext, createDelegatedPrivateContext, foundDelegatedNamespace, delegatedGovernance, latestPublishedVersion, HA_ACCOUNT_NOT_LINKED_MESSAGE, HA_REFUSAL_MESSAGES, governGroup, governRoot, namespaceOfGroup, rememberGroupNamespace, applicationIdForBundle, fetchRegistryBundles, resolveApplicationIdFromRegistry, selectLatestBundle, createAccountAdmin, InvitationNotClaimableError, NoRelayError, NotForAccountError, createNodeAdmin, } from "./account/index.js";
32
32
  export type { DelegatedCredential, DelegatedAccountSession, RelayMap, RelayNodeKeyAttempt, RelayResolutionStep, RelayResolutionFailure, ResolvedInvitationRelay, ResolveRelayResult, ResolveRelayFromInvitationInput, JoinWithNodeInput, JoinWithNodeResult, JoinWithNodeSuccess, JoinWithNodeFailure, JoinWithNodeStep, BootstrapStep, BootstrapResult, BootstrapSuccess, BootstrapFailure, BootstrapFromInvitationInput, CreateDelegatedContextRequest, FoundedDelegatedNamespace, RegistryBundle, ResolvedApplication, AccountAdminDeps, NodeAdminDeps, } from "./account/index.js";
33
33
  export { signDeviceCert, mintDeviceId, deviceCertPayload, accountForRoot, accountForRootPublicKey, parseDeviceCredential, verifyDeviceCredential, } from "./device-cert/index.js";
34
34
  export type { DeviceCertInput, DeviceCredential } from "./device-cert/index.js";
package/dist/index.js CHANGED
@@ -52,7 +52,7 @@ resolveRoot, resolveRootPair, } from "./account/index.js";
52
52
  // Framework-free; mero-react re-exports every one of these under the same name
53
53
  // (its `DelegatedSession` is `DelegatedAccountSession` here: the login module
54
54
  // already owns the bare name for the session `login()` mints).
55
- export { readDelegatedSession, saveDelegatedSession, clearDelegatedSession, readDelegatedCredential, saveDelegatedCredential, clearDelegatedCredential, persistedNonces, markContextNonceSpent, pinRelayNodeKey, readPinnedRelayNodeKey, attemptRelayNodeKey, resolveRelayNodeKey, learnRelayNodeKey, RELAY_NODE_KEY_RETRY_MS, forgetMethodKinds, buildDelegatedClient, readRelayMap, rememberRelay, relayForContext, carryExecutorAccount, knownRelays, listDelegatedContexts, listDelegatedNamespaces, resolveRelayFromInvitation, normaliseAccount, joinWithNode, bootstrapFromInvitation, joinAsAccount, createDelegatedContext, createDelegatedPrivateContext, foundDelegatedNamespace, delegatedGovernance, latestPublishedVersion, HA_ACCOUNT_NOT_LINKED_MESSAGE, HA_REFUSAL_MESSAGES, governGroup, governRoot, namespaceOfGroup, rememberGroupNamespace, applicationIdForBundle, fetchRegistryBundles, resolveApplicationIdFromRegistry, selectLatestBundle, createAccountAdmin, InvitationNotClaimableError, NoRelayError, NotForAccountError, createNodeAdmin, } from "./account/index.js";
55
+ export { readDelegatedSession, saveDelegatedSession, clearDelegatedSession, readDelegatedCredential, saveDelegatedCredential, clearDelegatedCredential, persistedNonces, markContextNonceSpent, pinRelayNodeKey, readPinnedRelayNodeKey, attemptRelayNodeKey, resolveRelayNodeKey, learnRelayNodeKey, RELAY_NODE_KEY_RETRY_MS, forgetMethodKinds, forgetRelaySealing, relayTransportFetch, buildDelegatedClient, readRelayMap, rememberRelay, relayForContext, carryExecutorAccount, knownRelays, listDelegatedContexts, listDelegatedNamespaces, resolveRelayFromInvitation, normaliseAccount, joinWithNode, bootstrapFromInvitation, joinAsAccount, createDelegatedContext, createDelegatedPrivateContext, foundDelegatedNamespace, delegatedGovernance, latestPublishedVersion, HA_ACCOUNT_NOT_LINKED_MESSAGE, HA_REFUSAL_MESSAGES, governGroup, governRoot, namespaceOfGroup, rememberGroupNamespace, applicationIdForBundle, fetchRegistryBundles, resolveApplicationIdFromRegistry, selectLatestBundle, createAccountAdmin, InvitationNotClaimableError, NoRelayError, NotForAccountError, createNodeAdmin, } from "./account/index.js";
56
56
  export { signDeviceCert, mintDeviceId, deviceCertPayload, accountForRoot, accountForRootPublicKey, parseDeviceCredential, verifyDeviceCredential, } from "./device-cert/index.js";
57
57
  // A device's scope — signed by the root beside its certificate, and what a relay
58
58
  // needs with it to bind the device of an account that has no node.
package/dist/index.mjs CHANGED
@@ -6562,6 +6562,9 @@ function createQuoteVerifier(options) {
6562
6562
  throw new Error(`The platform's TCB status is ${verified.status}, which is not accepted`);
6563
6563
  }
6564
6564
  const td = tdReport(verified.report);
6565
+ if (td.tdAttributes?.length !== 8 || (td.tdAttributes[0] & 1) !== 0) {
6566
+ throw new Error("The quote is from a debug TD, or its TD attributes cannot be read");
6567
+ }
6565
6568
  const measured = [td.mrTd, td.rtMr0, td.rtMr1, td.rtMr2, td.rtMr3].map((register) => hex(register));
6566
6569
  if (allowedImages.length > 0 && !allowedImages.includes(measured.join())) {
6567
6570
  throw new Error(
@@ -6614,7 +6617,8 @@ var TCB_STATUSES = [
6614
6617
  "OutOfDate",
6615
6618
  "OutOfDateConfigurationNeeded"
6616
6619
  ];
6617
- function trustedMeasurementsFromReleases(releases, { profile }) {
6620
+ var PRODUCTION_PROFILE = "locked-read-only";
6621
+ function trustedMeasurementsFromReleases(releases, { profile, allowDebugProfile = false }) {
6618
6622
  if (releases.length === 0) {
6619
6623
  throw new Error("No release given, so no image would be trusted");
6620
6624
  }
@@ -6629,6 +6633,11 @@ function trustedMeasurementsFromReleases(releases, { profile }) {
6629
6633
  const known = Object.keys(release?.profiles ?? {}).join(", ") || "none";
6630
6634
  throw new Error(`${name} has no profile ${JSON.stringify(profile)} (it has: ${known})`);
6631
6635
  }
6636
+ if (profile !== PRODUCTION_PROFILE && !allowDebugProfile) {
6637
+ throw new Error(
6638
+ `${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`
6639
+ );
6640
+ }
6632
6641
  const accepted = (image.allowed_tcb_statuses ?? ["uptodate"]).map((status) => tcbStatus(status, name));
6633
6642
  statuses = statuses === void 0 ? accepted : statuses.filter((status) => accepted.includes(status));
6634
6643
  return {
@@ -7117,7 +7126,10 @@ async function trustSignedRelease(options) {
7117
7126
  if (compareReleaseVersions(version, minimum) < 0) {
7118
7127
  throw new Error(`Release ${version} is older than the minimum trusted, ${minimum}`);
7119
7128
  }
7120
- return trustedMeasurementsFromReleases([published], { profile: options.profile });
7129
+ return trustedMeasurementsFromReleases([published], {
7130
+ profile: options.profile,
7131
+ allowDebugProfile: options.allowDebugProfile
7132
+ });
7121
7133
  }
7122
7134
  function createSignedReleaseVerifier(options) {
7123
7135
  releaseVersion(options.minReleaseVersion, "minReleaseVersion");
@@ -7125,6 +7137,7 @@ function createSignedReleaseVerifier(options) {
7125
7137
  const trusted = await trustSignedRelease({
7126
7138
  release: await options.release(),
7127
7139
  profile: options.profile,
7140
+ allowDebugProfile: options.allowDebugProfile,
7128
7141
  minReleaseVersion: options.minReleaseVersion,
7129
7142
  identity: options.identity,
7130
7143
  issuer: options.issuer
@@ -8836,7 +8849,7 @@ function missingNodeKey(surface) {
8836
8849
  `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.`
8837
8850
  );
8838
8851
  }
8839
- function buildObserver(relay, observe) {
8852
+ function buildObserver(relay, observe, fetchImpl) {
8840
8853
  const nodeKey = observe?.nodeKey;
8841
8854
  if (!nodeKey) return null;
8842
8855
  return new RelayObserver({
@@ -8847,7 +8860,7 @@ function buildObserver(relay, observe) {
8847
8860
  signer: observe?.signer ? async () => observe.signer : () => relay.authorSigner(),
8848
8861
  clientName: observe?.clientName,
8849
8862
  ttlSeconds: observe?.ttlSeconds,
8850
- fetch: observe?.fetch,
8863
+ fetch: observe?.fetch ?? fetchImpl,
8851
8864
  timeoutMs: observe?.timeoutMs
8852
8865
  });
8853
8866
  }
@@ -8868,12 +8881,16 @@ var MeroClient = class {
8868
8881
  this.proofAuthorizer = null;
8869
8882
  this.relayUrl = null;
8870
8883
  this.proofEvents = null;
8884
+ /** The relay transport's `fetch`, if one was given. */
8885
+ this.relayFetch = void 0;
8871
8886
  this.relayPresence = null;
8872
8887
  if (config.transport === "relay") {
8873
8888
  this.transport = "relay";
8874
8889
  this.nodeClient = null;
8875
- const relay = config.relay instanceof RelayClient ? config.relay : new RelayClient(config.relay);
8876
- this.relayObserver = buildObserver(relay, config.observe);
8890
+ const fetchImpl = config.fetch;
8891
+ this.relayFetch = fetchImpl;
8892
+ const relay = config.relay instanceof RelayClient ? config.relay : new RelayClient({ ...config.relay, fetch: config.relay.fetch ?? fetchImpl });
8893
+ this.relayObserver = buildObserver(relay, config.observe, fetchImpl);
8877
8894
  this.rpcTransport = new RelayTransport(relay, this.relayObserver);
8878
8895
  this.relayUrl = relay.relayUrl;
8879
8896
  if (config.proof) {
@@ -8883,13 +8900,14 @@ var MeroClient = class {
8883
8900
  const token = config.session;
8884
8901
  this.relayAdmin = createAdminApiClient({
8885
8902
  baseUrl: relay.relayUrl,
8886
- getAuthToken: typeof token === "string" ? async () => token : token
8903
+ getAuthToken: typeof token === "string" ? async () => token : token,
8904
+ fetch: fetchImpl
8887
8905
  });
8888
8906
  } else if (this.proofAuthorizer) {
8889
8907
  const authorizeRequest = this.proofAuthorizer;
8890
8908
  this.relayAdmin = createAdminApiClientFromHttpClient(
8891
8909
  createHttpClient({
8892
- fetch: (url, init) => globalThis.fetch(url, init),
8910
+ fetch: (url, init) => fetchImpl ? fetchImpl(url, init) : globalThis.fetch(url, init),
8893
8911
  baseUrl: relay.relayUrl,
8894
8912
  authorizeRequest
8895
8913
  }),
@@ -9003,7 +9021,8 @@ var MeroClient = class {
9003
9021
  if (this.proofAuthorizer && this.relayUrl) {
9004
9022
  this.proofEvents ?? (this.proofEvents = new SseClient({
9005
9023
  baseUrl: this.relayUrl,
9006
- authorize: this.proofAuthorizer
9024
+ authorize: this.proofAuthorizer,
9025
+ fetch: this.relayFetch
9007
9026
  }));
9008
9027
  return this.proofEvents;
9009
9028
  }
@@ -9253,8 +9272,8 @@ function relayQuoteVerifier(relayUrl, fetchRelay) {
9253
9272
  };
9254
9273
  return Object.assign(verify, { includeCollateral: true });
9255
9274
  }
9256
- async function attemptRelayNodeKey(relayUrl) {
9257
- const pinned = readPinnedRelayNodeKey(relayUrl);
9275
+ async function attemptRelayNodeKey(relayUrl, options = {}) {
9276
+ const pinned = options.fresh ? null : readPinnedRelayNodeKey(relayUrl);
9258
9277
  if (pinned) return { kind: "learned", nodeKey: pinned };
9259
9278
  const marked = { failure: null };
9260
9279
  const fetchRelay = relayFetch(marked);
@@ -9318,6 +9337,54 @@ function jwtExpiryMs2(token) {
9318
9337
  return null;
9319
9338
  }
9320
9339
  }
9340
+ var sealedFetches = /* @__PURE__ */ new Map();
9341
+ function forgetRelaySealing() {
9342
+ sealedFetches.clear();
9343
+ }
9344
+ async function isUnguardedRefusal(response) {
9345
+ if (response.status !== 403) return false;
9346
+ try {
9347
+ const body = await response.clone().json();
9348
+ return body.error?.code === "sealed_route_unguarded";
9349
+ } catch {
9350
+ return false;
9351
+ }
9352
+ }
9353
+ function needsSession(request) {
9354
+ return request.headers.has("authorization") || new URL(request.url).pathname.startsWith("/auth/");
9355
+ }
9356
+ function sealedRelayFetch(relayUrl) {
9357
+ const plain = (input, init) => globalThis.fetch(input, init);
9358
+ const sealed = createAttestedSealedFetch({
9359
+ baseUrl: relayUrl,
9360
+ verify: relayQuoteVerifier(relayUrl, plain),
9361
+ fetch: plain
9362
+ });
9363
+ let unguarded = false;
9364
+ return async (input, init) => {
9365
+ const request = new Request(input, init);
9366
+ if (unguarded && needsSession(request)) return plain(request);
9367
+ const response = await sealed(request.clone());
9368
+ if (!await isUnguardedRefusal(response)) return response;
9369
+ if (!unguarded) {
9370
+ unguarded = true;
9371
+ console.warn(
9372
+ `[mero-js] ${relayOrigin(relayUrl)} cannot guard logged-in calls sealed (no forward_auth); they go over its TLS until it does`
9373
+ );
9374
+ }
9375
+ return plain(request);
9376
+ };
9377
+ }
9378
+ function relayTransportFetch(relayUrl) {
9379
+ if (isLoopback(relayUrl)) return void 0;
9380
+ const origin2 = relayOrigin(relayUrl);
9381
+ let found = sealedFetches.get(origin2);
9382
+ if (!found) {
9383
+ found = sealedRelayFetch(relayUrl);
9384
+ sealedFetches.set(origin2, found);
9385
+ }
9386
+ return found;
9387
+ }
9321
9388
  function relaySession(s, nodeKey) {
9322
9389
  let token = null;
9323
9390
  let inflight = null;
@@ -9327,6 +9394,8 @@ function relaySession(s, nodeKey) {
9327
9394
  if (!inflight) {
9328
9395
  inflight = login({
9329
9396
  nodeUrl: s.relayUrl,
9397
+ // Sealed: the token is the session, and the TLS ends outside the TD.
9398
+ fetch: relayTransportFetch(s.relayUrl),
9330
9399
  node: nodeKey,
9331
9400
  deviceSecret: s.deviceSecret,
9332
9401
  accountProof: s.credential,
@@ -9422,6 +9491,9 @@ function buildDelegatedClient(s, _contextId) {
9422
9491
  const nodeKey = readPinnedRelayNodeKey(s.relayUrl);
9423
9492
  const client2 = createMeroClient({
9424
9493
  transport: "relay",
9494
+ // Every request sealed to the relay's attested TD: intents, admin reads,
9495
+ // queries, the login and events (see `relayTransportFetch`).
9496
+ fetch: relayTransportFetch(s.relayUrl),
9425
9497
  relay: {
9426
9498
  relayUrl: s.relayUrl,
9427
9499
  authorAccount: s.account,
@@ -10511,7 +10583,7 @@ function client(s, relayUrl, nonces, fetch2) {
10511
10583
  authorProof: s.credential,
10512
10584
  deviceSecret: s.deviceSecret,
10513
10585
  nonces,
10514
- fetch: fetch2
10586
+ fetch: fetch2 ?? relayTransportFetch(relayUrl)
10515
10587
  });
10516
10588
  }
10517
10589
  async function createDelegatedContext(s, req, deps = {}) {
@@ -10598,10 +10670,10 @@ async function foundingExecutor(s, relay, fetch2) {
10598
10670
  return { executorAccount: described.executorAccount, executorKey };
10599
10671
  }
10600
10672
  async function attestedNodeKey(relay) {
10601
- const key = await attemptRelayNodeKey(relay);
10673
+ const key = await attemptRelayNodeKey(relay, { fresh: true });
10602
10674
  if (key.kind === "learned") return key.nodeKey;
10603
10675
  throw new Error(
10604
- 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)"
10676
+ 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"
10605
10677
  );
10606
10678
  }
10607
10679
  var HA_ACCOUNT_NOT_LINKED_MESSAGE = "link this account to your cloud user in the wallet so invitees can find this namespace";
@@ -12616,6 +12688,7 @@ export {
12616
12688
  NotForAccountError,
12617
12689
  OWNER_OP_KIND,
12618
12690
  PRESENCE_DOMAIN,
12691
+ PRODUCTION_PROFILE,
12619
12692
  RELAY_NODE_KEY_RETRY_MS,
12620
12693
  REQUEST_PROOF_HEADER,
12621
12694
  RelayClient,
@@ -12721,6 +12794,7 @@ export {
12721
12794
  fetchNodeReleaseVersion,
12722
12795
  fetchRegistryBundles,
12723
12796
  forgetMethodKinds,
12797
+ forgetRelaySealing,
12724
12798
  foundDelegatedNamespace,
12725
12799
  foundedNamespaceId,
12726
12800
  generateAccountRoot,
@@ -12781,6 +12855,7 @@ export {
12781
12855
  readRelayMap,
12782
12856
  redeemInvitation,
12783
12857
  relayForContext,
12858
+ relayTransportFetch,
12784
12859
  rememberGroupNamespace,
12785
12860
  rememberRelay,
12786
12861
  reportDataOf,