@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/README.md +1 -1
- package/dist/cloud-v2/client.d.ts +52 -1
- package/dist/cloud-v2/client.js +45 -2
- package/dist/cloud-v2/index.d.ts +2 -1
- package/dist/cloud-v2/index.js +1 -0
- package/dist/cloud-v2/recovery.d.ts +23 -0
- package/dist/cloud-v2/recovery.js +70 -0
- package/dist/index.browser.mjs +3 -3
- package/dist/index.browser.mjs.map +4 -4
- package/dist/index.cjs +122 -7
- package/dist/index.cjs.map +4 -4
- package/dist/index.mjs +122 -7
- package/dist/index.mjs.map +4 -4
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -50,6 +50,7 @@ __export(index_exports, {
|
|
|
50
50
|
IdempotencyMismatch: () => IdempotencyMismatch,
|
|
51
51
|
KMS_RELEASE_SIGNER: () => KMS_RELEASE_SIGNER,
|
|
52
52
|
LoginRequired: () => LoginRequired,
|
|
53
|
+
MAX_RECOVERY_NONCE_BYTES: () => MAX_RECOVERY_NONCE_BYTES,
|
|
53
54
|
MAX_REPORT_PAGE: () => MAX_REPORT_PAGE,
|
|
54
55
|
MOCK_QUOTE_HEADER: () => MOCK_QUOTE_HEADER,
|
|
55
56
|
NODE_RELEASE_SIGNER: () => NODE_RELEASE_SIGNER,
|
|
@@ -59,6 +60,7 @@ __export(index_exports, {
|
|
|
59
60
|
OriginTaken: () => OriginTaken,
|
|
60
61
|
PRODUCTION_PROFILE: () => PRODUCTION_PROFILE,
|
|
61
62
|
QuotaExceeded: () => QuotaExceeded,
|
|
63
|
+
RECOVERY_DOMAIN: () => RECOVERY_DOMAIN,
|
|
62
64
|
RELEASE_VERIFIER: () => RELEASE_VERIFIER,
|
|
63
65
|
ReleaseDeadlineError: () => ReleaseDeadlineError,
|
|
64
66
|
SEALED_CONTENT_TYPE: () => SEALED_CONTENT_TYPE,
|
|
@@ -92,18 +94,24 @@ __export(index_exports, {
|
|
|
92
94
|
fromB64url: () => fromB64url,
|
|
93
95
|
frontendManifest: () => frontendManifest,
|
|
94
96
|
generateOwnerKey: () => generateOwnerKey,
|
|
97
|
+
newRecoverySeed: () => newRecoverySeed,
|
|
95
98
|
orgCapabilities: () => orgCapabilities,
|
|
96
99
|
ownerPublicKey: () => ownerPublicKey,
|
|
97
100
|
pageDeployment: () => pageDeployment,
|
|
98
101
|
pageQuery: () => pageQuery,
|
|
102
|
+
parseRecoverySeed: () => parseRecoverySeed,
|
|
99
103
|
pendingApprovalsFrom: () => pendingApprovalsFrom,
|
|
100
104
|
provisionAgent: () => provisionAgent,
|
|
101
105
|
provisionInfo: () => provisionInfo,
|
|
102
106
|
publisherBindingBytes: () => publisherBindingBytes,
|
|
103
107
|
publisherBindingMessage: () => publisherBindingMessage,
|
|
108
|
+
recoveryMessage: () => recoveryMessage,
|
|
109
|
+
recoveryPublicKey: () => recoveryPublicKey,
|
|
110
|
+
recoverySeedText: () => recoverySeedText,
|
|
104
111
|
releaseAgent: () => releaseAgent,
|
|
105
112
|
releaseConsent: () => releaseConsent,
|
|
106
113
|
releaseNotesUrl: () => releaseNotesUrl,
|
|
114
|
+
signRecovery: () => signRecovery,
|
|
107
115
|
stalledBy: () => stalledBy,
|
|
108
116
|
transportKeyBinding: () => transportKeyBinding,
|
|
109
117
|
trustSignedRelease: () => trustSignedRelease,
|
|
@@ -254,20 +262,20 @@ async function x25519KeyPair(secret) {
|
|
|
254
262
|
const publicKey = new Uint8Array(await crypto.subtle.exportKey("raw", pair.publicKey));
|
|
255
263
|
return { privateKey: pair.privateKey, publicKey };
|
|
256
264
|
}
|
|
257
|
-
const
|
|
265
|
+
const privateKey2 = await crypto.subtle.importKey(
|
|
258
266
|
"pkcs8",
|
|
259
267
|
concat(PKCS8_X25519_PREFIX, secret),
|
|
260
268
|
{ name: "X25519" },
|
|
261
269
|
true,
|
|
262
270
|
["deriveBits"]
|
|
263
271
|
);
|
|
264
|
-
const jwk = await crypto.subtle.exportKey("jwk",
|
|
265
|
-
return { privateKey, publicKey: base64UrlDecode(jwk.x) };
|
|
272
|
+
const jwk = await crypto.subtle.exportKey("jwk", privateKey2);
|
|
273
|
+
return { privateKey: privateKey2, publicKey: base64UrlDecode(jwk.x) };
|
|
266
274
|
}
|
|
267
|
-
async function dh(
|
|
275
|
+
async function dh(privateKey2, publicKey) {
|
|
268
276
|
const peer = await crypto.subtle.importKey("raw", publicKey, { name: "X25519" }, false, []);
|
|
269
277
|
return new Uint8Array(
|
|
270
|
-
await crypto.subtle.deriveBits({ name: "X25519", public: peer },
|
|
278
|
+
await crypto.subtle.deriveBits({ name: "X25519", public: peer }, privateKey2, 256)
|
|
271
279
|
);
|
|
272
280
|
}
|
|
273
281
|
function noiseNonce(n) {
|
|
@@ -1704,6 +1712,72 @@ function browserPasskey() {
|
|
|
1704
1712
|
};
|
|
1705
1713
|
}
|
|
1706
1714
|
|
|
1715
|
+
// src/cloud-v2/recovery.ts
|
|
1716
|
+
var RECOVERY_DOMAIN = "calimero.org-recovery.v1\0";
|
|
1717
|
+
var MAX_RECOVERY_NONCE_BYTES = 64;
|
|
1718
|
+
var PKCS8_ED25519 = Uint8Array.from([
|
|
1719
|
+
48,
|
|
1720
|
+
46,
|
|
1721
|
+
2,
|
|
1722
|
+
1,
|
|
1723
|
+
0,
|
|
1724
|
+
48,
|
|
1725
|
+
5,
|
|
1726
|
+
6,
|
|
1727
|
+
3,
|
|
1728
|
+
43,
|
|
1729
|
+
101,
|
|
1730
|
+
112,
|
|
1731
|
+
4,
|
|
1732
|
+
34,
|
|
1733
|
+
4,
|
|
1734
|
+
32
|
|
1735
|
+
]);
|
|
1736
|
+
function newRecoverySeed() {
|
|
1737
|
+
return crypto.getRandomValues(new Uint8Array(32));
|
|
1738
|
+
}
|
|
1739
|
+
function recoverySeedText(seed) {
|
|
1740
|
+
checkSeed(seed);
|
|
1741
|
+
const hex2 = Array.from(seed, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
1742
|
+
const groups = [];
|
|
1743
|
+
for (let i = 0; i < hex2.length; i += 8) groups.push(hex2.slice(i, i + 8));
|
|
1744
|
+
return groups.join("-");
|
|
1745
|
+
}
|
|
1746
|
+
function parseRecoverySeed(text) {
|
|
1747
|
+
const digits = text.replace(/[-\s]/g, "");
|
|
1748
|
+
if (!/^[0-9a-fA-F]{64}$/.test(digits)) throw new Error("a recovery seed is 64 hex digits");
|
|
1749
|
+
return Uint8Array.from({ length: 32 }, (_, i) => parseInt(digits.slice(2 * i, 2 * i + 2), 16));
|
|
1750
|
+
}
|
|
1751
|
+
function recoveryMessage(org, account, nonce2) {
|
|
1752
|
+
return new TextEncoder().encode(`${RECOVERY_DOMAIN}${org}\0${account}\0${nonce2}`);
|
|
1753
|
+
}
|
|
1754
|
+
async function recoveryPublicKey(seed) {
|
|
1755
|
+
const key = await privateKey(seed, true);
|
|
1756
|
+
const jwk = await crypto.subtle.exportKey("jwk", key);
|
|
1757
|
+
if (!jwk.x) throw new Error("this runtime cannot export an Ed25519 public key");
|
|
1758
|
+
return jwk.x;
|
|
1759
|
+
}
|
|
1760
|
+
async function signRecovery(seed, org, account, nonce2) {
|
|
1761
|
+
const nonceBytes = new TextEncoder().encode(nonce2).length;
|
|
1762
|
+
if (nonceBytes === 0 || nonceBytes > MAX_RECOVERY_NONCE_BYTES) {
|
|
1763
|
+
throw new Error(`a recovery nonce is 1 to ${MAX_RECOVERY_NONCE_BYTES} bytes`);
|
|
1764
|
+
}
|
|
1765
|
+
const key = await privateKey(seed, false);
|
|
1766
|
+
const sig = await crypto.subtle.sign("Ed25519", key, buf2(recoveryMessage(org, account, nonce2)));
|
|
1767
|
+
return b64url(new Uint8Array(sig));
|
|
1768
|
+
}
|
|
1769
|
+
function checkSeed(seed) {
|
|
1770
|
+
if (seed.length !== 32) throw new Error("a recovery seed is 32 bytes");
|
|
1771
|
+
}
|
|
1772
|
+
function privateKey(seed, extractable) {
|
|
1773
|
+
checkSeed(seed);
|
|
1774
|
+
const pkcs8 = new Uint8Array(PKCS8_ED25519.length + 32);
|
|
1775
|
+
pkcs8.set(PKCS8_ED25519);
|
|
1776
|
+
pkcs8.set(seed, PKCS8_ED25519.length);
|
|
1777
|
+
return crypto.subtle.importKey("pkcs8", buf2(pkcs8), { name: "Ed25519" }, extractable, ["sign"]);
|
|
1778
|
+
}
|
|
1779
|
+
var buf2 = (b) => b.buffer.slice(b.byteOffset, b.byteOffset + b.byteLength);
|
|
1780
|
+
|
|
1707
1781
|
// src/cloud-v2/approvals.ts
|
|
1708
1782
|
var MAX_REPORT_PAGE = 100;
|
|
1709
1783
|
function pageQuery(page = {}) {
|
|
@@ -2655,10 +2729,51 @@ var CalimeroClient = class _CalimeroClient {
|
|
|
2655
2729
|
await this.delete(`auth/aliases/${seg(alias)}`);
|
|
2656
2730
|
}
|
|
2657
2731
|
// ---------------------------------------------------------------- organizations
|
|
2658
|
-
|
|
2659
|
-
|
|
2732
|
+
/**
|
|
2733
|
+
* Create an organization. `recoveryKey` (from {@link recoveryPublicKey}) lets
|
|
2734
|
+
* whoever holds its seed recover the organization if its owner loses every
|
|
2735
|
+
* passkey; keep the seed offline ({@link recoverySeedText}).
|
|
2736
|
+
*/
|
|
2737
|
+
async createOrg(name, options = {}) {
|
|
2738
|
+
const body = { name };
|
|
2739
|
+
if (options.recoveryKey !== void 0) body.recoveryKey = options.recoveryKey;
|
|
2740
|
+
const { orgId } = await this.post("orgs", body);
|
|
2660
2741
|
return orgId;
|
|
2661
2742
|
}
|
|
2743
|
+
/**
|
|
2744
|
+
* Set or rotate (`publicKey`, from {@link recoveryPublicKey}) or remove
|
|
2745
|
+
* (`null`) the organization's recovery key: its owner only, approved with
|
|
2746
|
+
* `passkey`. A recovery under way is cancelled, and the old seed's
|
|
2747
|
+
* signatures are refused from then on.
|
|
2748
|
+
*/
|
|
2749
|
+
async setRecoveryKey(org, publicKey, passkey) {
|
|
2750
|
+
await this.postConfirmed(`orgs/${seg(org)}/recovery`, { publicKey }, passkey);
|
|
2751
|
+
}
|
|
2752
|
+
/**
|
|
2753
|
+
* Start handing `org` to the signed-in account with the recovery seed,
|
|
2754
|
+
* which signs here and never leaves this process. Finish with
|
|
2755
|
+
* {@link finishRecovery} once `readyAt` has passed; until then the owner may
|
|
2756
|
+
* cancel. A wrong seed (or no organization, or none set) is a `403`.
|
|
2757
|
+
*/
|
|
2758
|
+
async startRecovery(org, seed) {
|
|
2759
|
+
const account = (await this.me()).accountId;
|
|
2760
|
+
const nonce2 = b64url(crypto.getRandomValues(new Uint8Array(16)));
|
|
2761
|
+
const signature = await signRecovery(seed, org, account, nonce2);
|
|
2762
|
+
return this.post(`orgs/${seg(org)}/recovery/start`, { nonce: nonce2, signature });
|
|
2763
|
+
}
|
|
2764
|
+
/** Cancel the recovery under way: the organization's owner, approved with `passkey`. */
|
|
2765
|
+
async cancelRecovery(org, passkey) {
|
|
2766
|
+
await this.postConfirmed(`orgs/${seg(org)}/recovery/cancel`, {}, passkey);
|
|
2767
|
+
}
|
|
2768
|
+
/**
|
|
2769
|
+
* Finish a recovery this account started, once its delay passed
|
|
2770
|
+
* (`409 RECOVERY_NOT_READY` before, with `readyAt`), approved with this
|
|
2771
|
+
* account's `passkey`. The account becomes the owner; the previous owner
|
|
2772
|
+
* leaves the organization.
|
|
2773
|
+
*/
|
|
2774
|
+
finishRecovery(org, passkey) {
|
|
2775
|
+
return this.postConfirmed(`orgs/${seg(org)}/recovery/finish`, {}, passkey);
|
|
2776
|
+
}
|
|
2662
2777
|
/** The organization, with the teams this caller reaches or manages. */
|
|
2663
2778
|
getOrg(org) {
|
|
2664
2779
|
return this.get(`orgs/${seg(org)}`);
|