@calimero-network/mero-js 27.5.0 → 28.0.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 CHANGED
@@ -97,12 +97,12 @@ 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`.
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.
101
101
  - **Packages**: `installPackage`, `uploadPackage`, `packages`, `package`, `uninstallPackage`; `uploadBundle` (raw WASM, development clusters only).
102
102
  - **Apps**: `createApp`, `listApps`, `getApp`, `deleteApp`, `setAccess`, `setAppMetadata`, `appStorage`, `appAbi`, `upgradeApp`, `transferApp`.
103
103
  - **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.
104
104
  - **Blobs and presence**: `uploadBlob`, `downloadBlob`, `listBlobs`, `blobInfo`, `deleteBlob`; `publishPresence`, `presence`, `presenceStream`.
105
- - **Organizations**: `createOrg`, `getOrg`, `deleteOrg`, `setOrgMetadata`, `setMember`, `removeMember`, `setMemberMetadata`, `setCapabilities` (`ORG_CAPABILITIES`), teams, `invite`, `redeemInvite`.
105
+ - **Organizations**: `createOrg`, `getOrg`, `deleteOrg`, `setOrgMetadata`, `setMember`, `removeMember`, `setMemberMetadata`, `setCapabilities` (`ORG_CAPABILITIES`), teams, `invite`, `redeemInvite`. Setting or removing a member or team member, capabilities and invites are approved with the caller's passkey; redeeming an invite is not.
106
106
  - **Owner decisions** (passkey approval, never from an app's frontend): `upgradeOrg`, `upgradeRun`, `retryUpgradeRun`, `approveRelease`, `approveOutbound`, `bindPublisher`, `upgradePublished`.
107
107
  - **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/).
108
108
  - **Operator** (ops token): `opsApps`, `opsOrgs`, `setReleaseDeadline`, `liftReleaseDeadline`.
@@ -320,10 +320,15 @@ export declare class CalimeroClient {
320
320
  /** End every session of the signed-in account, on every device, this one too. */
321
321
  signOutEverywhere(): Promise<void>;
322
322
  me(): Promise<Record<string, unknown>>;
323
- /** Add another passkey (another device) to the signed-in account; answers its credential id. */
324
- addPasskey(passkey: PasskeyProvider): Promise<string>;
325
- /** Remove one of the account's passkeys (not the last). */
326
- removePasskey(credentialId: string): Promise<void>;
323
+ /**
324
+ * Add another passkey (another device, `passkey`) to the signed-in account;
325
+ * answers its credential id. The node asks for the approval of a passkey
326
+ * the account already has (`412 CONFIRM_REQUIRED`, never the new one),
327
+ * which `approver` gives.
328
+ */
329
+ addPasskey(passkey: PasskeyProvider, approver: PasskeyProvider): Promise<string>;
330
+ /** Remove one of the account's passkeys (not the last), approved with `passkey` (one it has). */
331
+ removePasskey(credentialId: string, passkey: PasskeyProvider): Promise<void>;
327
332
  /** Name one of the account's passkeys (a device label); `''` clears the name. */
328
333
  labelPasskey(credentialId: string, label: string): Promise<void>;
329
334
  /** What this account uses (apps, bundle bytes, API keys, owned orgs), with the cluster's quotas. */
@@ -336,7 +341,11 @@ export declare class CalimeroClient {
336
341
  /** The account's API keys (never the keys themselves). */
337
342
  listApiKeys(): Promise<Array<Record<string, unknown>>>;
338
343
  revokeApiKey(keyId: string): Promise<void>;
339
- /** Create an API key for an agent or server. The key is returned once. */
344
+ /**
345
+ * Create an API key for an agent or server, approved with the account's
346
+ * `passkey` (`412 CONFIRM_REQUIRED`); needs a passkey session (an API key
347
+ * is refused `403`). The key is returned once.
348
+ */
340
349
  createApiKey(options: {
341
350
  label?: string;
342
351
  apps?: string[];
@@ -344,7 +353,7 @@ export declare class CalimeroClient {
344
353
  readOnly?: boolean;
345
354
  allowConfirm?: boolean;
346
355
  ttlSecs?: number;
347
- }): Promise<{
356
+ }, passkey: PasskeyProvider): Promise<{
348
357
  keyId: string;
349
358
  key: string;
350
359
  }>;
@@ -418,10 +427,17 @@ export declare class CalimeroClient {
418
427
  }>;
419
428
  /**
420
429
  * POST `body` to `path`, answering a `412 CONFIRM_REQUIRED` with `passkey`'s
421
- * assertion over the node's challenge (deleting an org, approving a release, ...).
430
+ * assertion over the node's challenge (deleting an org, approving a release,
431
+ * adding a member, ...): the same body again, with `confirm`. Without a
432
+ * passkey (a caller in plain JavaScript passing none) the `412` reaches the
433
+ * caller as {@link ConfirmRequired}, carrying the challenge.
422
434
  */
423
435
  postConfirmed<T>(path: string, body: Record<string, unknown>, passkey: PasskeyProvider): Promise<T>;
424
- /** {@link postConfirmed} for a DELETE (deleting an organization). */
436
+ /**
437
+ * {@link postConfirmed} for a DELETE (deleting an organization, removing a
438
+ * member or a passkey): `body` (`{}` by default) goes as JSON, and the
439
+ * confirmed retry carries `confirm` beside it.
440
+ */
425
441
  deleteConfirmed<T>(path: string, passkey: PasskeyProvider, body?: Record<string, unknown>): Promise<T>;
426
442
  private confirmed;
427
443
  /**
@@ -612,15 +628,19 @@ export declare class CalimeroClient {
612
628
  setReleaseDeadline(version: string, after: Date | string): Promise<void>;
613
629
  /** Lift a release's deadline. Operator token. */
614
630
  liftReleaseDeadline(version: string): Promise<void>;
615
- /** Add a member, or change their role. */
616
- setMember(org: string, account: string, role: 'guest' | 'member' | 'admin' | 'owner'): Promise<void>;
617
- removeMember(org: string, account: string): Promise<Record<string, unknown>>;
631
+ /** Add a member, or change their role, approved with the caller's `passkey`. */
632
+ setMember(org: string, account: string, role: 'guest' | 'member' | 'admin' | 'owner', passkey: PasskeyProvider): Promise<void>;
633
+ /**
634
+ * Remove a member, or leave (`account` the caller's own), approved with the
635
+ * caller's `passkey`.
636
+ */
637
+ removeMember(org: string, account: string, passkey: PasskeyProvider): Promise<Record<string, unknown>>;
618
638
  /**
619
639
  * Set capability bits ({@link ORG_CAPABILITIES}): for a member (`account`),
620
640
  * for a member of one team (`account` and `team`), or the organization's
621
- * default (neither).
641
+ * default (neither). Approved with the caller's `passkey`.
622
642
  */
623
- setCapabilities(org: string, capabilities: number, at?: {
643
+ setCapabilities(org: string, capabilities: number, passkey: PasskeyProvider, at?: {
624
644
  account?: string;
625
645
  team?: string;
626
646
  }): Promise<void>;
@@ -638,14 +658,23 @@ export declare class CalimeroClient {
638
658
  }): Promise<void>;
639
659
  /** Delete a team, the teams below it and their apps. */
640
660
  deleteTeam(org: string, team: string): Promise<void>;
641
- /** Add a member to a team, or change their row: role, lead, capability bits. */
642
- setTeamMember(org: string, team: string, account: string, row?: {
661
+ /**
662
+ * Add a member to a team, or change their row: role, lead, capability bits.
663
+ * Approved with the caller's `passkey`.
664
+ */
665
+ setTeamMember(org: string, team: string, account: string, passkey: PasskeyProvider, row?: {
643
666
  role?: 'lead' | 'member' | 'readOnly';
644
667
  lead?: boolean;
645
668
  capabilities?: number;
646
669
  }): Promise<void>;
647
- removeTeamMember(org: string, team: string, account: string): Promise<void>;
648
- invite(org: string, role: 'guest' | 'member' | 'admin', teams?: string[], options?: {
670
+ /** Remove a member from a team, approved with the caller's `passkey`. */
671
+ removeTeamMember(org: string, team: string, account: string, passkey: PasskeyProvider): Promise<void>;
672
+ /**
673
+ * Make an invite into `org` (and `teams`), approved with the caller's
674
+ * `passkey`; answers its token, which {@link redeemInvite} takes (no
675
+ * approval there).
676
+ */
677
+ invite(org: string, role: 'guest' | 'member' | 'admin', passkey: PasskeyProvider, teams?: string[], options?: {
649
678
  ttlSecs?: number;
650
679
  uses?: number;
651
680
  }): Promise<string>;
@@ -478,19 +478,21 @@ export class CalimeroClient {
478
478
  me() {
479
479
  return this.get('auth/me');
480
480
  }
481
- /** Add another passkey (another device) to the signed-in account; answers its credential id. */
482
- async addPasskey(passkey) {
481
+ /**
482
+ * Add another passkey (another device, `passkey`) to the signed-in account;
483
+ * answers its credential id. The node asks for the approval of a passkey
484
+ * the account already has (`412 CONFIRM_REQUIRED`, never the new one),
485
+ * which `approver` gives.
486
+ */
487
+ async addPasskey(passkey, approver) {
483
488
  const begin = await this.post('auth/passkeys/add/begin');
484
489
  const registration = await passkey.create(begin.options);
485
- const { credentialId } = await this.post('auth/passkeys/add/finish', {
486
- challengeId: begin.challengeId,
487
- registration,
488
- });
490
+ const { credentialId } = await this.postConfirmed('auth/passkeys/add/finish', { challengeId: begin.challengeId, registration }, approver);
489
491
  return credentialId;
490
492
  }
491
- /** Remove one of the account's passkeys (not the last). */
492
- async removePasskey(credentialId) {
493
- await this.delete(`auth/passkeys/${seg(credentialId)}`);
493
+ /** Remove one of the account's passkeys (not the last), approved with `passkey` (one it has). */
494
+ async removePasskey(credentialId, passkey) {
495
+ await this.deleteConfirmed(`auth/passkeys/${seg(credentialId)}`, passkey);
494
496
  }
495
497
  /** Name one of the account's passkeys (a device label); `''` clears the name. */
496
498
  async labelPasskey(credentialId, label) {
@@ -519,9 +521,13 @@ export class CalimeroClient {
519
521
  async revokeApiKey(keyId) {
520
522
  await this.delete(`auth/apikeys/${seg(keyId)}`);
521
523
  }
522
- /** Create an API key for an agent or server. The key is returned once. */
523
- createApiKey(options) {
524
- return this.post('auth/apikeys', options);
524
+ /**
525
+ * Create an API key for an agent or server, approved with the account's
526
+ * `passkey` (`412 CONFIRM_REQUIRED`); needs a passkey session (an API key
527
+ * is refused `403`). The key is returned once.
528
+ */
529
+ createApiKey(options, passkey) {
530
+ return this.postConfirmed('auth/apikeys', { ...options }, passkey);
525
531
  }
526
532
  // ---------------------------------------------------------------- apps
527
533
  /** Upload a raw WASM bundle (development clusters only); answers its hash. */
@@ -631,12 +637,19 @@ export class CalimeroClient {
631
637
  }
632
638
  /**
633
639
  * POST `body` to `path`, answering a `412 CONFIRM_REQUIRED` with `passkey`'s
634
- * assertion over the node's challenge (deleting an org, approving a release, ...).
640
+ * assertion over the node's challenge (deleting an org, approving a release,
641
+ * adding a member, ...): the same body again, with `confirm`. Without a
642
+ * passkey (a caller in plain JavaScript passing none) the `412` reaches the
643
+ * caller as {@link ConfirmRequired}, carrying the challenge.
635
644
  */
636
645
  postConfirmed(path, body, passkey) {
637
646
  return this.confirmed('POST', path, body, passkey);
638
647
  }
639
- /** {@link postConfirmed} for a DELETE (deleting an organization). */
648
+ /**
649
+ * {@link postConfirmed} for a DELETE (deleting an organization, removing a
650
+ * member or a passkey): `body` (`{}` by default) goes as JSON, and the
651
+ * confirmed retry carries `confirm` beside it.
652
+ */
640
653
  deleteConfirmed(path, passkey, body = {}) {
641
654
  return this.confirmed('DELETE', path, body, passkey);
642
655
  }
@@ -645,7 +658,7 @@ export class CalimeroClient {
645
658
  return await send(body);
646
659
  }
647
660
  catch (error) {
648
- if (!(error instanceof ConfirmRequired))
661
+ if (!(error instanceof ConfirmRequired) || !passkey)
649
662
  throw error;
650
663
  const d = error.details;
651
664
  let assertion;
@@ -973,20 +986,24 @@ export class CalimeroClient {
973
986
  async liftReleaseDeadline(version) {
974
987
  await this.delete(`ops/releases/${seg(version)}/deadline`);
975
988
  }
976
- /** Add a member, or change their role. */
977
- async setMember(org, account, role) {
978
- await this.post(`orgs/${seg(org)}/members`, { account, role });
989
+ /** Add a member, or change their role, approved with the caller's `passkey`. */
990
+ async setMember(org, account, role, passkey) {
991
+ await this.postConfirmed(`orgs/${seg(org)}/members`, { account, role }, passkey);
979
992
  }
980
- removeMember(org, account) {
981
- return this.delete(`orgs/${seg(org)}/members/${seg(account)}`);
993
+ /**
994
+ * Remove a member, or leave (`account` the caller's own), approved with the
995
+ * caller's `passkey`.
996
+ */
997
+ removeMember(org, account, passkey) {
998
+ return this.deleteConfirmed(`orgs/${seg(org)}/members/${seg(account)}`, passkey);
982
999
  }
983
1000
  /**
984
1001
  * Set capability bits ({@link ORG_CAPABILITIES}): for a member (`account`),
985
1002
  * for a member of one team (`account` and `team`), or the organization's
986
- * default (neither).
1003
+ * default (neither). Approved with the caller's `passkey`.
987
1004
  */
988
- async setCapabilities(org, capabilities, at = {}) {
989
- await this.post(`orgs/${seg(org)}/capabilities`, { capabilities, ...at });
1005
+ async setCapabilities(org, capabilities, passkey, at = {}) {
1006
+ await this.postConfirmed(`orgs/${seg(org)}/capabilities`, { capabilities, ...at }, passkey);
990
1007
  }
991
1008
  async createTeam(org, name, join, parent) {
992
1009
  const { teamId } = await this.post(`orgs/${seg(org)}/teams`, { name, join, parent });
@@ -1006,15 +1023,24 @@ export class CalimeroClient {
1006
1023
  async deleteTeam(org, team) {
1007
1024
  await this.delete(`orgs/${seg(org)}/teams/${seg(team)}`);
1008
1025
  }
1009
- /** Add a member to a team, or change their row: role, lead, capability bits. */
1010
- async setTeamMember(org, team, account, row = {}) {
1011
- await this.post(`orgs/${seg(org)}/teams/${seg(team)}/members`, { account, ...row });
1026
+ /**
1027
+ * Add a member to a team, or change their row: role, lead, capability bits.
1028
+ * Approved with the caller's `passkey`.
1029
+ */
1030
+ async setTeamMember(org, team, account, passkey, row = {}) {
1031
+ await this.postConfirmed(`orgs/${seg(org)}/teams/${seg(team)}/members`, { account, ...row }, passkey);
1012
1032
  }
1013
- async removeTeamMember(org, team, account) {
1014
- await this.delete(`orgs/${seg(org)}/teams/${seg(team)}/members/${seg(account)}`);
1033
+ /** Remove a member from a team, approved with the caller's `passkey`. */
1034
+ async removeTeamMember(org, team, account, passkey) {
1035
+ await this.deleteConfirmed(`orgs/${seg(org)}/teams/${seg(team)}/members/${seg(account)}`, passkey);
1015
1036
  }
1016
- async invite(org, role, teams = [], options = {}) {
1017
- const { token } = await this.post(`orgs/${seg(org)}/invites`, { role, teams, ...options });
1037
+ /**
1038
+ * Make an invite into `org` (and `teams`), approved with the caller's
1039
+ * `passkey`; answers its token, which {@link redeemInvite} takes (no
1040
+ * approval there).
1041
+ */
1042
+ async invite(org, role, passkey, teams = [], options = {}) {
1043
+ const { token } = await this.postConfirmed(`orgs/${seg(org)}/invites`, { role, teams, ...options }, passkey);
1018
1044
  return token;
1019
1045
  }
1020
1046
  redeemInvite(token) {