@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/README.md
CHANGED
|
@@ -97,13 +97,13 @@ Pinned measurements go through `policy.verifyQuote` (`createQuoteVerifier`).
|
|
|
97
97
|
|
|
98
98
|
## The API
|
|
99
99
|
|
|
100
|
-
- **Credentials**: `signUp`, `signIn`, `refresh`, `signOut`, `signOutEverywhere`, `useSession`, `useApiKey`, `me`, `usage`, `addPasskey`, `labelPasskey`, `removePasskey`, `createApiKey`, `listApiKeys`, `revokeApiKey`. Adding or removing a passkey and creating an API key are approved with a passkey the account already has.
|
|
100
|
+
- **Credentials**: `signUp` (with an `invite`, on a cluster that takes new accounts by invite; `signUpPolicy` reads which), `signIn`, `refresh`, `signOut`, `signOutEverywhere`, `useSession`, `useApiKey`, `me`, `usage`, `addPasskey`, `labelPasskey`, `removePasskey`, `createApiKey`, `listApiKeys`, `revokeApiKey`. Adding or removing a passkey and creating an API key are approved with a passkey the account already has.
|
|
101
101
|
- **Package origins**: `packageOrigins.list`, `packageOrigins.add`, `packageOrigins.remove`. A package's managers register the web origins of a frontend they host themselves (approved with a passkey); `signUp` and `signIn` on such a page send its origin, sign for that origin's relying-party id, and open a session bound to the package (`me()` answers its `app` and `origin`). An unregistered origin is refused with `OriginNotRegistered`.
|
|
102
102
|
- **Packages**: `installPackage`, `uploadPackage`, `packages`, `package`, `uninstallPackage`; `uploadBundle` (raw WASM, development clusters only).
|
|
103
103
|
- **Apps**: `createApp`, `listApps`, `getApp`, `deleteApp`, `setAccess`, `setAppMetadata`, `appStorage`, `appAbi`, `upgradeApp`, `transferApp`.
|
|
104
104
|
- **Calls**: `call`, `callConfirmed`, `view`, `events`. A call always carries an idempotency key, and is sent again with it while the node answers `503`, then to the next node.
|
|
105
105
|
- **Blobs and presence**: `uploadBlob`, `downloadBlob`, `listBlobs`, `blobInfo`, `deleteBlob`; `publishPresence`, `presence`, `presenceStream`.
|
|
106
|
-
- **Organizations**: `createOrg`, `getOrg`, `deleteOrg`, `setOrgMetadata`, `setMember`, `removeMember`, `setMemberMetadata`, `setCapabilities` (`ORG_CAPABILITIES`), teams, `invite`, `redeemInvite
|
|
106
|
+
- **Organizations**: `createOrg`, `getOrg`, `deleteOrg`, `setOrgMetadata`, `setMember`, `removeMember`, `setMemberMetadata`, `setCapabilities` (`ORG_CAPABILITIES`), teams, `invite`, `redeemInvite`, recovery (`setRecoveryKey`, `startRecovery`, `cancelRecovery`, `finishRecovery`; `newRecoverySeed`, `recoveryPublicKey`). Setting or removing a member or team member, capabilities and invites are approved with the caller's passkey; redeeming an invite is not.
|
|
107
107
|
- **Owner decisions** (passkey approval, never from an app's frontend): `upgradeOrg`, `upgradeRun`, `retryUpgradeRun`, `approveRelease`, `approveOutbound`, `bindPublisher`, `upgradePublished`.
|
|
108
108
|
- **Approvals from an app**: `appsReport`, `releaseRules`, `pendingApprovals`, then `requestConsent` / `openConsentPage` to send the user to the node's consent page, and `consentResult`. Show the answer's `code` to the user before sending them to `url` (`openConsentPage`'s `confirm` hook); they should see the same code on the page (`confirmationCode(requestId)` checks it). See [Ask your users to approve node images and app upgrades](https://calimero-network.github.io/mero-js/guides/user-approvals/).
|
|
109
109
|
- **Operator** (ops token): `opsApps`, `opsOrgs`, `setReleaseDeadline`, `liftReleaseDeadline`.
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
import { type AttestationDoc, type CloudPolicy } from './attestation.js';
|
|
13
13
|
import { type PasskeyProvider } from './passkey.js';
|
|
14
14
|
import { type AppsReport, type ConsentRequest, type ConsentResult, type ConsentStarted, type OpenConsentOptions, type OpsAppsReport, type OpsOrgsReport, type PageOptions, type PendingApprovals, type PublisherBindingInfo, type PublisherProof, type ReleaseRules } from './approvals.js';
|
|
15
|
-
export { AppError, ArgsTooLarge, BadOrigin, CalimeroError, ConfirmCancelled, ConfirmRequired, IdempotencyMismatch, LoginRequired, ConsentStale, NotFound, OriginNotRegistered, OriginTaken, QuotaExceeded, ReleaseDeadlineError, TrapError, UnavailableError, WrongNode, errorFrom, } from './errors.js';
|
|
16
|
-
export type { Committed } from './errors.js';
|
|
15
|
+
export { AppError, ArgsTooLarge, BadOrigin, CalimeroError, ConfirmCancelled, ConfirmRequired, IdempotencyMismatch, LoginRequired, ConsentStale, NotFound, OriginNotRegistered, OriginTaken, QuotaExceeded, ReleaseDeadlineError, SignUpRefused, TrapError, UnavailableError, WrongNode, errorFrom, } from './errors.js';
|
|
16
|
+
export type { Committed, SignUpRefusal } from './errors.js';
|
|
17
17
|
export interface CalimeroSession {
|
|
18
18
|
accountId: string;
|
|
19
19
|
sessionToken: string;
|
|
@@ -131,6 +131,24 @@ export interface OrgInfo extends Record<string, unknown> {
|
|
|
131
131
|
release: string;
|
|
132
132
|
after: string;
|
|
133
133
|
} | null;
|
|
134
|
+
/** The recovery key and any recovery under way; absent while the organization has none. */
|
|
135
|
+
recovery?: OrgRecovery;
|
|
136
|
+
}
|
|
137
|
+
/** An organization's recovery key (core `Org::recovery`). */
|
|
138
|
+
export interface OrgRecovery {
|
|
139
|
+
/** The key's Ed25519 public half, base64url. */
|
|
140
|
+
publicKey: string;
|
|
141
|
+
/** When the owner set it (Unix seconds). */
|
|
142
|
+
setAt: number;
|
|
143
|
+
/** A recovery under way: warn every member, the owner first, who may cancel it. */
|
|
144
|
+
pending: RecoveryTimes & {
|
|
145
|
+
account: string;
|
|
146
|
+
} | null;
|
|
147
|
+
}
|
|
148
|
+
/** When a recovery started and the earliest it may finish (Unix seconds). */
|
|
149
|
+
export interface RecoveryTimes {
|
|
150
|
+
startedAt: number;
|
|
151
|
+
readyAt: number;
|
|
134
152
|
}
|
|
135
153
|
/** A blob's bytes with the content type the node serves them as. */
|
|
136
154
|
export interface BlobContent {
|
|
@@ -338,6 +356,30 @@ export interface PackageOrigins {
|
|
|
338
356
|
export interface SignInOptions {
|
|
339
357
|
origin?: string | null;
|
|
340
358
|
}
|
|
359
|
+
/**
|
|
360
|
+
* {@link SignInOptions} for a sign-up, and the invite it is made with: an
|
|
361
|
+
* organization invite's token (the new account joins the organization at
|
|
362
|
+
* the invite's role) or a cluster sign-up invite's. A cluster whose policy
|
|
363
|
+
* is `invite` or `closed` ({@link CalimeroClient.signUpPolicy}) needs one.
|
|
364
|
+
*/
|
|
365
|
+
export interface SignUpOptions extends SignInOptions {
|
|
366
|
+
invite?: string;
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* `GET /sign-up`: who may create an account on the cluster (core's sign-up
|
|
370
|
+
* policy, read without a session), so a page can say "invite only" before
|
|
371
|
+
* any passkey prompt.
|
|
372
|
+
*/
|
|
373
|
+
export interface SignUpPolicy {
|
|
374
|
+
/** `open` (anyone), `invite` (an organization or sign-up invite), `closed` (a sign-up invite only). */
|
|
375
|
+
policy: 'open' | 'invite' | 'closed';
|
|
376
|
+
/** Whether an organization invite lets a new person sign up (and join it). */
|
|
377
|
+
orgInvites: boolean;
|
|
378
|
+
/** Whether a cluster sign-up invite does (always). */
|
|
379
|
+
signUpInvites: boolean;
|
|
380
|
+
/** Whether a sign-up without an invite is refused. */
|
|
381
|
+
inviteRequired: boolean;
|
|
382
|
+
}
|
|
341
383
|
/**
|
|
342
384
|
* How a call or a view is sent again while the node is unavailable (D70): with
|
|
343
385
|
* the same key, after a backoff that starts at `baseDelayMs` and doubles up to
|
|
@@ -520,8 +562,15 @@ export declare class CalimeroClient {
|
|
|
520
562
|
* origin's relying party (the confirmation's `rpId` is that origin's too)
|
|
521
563
|
* and binds the session to the package the origin is registered for; an
|
|
522
564
|
* unregistered origin is refused with {@link OriginNotRegistered}.
|
|
565
|
+
*
|
|
566
|
+
* `invite` ({@link SignUpOptions}) goes on both halves and on the retry,
|
|
567
|
+
* as the node judges it on each before it spends anything; a sign-up the
|
|
568
|
+
* cluster's policy refuses, or an unknown, expired or used invite, is
|
|
569
|
+
* {@link SignUpRefused} (from `begin`, before any passkey is made).
|
|
523
570
|
*/
|
|
524
|
-
signUp(passkey: PasskeyProvider, name: string, options?:
|
|
571
|
+
signUp(passkey: PasskeyProvider, name: string, options?: SignUpOptions): Promise<CalimeroSession>;
|
|
572
|
+
/** Who may create an account on the cluster (`GET /sign-up`); needs no session. */
|
|
573
|
+
signUpPolicy(): Promise<SignUpPolicy>;
|
|
525
574
|
/**
|
|
526
575
|
* Sign in with an existing passkey. On a page whose origin is not the
|
|
527
576
|
* cluster's ({@link SignInOptions}), the assertion is made for that
|
|
@@ -775,7 +824,40 @@ export declare class CalimeroClient {
|
|
|
775
824
|
/** The app an alias names. */
|
|
776
825
|
alias(alias: string): Promise<string>;
|
|
777
826
|
deleteAlias(alias: string): Promise<void>;
|
|
778
|
-
|
|
827
|
+
/**
|
|
828
|
+
* Create an organization. `recoveryKey` (from {@link recoveryPublicKey}) lets
|
|
829
|
+
* whoever holds its seed recover the organization if its owner loses every
|
|
830
|
+
* passkey; keep the seed offline ({@link recoverySeedText}).
|
|
831
|
+
*/
|
|
832
|
+
createOrg(name: string, options?: {
|
|
833
|
+
recoveryKey?: string;
|
|
834
|
+
}): Promise<string>;
|
|
835
|
+
/**
|
|
836
|
+
* Set or rotate (`publicKey`, from {@link recoveryPublicKey}) or remove
|
|
837
|
+
* (`null`) the organization's recovery key: its owner only, approved with
|
|
838
|
+
* `passkey`. A recovery under way is cancelled, and the old seed's
|
|
839
|
+
* signatures are refused from then on.
|
|
840
|
+
*/
|
|
841
|
+
setRecoveryKey(org: string, publicKey: string | null, passkey: PasskeyProvider): Promise<void>;
|
|
842
|
+
/**
|
|
843
|
+
* Start handing `org` to the signed-in account with the recovery seed,
|
|
844
|
+
* which signs here and never leaves this process. Finish with
|
|
845
|
+
* {@link finishRecovery} once `readyAt` has passed; until then the owner may
|
|
846
|
+
* cancel. A wrong seed (or no organization, or none set) is a `403`.
|
|
847
|
+
*/
|
|
848
|
+
startRecovery(org: string, seed: Uint8Array): Promise<RecoveryTimes>;
|
|
849
|
+
/** Cancel the recovery under way: the organization's owner, approved with `passkey`. */
|
|
850
|
+
cancelRecovery(org: string, passkey: PasskeyProvider): Promise<void>;
|
|
851
|
+
/**
|
|
852
|
+
* Finish a recovery this account started, once its delay passed
|
|
853
|
+
* (`409 RECOVERY_NOT_READY` before, with `readyAt`), approved with this
|
|
854
|
+
* account's `passkey`. The account becomes the owner; the previous owner
|
|
855
|
+
* leaves the organization.
|
|
856
|
+
*/
|
|
857
|
+
finishRecovery(org: string, passkey: PasskeyProvider): Promise<{
|
|
858
|
+
org: string;
|
|
859
|
+
previousOwners: string[];
|
|
860
|
+
}>;
|
|
779
861
|
/** The organization, with the teams this caller reaches or manages. */
|
|
780
862
|
getOrg(org: string): Promise<OrgInfo>;
|
|
781
863
|
/**
|
package/dist/cloud-v2/client.js
CHANGED
|
@@ -14,8 +14,9 @@ import { fetchAttestedKey } from './attestation.js';
|
|
|
14
14
|
import { DEFAULT_DIRECTORY, orderNodes, readDirectory } from './directory.js';
|
|
15
15
|
import { CalimeroError, ConfirmCancelled, ConfirmRequired, LoginRequired, OriginNotRegistered, UnavailableError, WrongNode, errorFrom, } from './errors.js';
|
|
16
16
|
import { b64url, fromB64url } from './passkey.js';
|
|
17
|
+
import { signRecovery } from './recovery.js';
|
|
17
18
|
import { confirmationCode, pageQuery, pendingApprovalsFrom, } from './approvals.js';
|
|
18
|
-
export { AppError, ArgsTooLarge, BadOrigin, CalimeroError, ConfirmCancelled, ConfirmRequired, IdempotencyMismatch, LoginRequired, ConsentStale, NotFound, OriginNotRegistered, OriginTaken, QuotaExceeded, ReleaseDeadlineError, TrapError, UnavailableError, WrongNode, errorFrom, } from './errors.js';
|
|
19
|
+
export { AppError, ArgsTooLarge, BadOrigin, CalimeroError, ConfirmCancelled, ConfirmRequired, IdempotencyMismatch, LoginRequired, ConsentStale, NotFound, OriginNotRegistered, OriginTaken, QuotaExceeded, ReleaseDeadlineError, SignUpRefused, TrapError, UnavailableError, WrongNode, errorFrom, } from './errors.js';
|
|
19
20
|
/** A deployer's key, as `GET /deployers` lists it and `DELETE /deployers/{key}` takes it. */
|
|
20
21
|
export function deployerKey(deployer) {
|
|
21
22
|
return 'account' in deployer ? `account:${deployer.account}` : `org:${deployer.org}`;
|
|
@@ -462,15 +463,25 @@ export class CalimeroClient {
|
|
|
462
463
|
* origin's relying party (the confirmation's `rpId` is that origin's too)
|
|
463
464
|
* and binds the session to the package the origin is registered for; an
|
|
464
465
|
* unregistered origin is refused with {@link OriginNotRegistered}.
|
|
466
|
+
*
|
|
467
|
+
* `invite` ({@link SignUpOptions}) goes on both halves and on the retry,
|
|
468
|
+
* as the node judges it on each before it spends anything; a sign-up the
|
|
469
|
+
* cluster's policy refuses, or an unknown, expired or used invite, is
|
|
470
|
+
* {@link SignUpRefused} (from `begin`, before any passkey is made).
|
|
465
471
|
*/
|
|
466
472
|
async signUp(passkey, name, options = {}) {
|
|
467
473
|
const origin = this.pageOrigin(options);
|
|
468
|
-
const
|
|
474
|
+
const invite = options.invite ? { invite: options.invite } : {};
|
|
475
|
+
const begin = await this.begin('auth/passkey/register/begin', origin === undefined ? { name, ...invite } : { name, origin, ...invite }, origin);
|
|
469
476
|
const registration = await passkey.create(begin.options);
|
|
470
|
-
const session = await this.postConfirmed('auth/passkey/register/finish', { challengeId: begin.challengeId, registration }, passkey);
|
|
477
|
+
const session = await this.postConfirmed('auth/passkey/register/finish', { challengeId: begin.challengeId, registration, ...invite }, passkey);
|
|
471
478
|
this.useSession(session);
|
|
472
479
|
return session;
|
|
473
480
|
}
|
|
481
|
+
/** Who may create an account on the cluster (`GET /sign-up`); needs no session. */
|
|
482
|
+
signUpPolicy() {
|
|
483
|
+
return this.get('sign-up');
|
|
484
|
+
}
|
|
474
485
|
/**
|
|
475
486
|
* Sign in with an existing passkey. On a page whose origin is not the
|
|
476
487
|
* cluster's ({@link SignInOptions}), the assertion is made for that
|
|
@@ -916,10 +927,52 @@ export class CalimeroClient {
|
|
|
916
927
|
await this.delete(`auth/aliases/${seg(alias)}`);
|
|
917
928
|
}
|
|
918
929
|
// ---------------------------------------------------------------- organizations
|
|
919
|
-
|
|
920
|
-
|
|
930
|
+
/**
|
|
931
|
+
* Create an organization. `recoveryKey` (from {@link recoveryPublicKey}) lets
|
|
932
|
+
* whoever holds its seed recover the organization if its owner loses every
|
|
933
|
+
* passkey; keep the seed offline ({@link recoverySeedText}).
|
|
934
|
+
*/
|
|
935
|
+
async createOrg(name, options = {}) {
|
|
936
|
+
const body = { name };
|
|
937
|
+
if (options.recoveryKey !== undefined)
|
|
938
|
+
body.recoveryKey = options.recoveryKey;
|
|
939
|
+
const { orgId } = await this.post('orgs', body);
|
|
921
940
|
return orgId;
|
|
922
941
|
}
|
|
942
|
+
/**
|
|
943
|
+
* Set or rotate (`publicKey`, from {@link recoveryPublicKey}) or remove
|
|
944
|
+
* (`null`) the organization's recovery key: its owner only, approved with
|
|
945
|
+
* `passkey`. A recovery under way is cancelled, and the old seed's
|
|
946
|
+
* signatures are refused from then on.
|
|
947
|
+
*/
|
|
948
|
+
async setRecoveryKey(org, publicKey, passkey) {
|
|
949
|
+
await this.postConfirmed(`orgs/${seg(org)}/recovery`, { publicKey }, passkey);
|
|
950
|
+
}
|
|
951
|
+
/**
|
|
952
|
+
* Start handing `org` to the signed-in account with the recovery seed,
|
|
953
|
+
* which signs here and never leaves this process. Finish with
|
|
954
|
+
* {@link finishRecovery} once `readyAt` has passed; until then the owner may
|
|
955
|
+
* cancel. A wrong seed (or no organization, or none set) is a `403`.
|
|
956
|
+
*/
|
|
957
|
+
async startRecovery(org, seed) {
|
|
958
|
+
const account = (await this.me()).accountId;
|
|
959
|
+
const nonce = b64url(crypto.getRandomValues(new Uint8Array(16)));
|
|
960
|
+
const signature = await signRecovery(seed, org, account, nonce);
|
|
961
|
+
return this.post(`orgs/${seg(org)}/recovery/start`, { nonce, signature });
|
|
962
|
+
}
|
|
963
|
+
/** Cancel the recovery under way: the organization's owner, approved with `passkey`. */
|
|
964
|
+
async cancelRecovery(org, passkey) {
|
|
965
|
+
await this.postConfirmed(`orgs/${seg(org)}/recovery/cancel`, {}, passkey);
|
|
966
|
+
}
|
|
967
|
+
/**
|
|
968
|
+
* Finish a recovery this account started, once its delay passed
|
|
969
|
+
* (`409 RECOVERY_NOT_READY` before, with `readyAt`), approved with this
|
|
970
|
+
* account's `passkey`. The account becomes the owner; the previous owner
|
|
971
|
+
* leaves the organization.
|
|
972
|
+
*/
|
|
973
|
+
finishRecovery(org, passkey) {
|
|
974
|
+
return this.postConfirmed(`orgs/${seg(org)}/recovery/finish`, {}, passkey);
|
|
975
|
+
}
|
|
923
976
|
/** The organization, with the teams this caller reaches or manages. */
|
|
924
977
|
getOrg(org) {
|
|
925
978
|
return this.get(`orgs/${seg(org)}`);
|
|
@@ -126,6 +126,26 @@ export declare class BadOrigin extends CalimeroError {
|
|
|
126
126
|
export declare class OriginTaken extends CalimeroError {
|
|
127
127
|
constructor(message: string, details?: Record<string, unknown>);
|
|
128
128
|
}
|
|
129
|
+
/** Why a sign-up was refused ({@link SignUpRefused.reason}), read from what the node said. */
|
|
130
|
+
export type SignUpRefusal = 'invite-only' | 'closed' | 'unknown-invite' | 'expired-invite' | 'org-gone' | 'other';
|
|
131
|
+
/**
|
|
132
|
+
* `403 SIGN_UP_REFUSED`: the cluster's sign-up policy does not take this
|
|
133
|
+
* sign-up (core `SystemState::admits`), answered by `register/begin` before a
|
|
134
|
+
* passkey is made and by `register/finish` before its challenge is spent.
|
|
135
|
+
* `reason` says why, so a page can say "invite only" or "this invite has
|
|
136
|
+
* expired" instead of the node's sentence:
|
|
137
|
+
*
|
|
138
|
+
* - `invite-only`: the cluster takes new accounts with an invite only.
|
|
139
|
+
* - `closed`: only a cluster sign-up invite (the operator's or the owner's) admits.
|
|
140
|
+
* - `unknown-invite`: the invite is not one the cluster knows (mistyped, revoked,
|
|
141
|
+
* or used up: a spent invite is gone).
|
|
142
|
+
* - `expired-invite`: past its expiry, or no use left.
|
|
143
|
+
* - `org-gone`: the organization the invite was for was deleted.
|
|
144
|
+
*/
|
|
145
|
+
export declare class SignUpRefused extends CalimeroError {
|
|
146
|
+
readonly reason: SignUpRefusal;
|
|
147
|
+
constructor(message: string, details?: Record<string, unknown>);
|
|
148
|
+
}
|
|
129
149
|
/** `429 QUOTA_EXCEEDED`: the caller holds as many of something (origins, blobs, ...) as it may. */
|
|
130
150
|
export declare class QuotaExceeded extends CalimeroError {
|
|
131
151
|
constructor(message: string, details?: Record<string, unknown>);
|
package/dist/cloud-v2/errors.js
CHANGED
|
@@ -165,6 +165,40 @@ export class OriginTaken extends CalimeroError {
|
|
|
165
165
|
this.name = 'OriginTaken';
|
|
166
166
|
}
|
|
167
167
|
}
|
|
168
|
+
/**
|
|
169
|
+
* `403 SIGN_UP_REFUSED`: the cluster's sign-up policy does not take this
|
|
170
|
+
* sign-up (core `SystemState::admits`), answered by `register/begin` before a
|
|
171
|
+
* passkey is made and by `register/finish` before its challenge is spent.
|
|
172
|
+
* `reason` says why, so a page can say "invite only" or "this invite has
|
|
173
|
+
* expired" instead of the node's sentence:
|
|
174
|
+
*
|
|
175
|
+
* - `invite-only`: the cluster takes new accounts with an invite only.
|
|
176
|
+
* - `closed`: only a cluster sign-up invite (the operator's or the owner's) admits.
|
|
177
|
+
* - `unknown-invite`: the invite is not one the cluster knows (mistyped, revoked,
|
|
178
|
+
* or used up: a spent invite is gone).
|
|
179
|
+
* - `expired-invite`: past its expiry, or no use left.
|
|
180
|
+
* - `org-gone`: the organization the invite was for was deleted.
|
|
181
|
+
*/
|
|
182
|
+
export class SignUpRefused extends CalimeroError {
|
|
183
|
+
constructor(message, details = {}) {
|
|
184
|
+
super(403, 'SIGN_UP_REFUSED', message, details);
|
|
185
|
+
this.name = 'SignUpRefused';
|
|
186
|
+
this.reason = signUpRefusal(message);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
function signUpRefusal(message) {
|
|
190
|
+
if (/invite only/.test(message))
|
|
191
|
+
return 'invite-only';
|
|
192
|
+
if (/pre-authorized|sign-up invite/.test(message))
|
|
193
|
+
return 'closed';
|
|
194
|
+
if (/unknown invite/.test(message))
|
|
195
|
+
return 'unknown-invite';
|
|
196
|
+
if (/invite has expired/.test(message))
|
|
197
|
+
return 'expired-invite';
|
|
198
|
+
if (/organization is gone/.test(message))
|
|
199
|
+
return 'org-gone';
|
|
200
|
+
return 'other';
|
|
201
|
+
}
|
|
168
202
|
/** `429 QUOTA_EXCEEDED`: the caller holds as many of something (origins, blobs, ...) as it may. */
|
|
169
203
|
export class QuotaExceeded extends CalimeroError {
|
|
170
204
|
constructor(message, details = {}) {
|
|
@@ -265,6 +299,8 @@ export function errorFrom(status, body, retryAfterHeader) {
|
|
|
265
299
|
return new OriginTaken(message, details);
|
|
266
300
|
case '429 QUOTA_EXCEEDED':
|
|
267
301
|
return new QuotaExceeded(message, details);
|
|
302
|
+
case '403 SIGN_UP_REFUSED':
|
|
303
|
+
return new SignUpRefused(message, details);
|
|
268
304
|
case '412 CONFIRM_REQUIRED':
|
|
269
305
|
return new ConfirmRequired(message, details);
|
|
270
306
|
case '413 ARGS_TOO_LARGE':
|
package/dist/cloud-v2/index.d.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
export { CalimeroClient, deployerKey, frontendManifest, pageDeployment } from './client.js';
|
|
2
2
|
export { DEFAULT_DIRECTORY, DirectoryError } from './directory.js';
|
|
3
3
|
export type { DirectoryFailure } from './directory.js';
|
|
4
|
-
export { AppError, ArgsTooLarge, BadOrigin, CalimeroError, ConfirmCancelled, ConfirmRequired, IdempotencyMismatch, LoginRequired, ConsentStale, NotFound, OriginNotRegistered, OriginTaken, QuotaExceeded, ReleaseDeadlineError, TrapError, UnavailableError, WrongNode, errorFrom, } from './errors.js';
|
|
5
|
-
export type { Committed } from './errors.js';
|
|
6
|
-
export type { AppInfo, AppXcalls, BlobContent, BlobInfo, DeadLetter, Deployer, DeployerGrant, DeployersInfo, DeploymentInfo, DeploymentOffer, DeploymentOrgs, FrontendManifest, MeInfo, PackageOrigin, PackageOriginAdded, PackageOriginRemoved, PackageOrigins, SignInOptions, PublicDeployment, CallResult, CalimeroClientOptions, CalimeroConnectOptions, CloudEvent, CalimeroSession, PackageInfo, PackageInstalled, CloudRetryOptions, OrgInfo, OrgUpgradeTarget, PresenceEntry, UpgradeRun, ViewResult, } from './client.js';
|
|
4
|
+
export { AppError, ArgsTooLarge, BadOrigin, CalimeroError, ConfirmCancelled, ConfirmRequired, IdempotencyMismatch, LoginRequired, ConsentStale, NotFound, OriginNotRegistered, OriginTaken, QuotaExceeded, ReleaseDeadlineError, SignUpRefused, TrapError, UnavailableError, WrongNode, errorFrom, } from './errors.js';
|
|
5
|
+
export type { Committed, SignUpRefusal } from './errors.js';
|
|
6
|
+
export type { AppInfo, AppXcalls, BlobContent, BlobInfo, DeadLetter, Deployer, DeployerGrant, DeployersInfo, DeploymentInfo, DeploymentOffer, DeploymentOrgs, FrontendManifest, MeInfo, PackageOrigin, PackageOriginAdded, PackageOriginRemoved, PackageOrigins, SignInOptions, SignUpOptions, SignUpPolicy, PublicDeployment, CallResult, CalimeroClientOptions, CalimeroConnectOptions, CloudEvent, CalimeroSession, PackageInfo, PackageInstalled, CloudRetryOptions, OrgInfo, OrgRecovery, RecoveryTimes, OrgUpgradeTarget, PresenceEntry, UpgradeRun, ViewResult, } from './client.js';
|
|
7
7
|
export { claimedRelease, fetchAttestedKey, freshCollateral, MOCK_QUOTE_HEADER } from './attestation.js';
|
|
8
8
|
export type { AttestationDoc, CloudPolicy, CloudReleasePolicy } from './attestation.js';
|
|
9
9
|
export { browserPasskey, b64url, fromB64url } from './passkey.js';
|
|
10
|
+
export { MAX_RECOVERY_NONCE_BYTES, RECOVERY_DOMAIN, newRecoverySeed, parseRecoverySeed, recoveryMessage, recoveryPublicKey, recoverySeedText, signRecovery, } from './recovery.js';
|
|
10
11
|
export type { AssertionJSON, PasskeyProvider, RegistrationJSON } from './passkey.js';
|
|
11
12
|
export { MAX_REPORT_PAGE, RELEASE_VERIFIER, compareVersions, confirmationCode, consentReturn, pageQuery, pendingApprovalsFrom, publisherBindingBytes, publisherBindingMessage, releaseConsent, releaseNotesUrl, stalledBy, upgradeConsent, verifyReleaseUrl, withdrawConsent, } from './approvals.js';
|
|
12
13
|
export type { ActorKind, AppsReport, Authority, ClusterRelease, ConsentReleaseModel, ConsentRequest, ConsentResult, ConsentScope, ConsentStarted, ConsentStatus, OpenConsentOptions, Governance, LastUpgrade, OpsAppsReport, OpsOrgsReport, PageOptions, PendingApprovals, PendingRelease, PendingUpgrade, PublisherBindingInfo, PublisherProof, ReleaseDeadline, ReleaseRules, ReportScope, ReportedApp, ReportedBundle, ReportedRelease, ReportedReplica, UpgradeAuthority, } from './approvals.js';
|
package/dist/cloud-v2/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
export { CalimeroClient, deployerKey, frontendManifest, pageDeployment } from './client.js';
|
|
2
2
|
export { DEFAULT_DIRECTORY, DirectoryError } from './directory.js';
|
|
3
|
-
export { AppError, ArgsTooLarge, BadOrigin, CalimeroError, ConfirmCancelled, ConfirmRequired, IdempotencyMismatch, LoginRequired, ConsentStale, NotFound, OriginNotRegistered, OriginTaken, QuotaExceeded, ReleaseDeadlineError, TrapError, UnavailableError, WrongNode, errorFrom, } from './errors.js';
|
|
3
|
+
export { AppError, ArgsTooLarge, BadOrigin, CalimeroError, ConfirmCancelled, ConfirmRequired, IdempotencyMismatch, LoginRequired, ConsentStale, NotFound, OriginNotRegistered, OriginTaken, QuotaExceeded, ReleaseDeadlineError, SignUpRefused, TrapError, UnavailableError, WrongNode, errorFrom, } from './errors.js';
|
|
4
4
|
export { claimedRelease, fetchAttestedKey, freshCollateral, MOCK_QUOTE_HEADER } from './attestation.js';
|
|
5
5
|
export { browserPasskey, b64url, fromB64url } from './passkey.js';
|
|
6
|
+
export { MAX_RECOVERY_NONCE_BYTES, RECOVERY_DOMAIN, newRecoverySeed, parseRecoverySeed, recoveryMessage, recoveryPublicKey, recoverySeedText, signRecovery, } from './recovery.js';
|
|
6
7
|
export { MAX_REPORT_PAGE, RELEASE_VERIFIER, compareVersions, confirmationCode, consentReturn, pageQuery, pendingApprovalsFrom, publisherBindingBytes, publisherBindingMessage, releaseConsent, releaseNotesUrl, stalledBy, upgradeConsent, verifyReleaseUrl, withdrawConsent, } from './approvals.js';
|
|
7
8
|
export { DEFAULT_ORG_CAPABILITIES, ORG_CAPABILITIES, orgCapabilities } from './capabilities.js';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An organization's recovery key (core `docs/specs/org-recovery.md`): an
|
|
3
|
+
* Ed25519 key whose 32-byte seed the owner keeps offline. The node stores only
|
|
4
|
+
* the public half; the seed signs, here and never on a node, the statement
|
|
5
|
+
* that hands the organization to one account. Byte-for-byte core's
|
|
6
|
+
* `calimero_identity::recovery`.
|
|
7
|
+
*/
|
|
8
|
+
/** What a recovery signature covers before the organization, the account and the nonce. */
|
|
9
|
+
export declare const RECOVERY_DOMAIN = "calimero.org-recovery.v1\0";
|
|
10
|
+
/** The longest nonce a recovery carries, in bytes (core's `MAX_RECOVERY_NONCE_BYTES`). */
|
|
11
|
+
export declare const MAX_RECOVERY_NONCE_BYTES = 64;
|
|
12
|
+
/** A new random seed. */
|
|
13
|
+
export declare function newRecoverySeed(): Uint8Array;
|
|
14
|
+
/** The seed as people write it down: 64 hex digits in eight groups of eight, joined by `-`. */
|
|
15
|
+
export declare function recoverySeedText(seed: Uint8Array): string;
|
|
16
|
+
/** A seed from its written form: 64 hex digits; `-`, whitespace and case are ignored. */
|
|
17
|
+
export declare function parseRecoverySeed(text: string): Uint8Array;
|
|
18
|
+
/** The bytes a recovery signs: `"calimero.org-recovery.v1\0" || org || 0 || account || 0 || nonce`. */
|
|
19
|
+
export declare function recoveryMessage(org: string, account: string, nonce: string): Uint8Array;
|
|
20
|
+
/** The seed's public key, base64url: what the organization stores. */
|
|
21
|
+
export declare function recoveryPublicKey(seed: Uint8Array): Promise<string>;
|
|
22
|
+
/** The seed's signature (base64url) handing `org` to `account` under `nonce`. */
|
|
23
|
+
export declare function signRecovery(seed: Uint8Array, org: string, account: string, nonce: string): Promise<string>;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An organization's recovery key (core `docs/specs/org-recovery.md`): an
|
|
3
|
+
* Ed25519 key whose 32-byte seed the owner keeps offline. The node stores only
|
|
4
|
+
* the public half; the seed signs, here and never on a node, the statement
|
|
5
|
+
* that hands the organization to one account. Byte-for-byte core's
|
|
6
|
+
* `calimero_identity::recovery`.
|
|
7
|
+
*/
|
|
8
|
+
import { b64url } from './passkey.js';
|
|
9
|
+
/** What a recovery signature covers before the organization, the account and the nonce. */
|
|
10
|
+
export const RECOVERY_DOMAIN = 'calimero.org-recovery.v1\0';
|
|
11
|
+
/** The longest nonce a recovery carries, in bytes (core's `MAX_RECOVERY_NONCE_BYTES`). */
|
|
12
|
+
export const MAX_RECOVERY_NONCE_BYTES = 64;
|
|
13
|
+
/** PKCS#8 prefix of an Ed25519 private key; the 32-byte seed follows. */
|
|
14
|
+
const PKCS8_ED25519 = Uint8Array.from([
|
|
15
|
+
0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x04, 0x22, 0x04, 0x20,
|
|
16
|
+
]);
|
|
17
|
+
/** A new random seed. */
|
|
18
|
+
export function newRecoverySeed() {
|
|
19
|
+
return crypto.getRandomValues(new Uint8Array(32));
|
|
20
|
+
}
|
|
21
|
+
/** The seed as people write it down: 64 hex digits in eight groups of eight, joined by `-`. */
|
|
22
|
+
export function recoverySeedText(seed) {
|
|
23
|
+
checkSeed(seed);
|
|
24
|
+
const hex = Array.from(seed, (b) => b.toString(16).padStart(2, '0')).join('');
|
|
25
|
+
const groups = [];
|
|
26
|
+
for (let i = 0; i < hex.length; i += 8)
|
|
27
|
+
groups.push(hex.slice(i, i + 8));
|
|
28
|
+
return groups.join('-');
|
|
29
|
+
}
|
|
30
|
+
/** A seed from its written form: 64 hex digits; `-`, whitespace and case are ignored. */
|
|
31
|
+
export function parseRecoverySeed(text) {
|
|
32
|
+
const digits = text.replace(/[-\s]/g, '');
|
|
33
|
+
if (!/^[0-9a-fA-F]{64}$/.test(digits))
|
|
34
|
+
throw new Error('a recovery seed is 64 hex digits');
|
|
35
|
+
return Uint8Array.from({ length: 32 }, (_, i) => parseInt(digits.slice(2 * i, 2 * i + 2), 16));
|
|
36
|
+
}
|
|
37
|
+
/** The bytes a recovery signs: `"calimero.org-recovery.v1\0" || org || 0 || account || 0 || nonce`. */
|
|
38
|
+
export function recoveryMessage(org, account, nonce) {
|
|
39
|
+
return new TextEncoder().encode(`${RECOVERY_DOMAIN}${org}\0${account}\0${nonce}`);
|
|
40
|
+
}
|
|
41
|
+
/** The seed's public key, base64url: what the organization stores. */
|
|
42
|
+
export async function recoveryPublicKey(seed) {
|
|
43
|
+
const key = await privateKey(seed, true);
|
|
44
|
+
const jwk = await crypto.subtle.exportKey('jwk', key);
|
|
45
|
+
if (!jwk.x)
|
|
46
|
+
throw new Error('this runtime cannot export an Ed25519 public key');
|
|
47
|
+
return jwk.x;
|
|
48
|
+
}
|
|
49
|
+
/** The seed's signature (base64url) handing `org` to `account` under `nonce`. */
|
|
50
|
+
export async function signRecovery(seed, org, account, nonce) {
|
|
51
|
+
const nonceBytes = new TextEncoder().encode(nonce).length;
|
|
52
|
+
if (nonceBytes === 0 || nonceBytes > MAX_RECOVERY_NONCE_BYTES) {
|
|
53
|
+
throw new Error(`a recovery nonce is 1 to ${MAX_RECOVERY_NONCE_BYTES} bytes`);
|
|
54
|
+
}
|
|
55
|
+
const key = await privateKey(seed, false);
|
|
56
|
+
const sig = await crypto.subtle.sign('Ed25519', key, buf(recoveryMessage(org, account, nonce)));
|
|
57
|
+
return b64url(new Uint8Array(sig));
|
|
58
|
+
}
|
|
59
|
+
function checkSeed(seed) {
|
|
60
|
+
if (seed.length !== 32)
|
|
61
|
+
throw new Error('a recovery seed is 32 bytes');
|
|
62
|
+
}
|
|
63
|
+
function privateKey(seed, extractable) {
|
|
64
|
+
checkSeed(seed);
|
|
65
|
+
const pkcs8 = new Uint8Array(PKCS8_ED25519.length + 32);
|
|
66
|
+
pkcs8.set(PKCS8_ED25519);
|
|
67
|
+
pkcs8.set(seed, PKCS8_ED25519.length);
|
|
68
|
+
return crypto.subtle.importKey('pkcs8', buf(pkcs8), { name: 'Ed25519' }, extractable, ['sign']);
|
|
69
|
+
}
|
|
70
|
+
const buf = (b) => b.buffer.slice(b.byteOffset, b.byteOffset + b.byteLength);
|