@calimero-network/mero-js 29.3.0 → 29.4.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.mjs CHANGED
@@ -135,20 +135,20 @@ async function x25519KeyPair(secret) {
135
135
  const publicKey = new Uint8Array(await crypto.subtle.exportKey("raw", pair.publicKey));
136
136
  return { privateKey: pair.privateKey, publicKey };
137
137
  }
138
- const privateKey = await crypto.subtle.importKey(
138
+ const privateKey2 = await crypto.subtle.importKey(
139
139
  "pkcs8",
140
140
  concat(PKCS8_X25519_PREFIX, secret),
141
141
  { name: "X25519" },
142
142
  true,
143
143
  ["deriveBits"]
144
144
  );
145
- const jwk = await crypto.subtle.exportKey("jwk", privateKey);
146
- return { privateKey, publicKey: base64UrlDecode(jwk.x) };
145
+ const jwk = await crypto.subtle.exportKey("jwk", privateKey2);
146
+ return { privateKey: privateKey2, publicKey: base64UrlDecode(jwk.x) };
147
147
  }
148
- async function dh(privateKey, publicKey) {
148
+ async function dh(privateKey2, publicKey) {
149
149
  const peer = await crypto.subtle.importKey("raw", publicKey, { name: "X25519" }, false, []);
150
150
  return new Uint8Array(
151
- await crypto.subtle.deriveBits({ name: "X25519", public: peer }, privateKey, 256)
151
+ await crypto.subtle.deriveBits({ name: "X25519", public: peer }, privateKey2, 256)
152
152
  );
153
153
  }
154
154
  function noiseNonce(n) {
@@ -1585,6 +1585,72 @@ function browserPasskey() {
1585
1585
  };
1586
1586
  }
1587
1587
 
1588
+ // src/cloud-v2/recovery.ts
1589
+ var RECOVERY_DOMAIN = "calimero.org-recovery.v1\0";
1590
+ var MAX_RECOVERY_NONCE_BYTES = 64;
1591
+ var PKCS8_ED25519 = Uint8Array.from([
1592
+ 48,
1593
+ 46,
1594
+ 2,
1595
+ 1,
1596
+ 0,
1597
+ 48,
1598
+ 5,
1599
+ 6,
1600
+ 3,
1601
+ 43,
1602
+ 101,
1603
+ 112,
1604
+ 4,
1605
+ 34,
1606
+ 4,
1607
+ 32
1608
+ ]);
1609
+ function newRecoverySeed() {
1610
+ return crypto.getRandomValues(new Uint8Array(32));
1611
+ }
1612
+ function recoverySeedText(seed) {
1613
+ checkSeed(seed);
1614
+ const hex2 = Array.from(seed, (b) => b.toString(16).padStart(2, "0")).join("");
1615
+ const groups = [];
1616
+ for (let i = 0; i < hex2.length; i += 8) groups.push(hex2.slice(i, i + 8));
1617
+ return groups.join("-");
1618
+ }
1619
+ function parseRecoverySeed(text) {
1620
+ const digits = text.replace(/[-\s]/g, "");
1621
+ if (!/^[0-9a-fA-F]{64}$/.test(digits)) throw new Error("a recovery seed is 64 hex digits");
1622
+ return Uint8Array.from({ length: 32 }, (_, i) => parseInt(digits.slice(2 * i, 2 * i + 2), 16));
1623
+ }
1624
+ function recoveryMessage(org, account, nonce2) {
1625
+ return new TextEncoder().encode(`${RECOVERY_DOMAIN}${org}\0${account}\0${nonce2}`);
1626
+ }
1627
+ async function recoveryPublicKey(seed) {
1628
+ const key = await privateKey(seed, true);
1629
+ const jwk = await crypto.subtle.exportKey("jwk", key);
1630
+ if (!jwk.x) throw new Error("this runtime cannot export an Ed25519 public key");
1631
+ return jwk.x;
1632
+ }
1633
+ async function signRecovery(seed, org, account, nonce2) {
1634
+ const nonceBytes = new TextEncoder().encode(nonce2).length;
1635
+ if (nonceBytes === 0 || nonceBytes > MAX_RECOVERY_NONCE_BYTES) {
1636
+ throw new Error(`a recovery nonce is 1 to ${MAX_RECOVERY_NONCE_BYTES} bytes`);
1637
+ }
1638
+ const key = await privateKey(seed, false);
1639
+ const sig = await crypto.subtle.sign("Ed25519", key, buf2(recoveryMessage(org, account, nonce2)));
1640
+ return b64url(new Uint8Array(sig));
1641
+ }
1642
+ function checkSeed(seed) {
1643
+ if (seed.length !== 32) throw new Error("a recovery seed is 32 bytes");
1644
+ }
1645
+ function privateKey(seed, extractable) {
1646
+ checkSeed(seed);
1647
+ const pkcs8 = new Uint8Array(PKCS8_ED25519.length + 32);
1648
+ pkcs8.set(PKCS8_ED25519);
1649
+ pkcs8.set(seed, PKCS8_ED25519.length);
1650
+ return crypto.subtle.importKey("pkcs8", buf2(pkcs8), { name: "Ed25519" }, extractable, ["sign"]);
1651
+ }
1652
+ var buf2 = (b) => b.buffer.slice(b.byteOffset, b.byteOffset + b.byteLength);
1653
+
1588
1654
  // src/cloud-v2/approvals.ts
1589
1655
  var MAX_REPORT_PAGE = 100;
1590
1656
  function pageQuery(page = {}) {
@@ -2536,10 +2602,51 @@ var CalimeroClient = class _CalimeroClient {
2536
2602
  await this.delete(`auth/aliases/${seg(alias)}`);
2537
2603
  }
2538
2604
  // ---------------------------------------------------------------- organizations
2539
- async createOrg(name) {
2540
- const { orgId } = await this.post("orgs", { name });
2605
+ /**
2606
+ * Create an organization. `recoveryKey` (from {@link recoveryPublicKey}) lets
2607
+ * whoever holds its seed recover the organization if its owner loses every
2608
+ * passkey; keep the seed offline ({@link recoverySeedText}).
2609
+ */
2610
+ async createOrg(name, options = {}) {
2611
+ const body = { name };
2612
+ if (options.recoveryKey !== void 0) body.recoveryKey = options.recoveryKey;
2613
+ const { orgId } = await this.post("orgs", body);
2541
2614
  return orgId;
2542
2615
  }
2616
+ /**
2617
+ * Set or rotate (`publicKey`, from {@link recoveryPublicKey}) or remove
2618
+ * (`null`) the organization's recovery key: its owner only, approved with
2619
+ * `passkey`. A recovery under way is cancelled, and the old seed's
2620
+ * signatures are refused from then on.
2621
+ */
2622
+ async setRecoveryKey(org, publicKey, passkey) {
2623
+ await this.postConfirmed(`orgs/${seg(org)}/recovery`, { publicKey }, passkey);
2624
+ }
2625
+ /**
2626
+ * Start handing `org` to the signed-in account with the recovery seed,
2627
+ * which signs here and never leaves this process. Finish with
2628
+ * {@link finishRecovery} once `readyAt` has passed; until then the owner may
2629
+ * cancel. A wrong seed (or no organization, or none set) is a `403`.
2630
+ */
2631
+ async startRecovery(org, seed) {
2632
+ const account = (await this.me()).accountId;
2633
+ const nonce2 = b64url(crypto.getRandomValues(new Uint8Array(16)));
2634
+ const signature = await signRecovery(seed, org, account, nonce2);
2635
+ return this.post(`orgs/${seg(org)}/recovery/start`, { nonce: nonce2, signature });
2636
+ }
2637
+ /** Cancel the recovery under way: the organization's owner, approved with `passkey`. */
2638
+ async cancelRecovery(org, passkey) {
2639
+ await this.postConfirmed(`orgs/${seg(org)}/recovery/cancel`, {}, passkey);
2640
+ }
2641
+ /**
2642
+ * Finish a recovery this account started, once its delay passed
2643
+ * (`409 RECOVERY_NOT_READY` before, with `readyAt`), approved with this
2644
+ * account's `passkey`. The account becomes the owner; the previous owner
2645
+ * leaves the organization.
2646
+ */
2647
+ finishRecovery(org, passkey) {
2648
+ return this.postConfirmed(`orgs/${seg(org)}/recovery/finish`, {}, passkey);
2649
+ }
2543
2650
  /** The organization, with the teams this caller reaches or manages. */
2544
2651
  getOrg(org) {
2545
2652
  return this.get(`orgs/${seg(org)}`);
@@ -3386,6 +3493,7 @@ export {
3386
3493
  IdempotencyMismatch,
3387
3494
  KMS_RELEASE_SIGNER,
3388
3495
  LoginRequired,
3496
+ MAX_RECOVERY_NONCE_BYTES,
3389
3497
  MAX_REPORT_PAGE,
3390
3498
  MOCK_QUOTE_HEADER,
3391
3499
  NODE_RELEASE_SIGNER,
@@ -3395,6 +3503,7 @@ export {
3395
3503
  OriginTaken,
3396
3504
  PRODUCTION_PROFILE,
3397
3505
  QuotaExceeded,
3506
+ RECOVERY_DOMAIN,
3398
3507
  RELEASE_VERIFIER,
3399
3508
  ReleaseDeadlineError,
3400
3509
  SEALED_CONTENT_TYPE,
@@ -3428,18 +3537,24 @@ export {
3428
3537
  fromB64url,
3429
3538
  frontendManifest,
3430
3539
  generateOwnerKey,
3540
+ newRecoverySeed,
3431
3541
  orgCapabilities,
3432
3542
  ownerPublicKey,
3433
3543
  pageDeployment,
3434
3544
  pageQuery,
3545
+ parseRecoverySeed,
3435
3546
  pendingApprovalsFrom,
3436
3547
  provisionAgent,
3437
3548
  provisionInfo,
3438
3549
  publisherBindingBytes,
3439
3550
  publisherBindingMessage,
3551
+ recoveryMessage,
3552
+ recoveryPublicKey,
3553
+ recoverySeedText,
3440
3554
  releaseAgent,
3441
3555
  releaseConsent,
3442
3556
  releaseNotesUrl,
3557
+ signRecovery,
3443
3558
  stalledBy,
3444
3559
  transportKeyBinding,
3445
3560
  trustSignedRelease,