@calimero-network/mero-js 29.2.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 +2 -2
- package/dist/cloud-v2/client.d.ts +86 -4
- package/dist/cloud-v2/client.js +58 -5
- package/dist/cloud-v2/errors.d.ts +20 -0
- package/dist/cloud-v2/errors.js +36 -0
- package/dist/cloud-v2/index.d.ts +4 -3
- package/dist/cloud-v2/index.js +2 -1
- 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 +152 -9
- package/dist/index.cjs.map +4 -4
- package/dist/index.mjs +152 -9
- 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,11 +60,13 @@ __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,
|
|
65
67
|
SEALED_PATH: () => SEALED_PATH,
|
|
66
68
|
SealedTransportError: () => SealedTransportError,
|
|
69
|
+
SignUpRefused: () => SignUpRefused,
|
|
67
70
|
StaleTransportKeyError: () => StaleTransportKeyError,
|
|
68
71
|
TrapError: () => TrapError,
|
|
69
72
|
UnavailableError: () => UnavailableError,
|
|
@@ -91,18 +94,24 @@ __export(index_exports, {
|
|
|
91
94
|
fromB64url: () => fromB64url,
|
|
92
95
|
frontendManifest: () => frontendManifest,
|
|
93
96
|
generateOwnerKey: () => generateOwnerKey,
|
|
97
|
+
newRecoverySeed: () => newRecoverySeed,
|
|
94
98
|
orgCapabilities: () => orgCapabilities,
|
|
95
99
|
ownerPublicKey: () => ownerPublicKey,
|
|
96
100
|
pageDeployment: () => pageDeployment,
|
|
97
101
|
pageQuery: () => pageQuery,
|
|
102
|
+
parseRecoverySeed: () => parseRecoverySeed,
|
|
98
103
|
pendingApprovalsFrom: () => pendingApprovalsFrom,
|
|
99
104
|
provisionAgent: () => provisionAgent,
|
|
100
105
|
provisionInfo: () => provisionInfo,
|
|
101
106
|
publisherBindingBytes: () => publisherBindingBytes,
|
|
102
107
|
publisherBindingMessage: () => publisherBindingMessage,
|
|
108
|
+
recoveryMessage: () => recoveryMessage,
|
|
109
|
+
recoveryPublicKey: () => recoveryPublicKey,
|
|
110
|
+
recoverySeedText: () => recoverySeedText,
|
|
103
111
|
releaseAgent: () => releaseAgent,
|
|
104
112
|
releaseConsent: () => releaseConsent,
|
|
105
113
|
releaseNotesUrl: () => releaseNotesUrl,
|
|
114
|
+
signRecovery: () => signRecovery,
|
|
106
115
|
stalledBy: () => stalledBy,
|
|
107
116
|
transportKeyBinding: () => transportKeyBinding,
|
|
108
117
|
trustSignedRelease: () => trustSignedRelease,
|
|
@@ -253,20 +262,20 @@ async function x25519KeyPair(secret) {
|
|
|
253
262
|
const publicKey = new Uint8Array(await crypto.subtle.exportKey("raw", pair.publicKey));
|
|
254
263
|
return { privateKey: pair.privateKey, publicKey };
|
|
255
264
|
}
|
|
256
|
-
const
|
|
265
|
+
const privateKey2 = await crypto.subtle.importKey(
|
|
257
266
|
"pkcs8",
|
|
258
267
|
concat(PKCS8_X25519_PREFIX, secret),
|
|
259
268
|
{ name: "X25519" },
|
|
260
269
|
true,
|
|
261
270
|
["deriveBits"]
|
|
262
271
|
);
|
|
263
|
-
const jwk = await crypto.subtle.exportKey("jwk",
|
|
264
|
-
return { privateKey, publicKey: base64UrlDecode(jwk.x) };
|
|
272
|
+
const jwk = await crypto.subtle.exportKey("jwk", privateKey2);
|
|
273
|
+
return { privateKey: privateKey2, publicKey: base64UrlDecode(jwk.x) };
|
|
265
274
|
}
|
|
266
|
-
async function dh(
|
|
275
|
+
async function dh(privateKey2, publicKey) {
|
|
267
276
|
const peer = await crypto.subtle.importKey("raw", publicKey, { name: "X25519" }, false, []);
|
|
268
277
|
return new Uint8Array(
|
|
269
|
-
await crypto.subtle.deriveBits({ name: "X25519", public: peer },
|
|
278
|
+
await crypto.subtle.deriveBits({ name: "X25519", public: peer }, privateKey2, 256)
|
|
270
279
|
);
|
|
271
280
|
}
|
|
272
281
|
function noiseNonce(n) {
|
|
@@ -1536,6 +1545,21 @@ var OriginTaken = class extends CalimeroError {
|
|
|
1536
1545
|
this.name = "OriginTaken";
|
|
1537
1546
|
}
|
|
1538
1547
|
};
|
|
1548
|
+
var SignUpRefused = class extends CalimeroError {
|
|
1549
|
+
constructor(message, details = {}) {
|
|
1550
|
+
super(403, "SIGN_UP_REFUSED", message, details);
|
|
1551
|
+
this.name = "SignUpRefused";
|
|
1552
|
+
this.reason = signUpRefusal(message);
|
|
1553
|
+
}
|
|
1554
|
+
};
|
|
1555
|
+
function signUpRefusal(message) {
|
|
1556
|
+
if (/invite only/.test(message)) return "invite-only";
|
|
1557
|
+
if (/pre-authorized|sign-up invite/.test(message)) return "closed";
|
|
1558
|
+
if (/unknown invite/.test(message)) return "unknown-invite";
|
|
1559
|
+
if (/invite has expired/.test(message)) return "expired-invite";
|
|
1560
|
+
if (/organization is gone/.test(message)) return "org-gone";
|
|
1561
|
+
return "other";
|
|
1562
|
+
}
|
|
1539
1563
|
var QuotaExceeded = class extends CalimeroError {
|
|
1540
1564
|
constructor(message, details = {}) {
|
|
1541
1565
|
super(429, "QUOTA_EXCEEDED", message, details);
|
|
@@ -1610,6 +1634,8 @@ function errorFrom(status, body, retryAfterHeader) {
|
|
|
1610
1634
|
return new OriginTaken(message, details);
|
|
1611
1635
|
case "429 QUOTA_EXCEEDED":
|
|
1612
1636
|
return new QuotaExceeded(message, details);
|
|
1637
|
+
case "403 SIGN_UP_REFUSED":
|
|
1638
|
+
return new SignUpRefused(message, details);
|
|
1613
1639
|
case "412 CONFIRM_REQUIRED":
|
|
1614
1640
|
return new ConfirmRequired(message, details);
|
|
1615
1641
|
case "413 ARGS_TOO_LARGE":
|
|
@@ -1686,6 +1712,72 @@ function browserPasskey() {
|
|
|
1686
1712
|
};
|
|
1687
1713
|
}
|
|
1688
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
|
+
|
|
1689
1781
|
// src/cloud-v2/approvals.ts
|
|
1690
1782
|
var MAX_REPORT_PAGE = 100;
|
|
1691
1783
|
function pageQuery(page = {}) {
|
|
@@ -2178,23 +2270,33 @@ var CalimeroClient = class _CalimeroClient {
|
|
|
2178
2270
|
* origin's relying party (the confirmation's `rpId` is that origin's too)
|
|
2179
2271
|
* and binds the session to the package the origin is registered for; an
|
|
2180
2272
|
* unregistered origin is refused with {@link OriginNotRegistered}.
|
|
2273
|
+
*
|
|
2274
|
+
* `invite` ({@link SignUpOptions}) goes on both halves and on the retry,
|
|
2275
|
+
* as the node judges it on each before it spends anything; a sign-up the
|
|
2276
|
+
* cluster's policy refuses, or an unknown, expired or used invite, is
|
|
2277
|
+
* {@link SignUpRefused} (from `begin`, before any passkey is made).
|
|
2181
2278
|
*/
|
|
2182
2279
|
async signUp(passkey, name, options = {}) {
|
|
2183
2280
|
const origin = this.pageOrigin(options);
|
|
2281
|
+
const invite = options.invite ? { invite: options.invite } : {};
|
|
2184
2282
|
const begin = await this.begin(
|
|
2185
2283
|
"auth/passkey/register/begin",
|
|
2186
|
-
origin === void 0 ? { name } : { name, origin },
|
|
2284
|
+
origin === void 0 ? { name, ...invite } : { name, origin, ...invite },
|
|
2187
2285
|
origin
|
|
2188
2286
|
);
|
|
2189
2287
|
const registration = await passkey.create(begin.options);
|
|
2190
2288
|
const session = await this.postConfirmed(
|
|
2191
2289
|
"auth/passkey/register/finish",
|
|
2192
|
-
{ challengeId: begin.challengeId, registration },
|
|
2290
|
+
{ challengeId: begin.challengeId, registration, ...invite },
|
|
2193
2291
|
passkey
|
|
2194
2292
|
);
|
|
2195
2293
|
this.useSession(session);
|
|
2196
2294
|
return session;
|
|
2197
2295
|
}
|
|
2296
|
+
/** Who may create an account on the cluster (`GET /sign-up`); needs no session. */
|
|
2297
|
+
signUpPolicy() {
|
|
2298
|
+
return this.get("sign-up");
|
|
2299
|
+
}
|
|
2198
2300
|
/**
|
|
2199
2301
|
* Sign in with an existing passkey. On a page whose origin is not the
|
|
2200
2302
|
* cluster's ({@link SignInOptions}), the assertion is made for that
|
|
@@ -2627,10 +2729,51 @@ var CalimeroClient = class _CalimeroClient {
|
|
|
2627
2729
|
await this.delete(`auth/aliases/${seg(alias)}`);
|
|
2628
2730
|
}
|
|
2629
2731
|
// ---------------------------------------------------------------- organizations
|
|
2630
|
-
|
|
2631
|
-
|
|
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);
|
|
2632
2741
|
return orgId;
|
|
2633
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
|
+
}
|
|
2634
2777
|
/** The organization, with the teams this caller reaches or manages. */
|
|
2635
2778
|
getOrg(org) {
|
|
2636
2779
|
return this.get(`orgs/${seg(org)}`);
|