@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.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
|
|
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",
|
|
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(
|
|
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 },
|
|
151
|
+
await crypto.subtle.deriveBits({ name: "X25519", public: peer }, privateKey2, 256)
|
|
152
152
|
);
|
|
153
153
|
}
|
|
154
154
|
function noiseNonce(n) {
|
|
@@ -1418,6 +1418,21 @@ var OriginTaken = class extends CalimeroError {
|
|
|
1418
1418
|
this.name = "OriginTaken";
|
|
1419
1419
|
}
|
|
1420
1420
|
};
|
|
1421
|
+
var SignUpRefused = class extends CalimeroError {
|
|
1422
|
+
constructor(message, details = {}) {
|
|
1423
|
+
super(403, "SIGN_UP_REFUSED", message, details);
|
|
1424
|
+
this.name = "SignUpRefused";
|
|
1425
|
+
this.reason = signUpRefusal(message);
|
|
1426
|
+
}
|
|
1427
|
+
};
|
|
1428
|
+
function signUpRefusal(message) {
|
|
1429
|
+
if (/invite only/.test(message)) return "invite-only";
|
|
1430
|
+
if (/pre-authorized|sign-up invite/.test(message)) return "closed";
|
|
1431
|
+
if (/unknown invite/.test(message)) return "unknown-invite";
|
|
1432
|
+
if (/invite has expired/.test(message)) return "expired-invite";
|
|
1433
|
+
if (/organization is gone/.test(message)) return "org-gone";
|
|
1434
|
+
return "other";
|
|
1435
|
+
}
|
|
1421
1436
|
var QuotaExceeded = class extends CalimeroError {
|
|
1422
1437
|
constructor(message, details = {}) {
|
|
1423
1438
|
super(429, "QUOTA_EXCEEDED", message, details);
|
|
@@ -1492,6 +1507,8 @@ function errorFrom(status, body, retryAfterHeader) {
|
|
|
1492
1507
|
return new OriginTaken(message, details);
|
|
1493
1508
|
case "429 QUOTA_EXCEEDED":
|
|
1494
1509
|
return new QuotaExceeded(message, details);
|
|
1510
|
+
case "403 SIGN_UP_REFUSED":
|
|
1511
|
+
return new SignUpRefused(message, details);
|
|
1495
1512
|
case "412 CONFIRM_REQUIRED":
|
|
1496
1513
|
return new ConfirmRequired(message, details);
|
|
1497
1514
|
case "413 ARGS_TOO_LARGE":
|
|
@@ -1568,6 +1585,72 @@ function browserPasskey() {
|
|
|
1568
1585
|
};
|
|
1569
1586
|
}
|
|
1570
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
|
+
|
|
1571
1654
|
// src/cloud-v2/approvals.ts
|
|
1572
1655
|
var MAX_REPORT_PAGE = 100;
|
|
1573
1656
|
function pageQuery(page = {}) {
|
|
@@ -2060,23 +2143,33 @@ var CalimeroClient = class _CalimeroClient {
|
|
|
2060
2143
|
* origin's relying party (the confirmation's `rpId` is that origin's too)
|
|
2061
2144
|
* and binds the session to the package the origin is registered for; an
|
|
2062
2145
|
* unregistered origin is refused with {@link OriginNotRegistered}.
|
|
2146
|
+
*
|
|
2147
|
+
* `invite` ({@link SignUpOptions}) goes on both halves and on the retry,
|
|
2148
|
+
* as the node judges it on each before it spends anything; a sign-up the
|
|
2149
|
+
* cluster's policy refuses, or an unknown, expired or used invite, is
|
|
2150
|
+
* {@link SignUpRefused} (from `begin`, before any passkey is made).
|
|
2063
2151
|
*/
|
|
2064
2152
|
async signUp(passkey, name, options = {}) {
|
|
2065
2153
|
const origin = this.pageOrigin(options);
|
|
2154
|
+
const invite = options.invite ? { invite: options.invite } : {};
|
|
2066
2155
|
const begin = await this.begin(
|
|
2067
2156
|
"auth/passkey/register/begin",
|
|
2068
|
-
origin === void 0 ? { name } : { name, origin },
|
|
2157
|
+
origin === void 0 ? { name, ...invite } : { name, origin, ...invite },
|
|
2069
2158
|
origin
|
|
2070
2159
|
);
|
|
2071
2160
|
const registration = await passkey.create(begin.options);
|
|
2072
2161
|
const session = await this.postConfirmed(
|
|
2073
2162
|
"auth/passkey/register/finish",
|
|
2074
|
-
{ challengeId: begin.challengeId, registration },
|
|
2163
|
+
{ challengeId: begin.challengeId, registration, ...invite },
|
|
2075
2164
|
passkey
|
|
2076
2165
|
);
|
|
2077
2166
|
this.useSession(session);
|
|
2078
2167
|
return session;
|
|
2079
2168
|
}
|
|
2169
|
+
/** Who may create an account on the cluster (`GET /sign-up`); needs no session. */
|
|
2170
|
+
signUpPolicy() {
|
|
2171
|
+
return this.get("sign-up");
|
|
2172
|
+
}
|
|
2080
2173
|
/**
|
|
2081
2174
|
* Sign in with an existing passkey. On a page whose origin is not the
|
|
2082
2175
|
* cluster's ({@link SignInOptions}), the assertion is made for that
|
|
@@ -2509,10 +2602,51 @@ var CalimeroClient = class _CalimeroClient {
|
|
|
2509
2602
|
await this.delete(`auth/aliases/${seg(alias)}`);
|
|
2510
2603
|
}
|
|
2511
2604
|
// ---------------------------------------------------------------- organizations
|
|
2512
|
-
|
|
2513
|
-
|
|
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);
|
|
2514
2614
|
return orgId;
|
|
2515
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
|
+
}
|
|
2516
2650
|
/** The organization, with the teams this caller reaches or manages. */
|
|
2517
2651
|
getOrg(org) {
|
|
2518
2652
|
return this.get(`orgs/${seg(org)}`);
|
|
@@ -3359,6 +3493,7 @@ export {
|
|
|
3359
3493
|
IdempotencyMismatch,
|
|
3360
3494
|
KMS_RELEASE_SIGNER,
|
|
3361
3495
|
LoginRequired,
|
|
3496
|
+
MAX_RECOVERY_NONCE_BYTES,
|
|
3362
3497
|
MAX_REPORT_PAGE,
|
|
3363
3498
|
MOCK_QUOTE_HEADER,
|
|
3364
3499
|
NODE_RELEASE_SIGNER,
|
|
@@ -3368,11 +3503,13 @@ export {
|
|
|
3368
3503
|
OriginTaken,
|
|
3369
3504
|
PRODUCTION_PROFILE,
|
|
3370
3505
|
QuotaExceeded,
|
|
3506
|
+
RECOVERY_DOMAIN,
|
|
3371
3507
|
RELEASE_VERIFIER,
|
|
3372
3508
|
ReleaseDeadlineError,
|
|
3373
3509
|
SEALED_CONTENT_TYPE,
|
|
3374
3510
|
SEALED_PATH,
|
|
3375
3511
|
SealedTransportError,
|
|
3512
|
+
SignUpRefused,
|
|
3376
3513
|
StaleTransportKeyError,
|
|
3377
3514
|
TrapError,
|
|
3378
3515
|
UnavailableError,
|
|
@@ -3400,18 +3537,24 @@ export {
|
|
|
3400
3537
|
fromB64url,
|
|
3401
3538
|
frontendManifest,
|
|
3402
3539
|
generateOwnerKey,
|
|
3540
|
+
newRecoverySeed,
|
|
3403
3541
|
orgCapabilities,
|
|
3404
3542
|
ownerPublicKey,
|
|
3405
3543
|
pageDeployment,
|
|
3406
3544
|
pageQuery,
|
|
3545
|
+
parseRecoverySeed,
|
|
3407
3546
|
pendingApprovalsFrom,
|
|
3408
3547
|
provisionAgent,
|
|
3409
3548
|
provisionInfo,
|
|
3410
3549
|
publisherBindingBytes,
|
|
3411
3550
|
publisherBindingMessage,
|
|
3551
|
+
recoveryMessage,
|
|
3552
|
+
recoveryPublicKey,
|
|
3553
|
+
recoverySeedText,
|
|
3412
3554
|
releaseAgent,
|
|
3413
3555
|
releaseConsent,
|
|
3414
3556
|
releaseNotesUrl,
|
|
3557
|
+
signRecovery,
|
|
3415
3558
|
stalledBy,
|
|
3416
3559
|
transportKeyBinding,
|
|
3417
3560
|
trustSignedRelease,
|