@calimero-network/mero-js 29.7.6 → 29.8.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 +180 -4
- package/dist/cloud-v2/client.js +97 -13
- package/dist/cloud-v2/index.d.ts +2 -2
- package/dist/cloud-v2/passkey.d.ts +18 -2
- package/dist/cloud-v2/passkey.js +16 -5
- package/dist/index.browser.mjs +3 -3
- package/dist/index.browser.mjs.map +3 -3
- package/dist/index.cjs +104 -19
- package/dist/index.cjs.map +2 -2
- package/dist/index.mjs +104 -19
- package/dist/index.mjs.map +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -104,7 +104,7 @@ Pinned measurements go through `policy.verifyQuote` (`createQuoteVerifier`).
|
|
|
104
104
|
- **Apps**: `createApp`, `listApps`, `getApp`, `deleteApp`, `setAccess`, `setAppMetadata`, `appStorage`, `appAbi`, `upgradeApp`, `transferApp`.
|
|
105
105
|
- **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.
|
|
106
106
|
- **Blobs and presence**: `uploadBlob`, `downloadBlob`, `listBlobs`, `blobInfo`, `deleteBlob`; `publishPresence`, `presence`, `presenceStream`.
|
|
107
|
-
- **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
|
|
107
|
+
- **Organizations**: `createOrg`, `getOrg`, `deleteOrg`, `setOrgMetadata`, `setMember`, `removeMember`, `setMemberMetadata`, `setCapabilities` (`ORG_CAPABILITIES`), `setTeamDefaultCapabilities`, teams, app teams (`listAppTeams`, `scopeApps`), `invite`, `redeemInvite`, `listInvites`, `revokeInvite`, recovery (`setRecoveryKey`, `startRecovery`, `cancelRecovery`, `finishRecovery`; `newRecoverySeed`, `recoveryPublicKey`). Setting or removing a member or team member, capabilities, invites (minting and revoking) and scoping apps are approved with the caller's passkey; redeeming an invite is not.
|
|
108
108
|
- **Owner decisions** (passkey approval, never from an app's frontend): `upgradeOrg`, `upgradeRun`, `retryUpgradeRun`, `approveRelease`, `approveOutbound`, `bindPublisher`, `upgradePublished`.
|
|
109
109
|
- **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/).
|
|
110
110
|
- **Operator** (ops token): `opsApps`, `opsOrgs`, `opsServedOrigins` (every custom domain the log holds, by package label), `setReleaseDeadline`, `liftReleaseDeadline`.
|
|
@@ -133,6 +133,119 @@ export interface OrgInfo extends Record<string, unknown> {
|
|
|
133
133
|
} | null;
|
|
134
134
|
/** The recovery key and any recovery under way; absent while the organization has none. */
|
|
135
135
|
recovery?: OrgRecovery;
|
|
136
|
+
/** How it holds its apps ({@link OrgMode}); absent from nodes before app teams. */
|
|
137
|
+
mode?: OrgMode;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* How an organization holds its apps, derived from its app teams (core
|
|
141
|
+
* `OrgMode`): `legacy` has none (its apps use organization access),
|
|
142
|
+
* `standalone` has only open ones (everyone has every app), `shared` has a
|
|
143
|
+
* closed one (each app lists who is in it).
|
|
144
|
+
*/
|
|
145
|
+
export type OrgMode = 'legacy' | 'standalone' | 'shared';
|
|
146
|
+
/** One app team, as `GET /orgs/{org}/app-teams` lists it ({@link CalimeroClient.listAppTeams}). */
|
|
147
|
+
export interface AppTeam {
|
|
148
|
+
/** The package this team is the app team of (one per package per organization). */
|
|
149
|
+
package: string;
|
|
150
|
+
teamId: string;
|
|
151
|
+
name: string;
|
|
152
|
+
/** `everyone` (open) or `invite` (closed). */
|
|
153
|
+
join: 'everyone' | 'invite';
|
|
154
|
+
/** How many of the organization's people are in the team. */
|
|
155
|
+
memberCount: number;
|
|
156
|
+
/** Capability bits everyone in the team holds there ({@link ORG_CAPABILITIES}); `null` when unset. */
|
|
157
|
+
defaultCapabilities: number | null;
|
|
158
|
+
/** Whether the caller is in the team. */
|
|
159
|
+
member: boolean;
|
|
160
|
+
/** Whether the caller manages it (its lead, an ancestor's lead, or an admin). */
|
|
161
|
+
manages: boolean;
|
|
162
|
+
}
|
|
163
|
+
/** `GET /orgs/{org}/app-teams`: the organization's mode and its app teams. A guest sees only the app teams they are in. */
|
|
164
|
+
export interface AppTeams {
|
|
165
|
+
mode: OrgMode;
|
|
166
|
+
appTeams: AppTeam[];
|
|
167
|
+
}
|
|
168
|
+
/** One package to give an app team ({@link CalimeroClient.scopeApps}, core `ScopeApp`). */
|
|
169
|
+
export interface ScopeApp {
|
|
170
|
+
package: string;
|
|
171
|
+
/** The team's name when one is made, 1..64 characters (absent: the package's display name, else the package id). */
|
|
172
|
+
name?: string;
|
|
173
|
+
/**
|
|
174
|
+
* The instances to move onto the team (absent: every organization-access
|
|
175
|
+
* instance of the package here; `[]`: none, for a fresh install).
|
|
176
|
+
*/
|
|
177
|
+
instances?: string[];
|
|
178
|
+
}
|
|
179
|
+
/** What {@link CalimeroClient.scopeApps} asks for; every field absent takes the node's default. */
|
|
180
|
+
export interface ScopeAppsOptions {
|
|
181
|
+
/** At most 64 packages (absent: every package with an organization-access instance here). */
|
|
182
|
+
apps?: ScopeApp[];
|
|
183
|
+
/** Close every app team (absent: when the organization then holds two or more). */
|
|
184
|
+
shared?: boolean;
|
|
185
|
+
}
|
|
186
|
+
/** One package's app team after {@link CalimeroClient.scopeApps}. */
|
|
187
|
+
export interface ScopedAppTeam {
|
|
188
|
+
package: string;
|
|
189
|
+
teamId: string;
|
|
190
|
+
/** Whether the team was made now (`false`: it existed already). */
|
|
191
|
+
created: boolean;
|
|
192
|
+
/** The instances moved onto it. */
|
|
193
|
+
apps: string[];
|
|
194
|
+
/** How many rows were added for people who reached those instances. */
|
|
195
|
+
added: number;
|
|
196
|
+
}
|
|
197
|
+
/** `POST /orgs/{org}/app-teams`: the organization's mode after, and each package's team. */
|
|
198
|
+
export interface ScopeAppsResult {
|
|
199
|
+
mode: OrgMode;
|
|
200
|
+
teams: ScopedAppTeam[];
|
|
201
|
+
}
|
|
202
|
+
/** A pending organization invite, as `GET /orgs/{org}/invites` lists it (never its token). */
|
|
203
|
+
export interface OrgInvite {
|
|
204
|
+
/** The invite's key, which {@link CalimeroClient.revokeInvite} takes. */
|
|
205
|
+
inviteKey: string;
|
|
206
|
+
role: 'guest' | 'member' | 'admin';
|
|
207
|
+
/** The teams it adds its holder to. */
|
|
208
|
+
teams: string[];
|
|
209
|
+
usesLeft: number;
|
|
210
|
+
/** Unix seconds. */
|
|
211
|
+
expiresAt: number;
|
|
212
|
+
/** Listed until revoked, even past `expiresAt`. */
|
|
213
|
+
expired: boolean;
|
|
214
|
+
/** The account that made it; `null` for invites made before core recorded it. */
|
|
215
|
+
createdBy: string | null;
|
|
216
|
+
/** Unix seconds; `null` for invites made before core recorded it. */
|
|
217
|
+
createdAt: number | null;
|
|
218
|
+
}
|
|
219
|
+
/** One direct row of a team ({@link TeamInfo}). */
|
|
220
|
+
export interface TeamMemberInfo {
|
|
221
|
+
account: string;
|
|
222
|
+
role: 'lead' | 'member' | 'readOnly';
|
|
223
|
+
/** Their effective bits there: their row's own, else the organization's with the team's default on top. */
|
|
224
|
+
capabilities: number;
|
|
225
|
+
}
|
|
226
|
+
/** `GET /orgs/{org}/teams/{team}` ({@link CalimeroClient.getTeam}). */
|
|
227
|
+
export interface TeamInfo extends Record<string, unknown> {
|
|
228
|
+
teamId: string;
|
|
229
|
+
name: string;
|
|
230
|
+
parent: string | null;
|
|
231
|
+
join: 'everyone' | 'invite';
|
|
232
|
+
metadata: unknown;
|
|
233
|
+
/** The package it is the app team of (`metadata.app.package` on a top-level team), else `null`. */
|
|
234
|
+
package?: string | null;
|
|
235
|
+
/** Capability bits everyone in the team holds there on top of their own; `null` when unset. */
|
|
236
|
+
defaultCapabilities?: number | null;
|
|
237
|
+
members: TeamMemberInfo[];
|
|
238
|
+
/** The child teams the caller sees. */
|
|
239
|
+
children: string[];
|
|
240
|
+
apps: Array<{
|
|
241
|
+
appId: string;
|
|
242
|
+
name: string;
|
|
243
|
+
contextId: string;
|
|
244
|
+
}>;
|
|
245
|
+
/** The caller's direct row's role, if any. */
|
|
246
|
+
myRole: 'lead' | 'member' | 'readOnly' | null;
|
|
247
|
+
/** Whether the caller is in the team. */
|
|
248
|
+
member: boolean;
|
|
136
249
|
}
|
|
137
250
|
/** An organization's recovery key (core `Org::recovery`). */
|
|
138
251
|
export interface OrgRecovery {
|
|
@@ -408,6 +521,13 @@ export interface ServedOrigins {
|
|
|
408
521
|
*/
|
|
409
522
|
export interface SignInOptions {
|
|
410
523
|
origin?: string | null;
|
|
524
|
+
/**
|
|
525
|
+
* Closes an open passkey prompt when it aborts (a Cancel button): the
|
|
526
|
+
* ceremony then rejects as a dismissed prompt does (`NotAllowedError`; in a
|
|
527
|
+
* sign-up's confirmation, {@link ConfirmCancelled}). Given to the
|
|
528
|
+
* {@link PasskeyProvider} as `signal`.
|
|
529
|
+
*/
|
|
530
|
+
signal?: AbortSignal;
|
|
411
531
|
}
|
|
412
532
|
/**
|
|
413
533
|
* {@link SignInOptions} for a sign-up, and the invite it is made with: an
|
|
@@ -648,9 +768,21 @@ export declare class CalimeroClient {
|
|
|
648
768
|
*/
|
|
649
769
|
refresh(): Promise<CalimeroSession>;
|
|
650
770
|
private exchangeRefresh;
|
|
771
|
+
/**
|
|
772
|
+
* End this session. The client is signed out whether or not the node
|
|
773
|
+
* answers: the revoke (`POST /auth/logout`) is attempted, and its failure
|
|
774
|
+
* (the node unreachable, say) is thrown after the session is dropped, so a
|
|
775
|
+
* caller can say the server may still hold it, but no request after this
|
|
776
|
+
* one carries its token.
|
|
777
|
+
*/
|
|
651
778
|
signOut(): Promise<void>;
|
|
652
|
-
/**
|
|
779
|
+
/**
|
|
780
|
+
* End every session of the signed-in account, on every device, this one
|
|
781
|
+
* too. Like {@link signOut}, this client is signed out even when the
|
|
782
|
+
* request fails, and the failure is thrown.
|
|
783
|
+
*/
|
|
653
784
|
signOutEverywhere(): Promise<void>;
|
|
785
|
+
private signingOut;
|
|
654
786
|
/** Who the credential is: the account, how it signed in, and the package (`app`) and `origin` a session is bound to. */
|
|
655
787
|
me(): Promise<MeInfo>;
|
|
656
788
|
/**
|
|
@@ -1093,15 +1225,30 @@ export declare class CalimeroClient {
|
|
|
1093
1225
|
removeMember(org: string, account: string, passkey: PasskeyProvider): Promise<Record<string, unknown>>;
|
|
1094
1226
|
/**
|
|
1095
1227
|
* Set capability bits ({@link ORG_CAPABILITIES}): for a member (`account`),
|
|
1096
|
-
* for a member of one team (`account` and `team`),
|
|
1097
|
-
*
|
|
1228
|
+
* for a member of one team (`account` and `team`), a team's default
|
|
1229
|
+
* (`team` alone, {@link setTeamDefaultCapabilities}), or the
|
|
1230
|
+
* organization's default (neither). Approved with the caller's `passkey`.
|
|
1098
1231
|
*/
|
|
1099
1232
|
setCapabilities(org: string, capabilities: number, passkey: PasskeyProvider, at?: {
|
|
1100
1233
|
account?: string;
|
|
1101
1234
|
team?: string;
|
|
1102
1235
|
}): Promise<void>;
|
|
1236
|
+
/**
|
|
1237
|
+
* Set the capability bits everyone in `team` holds there on top of their
|
|
1238
|
+
* own, unless their row sets its own (`POST /orgs/{org}/capabilities` with
|
|
1239
|
+
* `team` and no `account`): `ORG_CAPABILITIES.INVITE_MEMBERS` is "members
|
|
1240
|
+
* may invite others to this app". The team's managers, or whoever holds
|
|
1241
|
+
* `MANAGE_MEMBERS` on it; approved with the caller's `passkey`
|
|
1242
|
+
* (`__set_default_capabilities { team, capabilities }`).
|
|
1243
|
+
*/
|
|
1244
|
+
setTeamDefaultCapabilities(org: string, team: string, capabilities: number, passkey: PasskeyProvider): Promise<void>;
|
|
1103
1245
|
createTeam(org: string, name: string, join: 'everyone' | 'invite', parent?: string): Promise<string>;
|
|
1104
|
-
|
|
1246
|
+
/**
|
|
1247
|
+
* A team the caller sees: its rows (each with its effective capabilities),
|
|
1248
|
+
* children, apps, the package it is the app team of and its default
|
|
1249
|
+
* capabilities.
|
|
1250
|
+
*/
|
|
1251
|
+
getTeam(org: string, team: string): Promise<TeamInfo>;
|
|
1105
1252
|
/**
|
|
1106
1253
|
* Rename, change who may join, set the app's metadata, or move the team:
|
|
1107
1254
|
* `parent: null` moves it to the top, an absent `parent` leaves it.
|
|
@@ -1135,6 +1282,35 @@ export declare class CalimeroClient {
|
|
|
1135
1282
|
uses?: number;
|
|
1136
1283
|
}): Promise<string>;
|
|
1137
1284
|
redeemInvite(token: string): Promise<Record<string, unknown>>;
|
|
1285
|
+
/**
|
|
1286
|
+
* The organization's pending invites the caller may see (never a token):
|
|
1287
|
+
* every one for an owner or admin, their own, and those naming only teams
|
|
1288
|
+
* they manage. Expired ones are listed (`expired: true`) until revoked. A
|
|
1289
|
+
* caller outside the organization: {@link NotFound}.
|
|
1290
|
+
*/
|
|
1291
|
+
listInvites(org: string): Promise<OrgInvite[]>;
|
|
1292
|
+
/**
|
|
1293
|
+
* Withdraw an invite by its key ({@link listInvites}), approved with the
|
|
1294
|
+
* caller's `passkey` (`__revoke_invite { inviteKey }`). One the caller may
|
|
1295
|
+
* not see: {@link NotFound}.
|
|
1296
|
+
*/
|
|
1297
|
+
revokeInvite(org: string, inviteKey: string, passkey: PasskeyProvider): Promise<void>;
|
|
1298
|
+
/**
|
|
1299
|
+
* The organization's mode and its app teams (one per package), for any
|
|
1300
|
+
* member; a guest sees only those they are in. A caller outside the
|
|
1301
|
+
* organization: {@link NotFound}.
|
|
1302
|
+
*/
|
|
1303
|
+
listAppTeams(org: string): Promise<AppTeams>;
|
|
1304
|
+
/**
|
|
1305
|
+
* Give apps their own access lists in one step (owner or admin): find or
|
|
1306
|
+
* make each package's app team, add everyone who reaches its instances now,
|
|
1307
|
+
* and move those instances onto it; with `shared`, close every app team.
|
|
1308
|
+
* Idempotent per package. Approved with the caller's `passkey`
|
|
1309
|
+
* (`__scope_apps { apps, shared }`); a refusal is `400 REJECTED` with the
|
|
1310
|
+
* node's reason. For a fresh standalone install pass
|
|
1311
|
+
* `{ apps: [{ package, name, instances: [] }] }`.
|
|
1312
|
+
*/
|
|
1313
|
+
scopeApps(org: string, passkey: PasskeyProvider, options?: ScopeAppsOptions): Promise<ScopeAppsResult>;
|
|
1138
1314
|
/**
|
|
1139
1315
|
* The web origins `{package}`'s managers registered so a frontend they host
|
|
1140
1316
|
* themselves can sign users in with passkeys ({@link PackageOrigins}).
|
package/dist/cloud-v2/client.js
CHANGED
|
@@ -511,13 +511,14 @@ export class CalimeroClient {
|
|
|
511
511
|
const origin = this.pageOrigin(options);
|
|
512
512
|
const invite = options.invite ? { invite: options.invite } : {};
|
|
513
513
|
const begin = await this.begin('auth/passkey/register/begin', origin === undefined ? { name, ...invite } : { name, origin, ...invite }, origin);
|
|
514
|
-
const
|
|
514
|
+
const signal = options.signal;
|
|
515
|
+
const registration = await (signal ? passkey.create(begin.options, { signal }) : passkey.create(begin.options));
|
|
515
516
|
// The node checks the sign-up's confirmation with the user present, not
|
|
516
517
|
// verified (core `api.rs`: `rp(node)`, `require_uv: false`), as it made
|
|
517
518
|
// the passkey with `preferred`: asking `required` here would refuse a
|
|
518
519
|
// security key without a PIN after it has made the passkey, leaving a
|
|
519
520
|
// passkey with no account.
|
|
520
|
-
const session = await this.confirmed('POST', 'auth/passkey/register/finish', { challengeId: begin.challengeId, registration, ...invite }, passkey, undefined, 'preferred');
|
|
521
|
+
const session = await this.confirmed('POST', 'auth/passkey/register/finish', { challengeId: begin.challengeId, registration, ...invite }, passkey, undefined, 'preferred', signal);
|
|
521
522
|
this.useSession(session);
|
|
522
523
|
return session;
|
|
523
524
|
}
|
|
@@ -537,7 +538,10 @@ export class CalimeroClient {
|
|
|
537
538
|
const origin = this.pageOrigin(options);
|
|
538
539
|
const begin = await this.begin('auth/passkey/login/begin', origin === undefined ? undefined : { origin }, origin);
|
|
539
540
|
const rpId = begin.options.rpId ?? begin.options.rp?.id ?? '';
|
|
540
|
-
const
|
|
541
|
+
const challenge = fromB64url(begin.options.challenge);
|
|
542
|
+
const assertion = await (options.signal
|
|
543
|
+
? passkey.get(challenge, rpId, undefined, { signal: options.signal })
|
|
544
|
+
: passkey.get(challenge, rpId));
|
|
541
545
|
const session = await this.post('auth/passkey/login/finish', {
|
|
542
546
|
challengeId: begin.challengeId,
|
|
543
547
|
assertion,
|
|
@@ -617,16 +621,32 @@ export class CalimeroClient {
|
|
|
617
621
|
this.useSession(session);
|
|
618
622
|
return session;
|
|
619
623
|
}
|
|
624
|
+
/**
|
|
625
|
+
* End this session. The client is signed out whether or not the node
|
|
626
|
+
* answers: the revoke (`POST /auth/logout`) is attempted, and its failure
|
|
627
|
+
* (the node unreachable, say) is thrown after the session is dropped, so a
|
|
628
|
+
* caller can say the server may still hold it, but no request after this
|
|
629
|
+
* one carries its token.
|
|
630
|
+
*/
|
|
620
631
|
async signOut() {
|
|
621
|
-
await this.
|
|
622
|
-
this.auth = undefined;
|
|
623
|
-
this.current = undefined;
|
|
632
|
+
await this.signingOut('auth/logout');
|
|
624
633
|
}
|
|
625
|
-
/**
|
|
634
|
+
/**
|
|
635
|
+
* End every session of the signed-in account, on every device, this one
|
|
636
|
+
* too. Like {@link signOut}, this client is signed out even when the
|
|
637
|
+
* request fails, and the failure is thrown.
|
|
638
|
+
*/
|
|
626
639
|
async signOutEverywhere() {
|
|
627
|
-
await this.
|
|
628
|
-
|
|
629
|
-
|
|
640
|
+
await this.signingOut('auth/logout-all');
|
|
641
|
+
}
|
|
642
|
+
async signingOut(path) {
|
|
643
|
+
try {
|
|
644
|
+
await this.post(path);
|
|
645
|
+
}
|
|
646
|
+
finally {
|
|
647
|
+
this.auth = undefined;
|
|
648
|
+
this.current = undefined;
|
|
649
|
+
}
|
|
630
650
|
}
|
|
631
651
|
/** Who the credential is: the account, how it signed in, and the package (`app`) and `origin` a session is bound to. */
|
|
632
652
|
me() {
|
|
@@ -915,7 +935,7 @@ export class CalimeroClient {
|
|
|
915
935
|
deleteConfirmed(path, passkey, body = {}) {
|
|
916
936
|
return this.confirmed('DELETE', path, body, passkey);
|
|
917
937
|
}
|
|
918
|
-
async confirmed(method, path, body, passkey, send = (b) => this.json(method, path, b), userVerification = 'required') {
|
|
938
|
+
async confirmed(method, path, body, passkey, send = (b) => this.json(method, path, b), userVerification = 'required', signal) {
|
|
919
939
|
try {
|
|
920
940
|
return await send(body);
|
|
921
941
|
}
|
|
@@ -931,6 +951,7 @@ export class CalimeroClient {
|
|
|
931
951
|
// requirement the node names itself wins.
|
|
932
952
|
assertion = await passkey.get(fromB64url(d.challenge), d.rpId, d.allowCredentials, {
|
|
933
953
|
userVerification: userVerificationOf(d.userVerification, userVerification),
|
|
954
|
+
...(signal ? { signal } : {}),
|
|
934
955
|
});
|
|
935
956
|
}
|
|
936
957
|
catch (declined) {
|
|
@@ -1318,16 +1339,33 @@ export class CalimeroClient {
|
|
|
1318
1339
|
}
|
|
1319
1340
|
/**
|
|
1320
1341
|
* Set capability bits ({@link ORG_CAPABILITIES}): for a member (`account`),
|
|
1321
|
-
* for a member of one team (`account` and `team`),
|
|
1322
|
-
*
|
|
1342
|
+
* for a member of one team (`account` and `team`), a team's default
|
|
1343
|
+
* (`team` alone, {@link setTeamDefaultCapabilities}), or the
|
|
1344
|
+
* organization's default (neither). Approved with the caller's `passkey`.
|
|
1323
1345
|
*/
|
|
1324
1346
|
async setCapabilities(org, capabilities, passkey, at = {}) {
|
|
1325
1347
|
await this.postConfirmed(`orgs/${seg(org)}/capabilities`, { capabilities, ...at }, passkey);
|
|
1326
1348
|
}
|
|
1349
|
+
/**
|
|
1350
|
+
* Set the capability bits everyone in `team` holds there on top of their
|
|
1351
|
+
* own, unless their row sets its own (`POST /orgs/{org}/capabilities` with
|
|
1352
|
+
* `team` and no `account`): `ORG_CAPABILITIES.INVITE_MEMBERS` is "members
|
|
1353
|
+
* may invite others to this app". The team's managers, or whoever holds
|
|
1354
|
+
* `MANAGE_MEMBERS` on it; approved with the caller's `passkey`
|
|
1355
|
+
* (`__set_default_capabilities { team, capabilities }`).
|
|
1356
|
+
*/
|
|
1357
|
+
async setTeamDefaultCapabilities(org, team, capabilities, passkey) {
|
|
1358
|
+
await this.postConfirmed(`orgs/${seg(org)}/capabilities`, { team, capabilities }, passkey);
|
|
1359
|
+
}
|
|
1327
1360
|
async createTeam(org, name, join, parent) {
|
|
1328
1361
|
const { teamId } = await this.post(`orgs/${seg(org)}/teams`, { name, join, parent });
|
|
1329
1362
|
return teamId;
|
|
1330
1363
|
}
|
|
1364
|
+
/**
|
|
1365
|
+
* A team the caller sees: its rows (each with its effective capabilities),
|
|
1366
|
+
* children, apps, the package it is the app team of and its default
|
|
1367
|
+
* capabilities.
|
|
1368
|
+
*/
|
|
1331
1369
|
getTeam(org, team) {
|
|
1332
1370
|
return this.get(`orgs/${seg(org)}/teams/${seg(team)}`);
|
|
1333
1371
|
}
|
|
@@ -1365,6 +1403,52 @@ export class CalimeroClient {
|
|
|
1365
1403
|
redeemInvite(token) {
|
|
1366
1404
|
return this.post(`invites/${seg(token)}/redeem`);
|
|
1367
1405
|
}
|
|
1406
|
+
/**
|
|
1407
|
+
* The organization's pending invites the caller may see (never a token):
|
|
1408
|
+
* every one for an owner or admin, their own, and those naming only teams
|
|
1409
|
+
* they manage. Expired ones are listed (`expired: true`) until revoked. A
|
|
1410
|
+
* caller outside the organization: {@link NotFound}.
|
|
1411
|
+
*/
|
|
1412
|
+
async listInvites(org) {
|
|
1413
|
+
const { invites } = await this.get(`orgs/${seg(org)}/invites`);
|
|
1414
|
+
return invites;
|
|
1415
|
+
}
|
|
1416
|
+
/**
|
|
1417
|
+
* Withdraw an invite by its key ({@link listInvites}), approved with the
|
|
1418
|
+
* caller's `passkey` (`__revoke_invite { inviteKey }`). One the caller may
|
|
1419
|
+
* not see: {@link NotFound}.
|
|
1420
|
+
*/
|
|
1421
|
+
async revokeInvite(org, inviteKey, passkey) {
|
|
1422
|
+
await this.deleteConfirmed(`orgs/${seg(org)}/invites/${seg(inviteKey)}`, passkey);
|
|
1423
|
+
}
|
|
1424
|
+
// ---------------------------------------------------------------- app teams
|
|
1425
|
+
/**
|
|
1426
|
+
* The organization's mode and its app teams (one per package), for any
|
|
1427
|
+
* member; a guest sees only those they are in. A caller outside the
|
|
1428
|
+
* organization: {@link NotFound}.
|
|
1429
|
+
*/
|
|
1430
|
+
listAppTeams(org) {
|
|
1431
|
+
return this.get(`orgs/${seg(org)}/app-teams`);
|
|
1432
|
+
}
|
|
1433
|
+
/**
|
|
1434
|
+
* Give apps their own access lists in one step (owner or admin): find or
|
|
1435
|
+
* make each package's app team, add everyone who reaches its instances now,
|
|
1436
|
+
* and move those instances onto it; with `shared`, close every app team.
|
|
1437
|
+
* Idempotent per package. Approved with the caller's `passkey`
|
|
1438
|
+
* (`__scope_apps { apps, shared }`); a refusal is `400 REJECTED` with the
|
|
1439
|
+
* node's reason. For a fresh standalone install pass
|
|
1440
|
+
* `{ apps: [{ package, name, instances: [] }] }`.
|
|
1441
|
+
*/
|
|
1442
|
+
scopeApps(org, passkey, options = {}) {
|
|
1443
|
+
// The node refuses unknown fields and reads an absent one as its default,
|
|
1444
|
+
// so only the fields given go.
|
|
1445
|
+
const body = {};
|
|
1446
|
+
if (options.apps !== undefined)
|
|
1447
|
+
body.apps = options.apps;
|
|
1448
|
+
if (options.shared !== undefined)
|
|
1449
|
+
body.shared = options.shared;
|
|
1450
|
+
return this.postConfirmed(`orgs/${seg(org)}/app-teams`, body, passkey);
|
|
1451
|
+
}
|
|
1368
1452
|
// ---------------------------------------------------------------- deployers
|
|
1369
1453
|
/**
|
|
1370
1454
|
* Let an account or organization install apps where the cluster's
|
package/dist/cloud-v2/index.d.ts
CHANGED
|
@@ -3,12 +3,12 @@ export { DEFAULT_DIRECTORY, DirectoryError } from './directory.js';
|
|
|
3
3
|
export type { DirectoryFailure } from './directory.js';
|
|
4
4
|
export { AppError, ArgsTooLarge, BadOrigin, CalimeroError, ClusterNotUpgraded, ConfirmCancelled, ConfirmPromptBlocked, ConfirmRequired, IdempotencyMismatch, LoginRequired, ConsentStale, NotFound, NotServing, OriginNotRegistered, OriginTaken, PasskeyUnknownHere, QuotaExceeded, RecoveryNotReady, ReleaseDeadlineError, SignUpRefused, TrapError, UnavailableError, WrongNode, WrongRecoveryCode, errorFrom, } from './errors.js';
|
|
5
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, ServedOrigin, ServedOrigins, SignInOptions, AccountRecovery, SignUpOptions, SignUpPolicy, PublicDeployment, CallResult, CalimeroClientOptions, CalimeroConnectOptions, CloudEvent, CalimeroSession, PackageInfo, PackageInstalled, CloudRetryOptions, OrgInfo, OrgRecovery, RecoveryTimes, OrgUpgradeTarget, PresenceEntry, UpgradeRun, ViewResult, } from './client.js';
|
|
6
|
+
export type { AppInfo, AppTeam, AppTeams, AppXcalls, BlobContent, BlobInfo, DeadLetter, Deployer, DeployerGrant, DeployersInfo, DeploymentInfo, DeploymentOffer, DeploymentOrgs, FrontendManifest, MeInfo, PackageOrigin, PackageOriginAdded, PackageOriginRemoved, PackageOrigins, ServedOrigin, ServedOrigins, SignInOptions, AccountRecovery, SignUpOptions, SignUpPolicy, PublicDeployment, CallResult, CalimeroClientOptions, CalimeroConnectOptions, CloudEvent, CalimeroSession, PackageInfo, PackageInstalled, CloudRetryOptions, OrgInfo, OrgInvite, OrgMode, OrgRecovery, RecoveryTimes, OrgUpgradeTarget, PresenceEntry, ScopeApp, ScopeAppsOptions, ScopeAppsResult, ScopedAppTeam, TeamInfo, TeamMemberInfo, 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
10
|
export { MAX_RECOVERY_NONCE_BYTES, RECOVERY_DOMAIN, newRecoverySeed, parseRecoverySeed, recoveryMessage, recoveryPublicKey, recoverySeedText, signRecovery, } from './recovery.js';
|
|
11
|
-
export type { AssertionJSON, PasskeyGetOptions, PasskeyProvider, PasskeyUserVerification, RegistrationJSON, } from './passkey.js';
|
|
11
|
+
export type { AssertionJSON, PasskeyCreateOptions, PasskeyGetOptions, PasskeyProvider, PasskeyUserVerification, RegistrationJSON, } from './passkey.js';
|
|
12
12
|
export { MAX_REPORT_PAGE, RELEASE_VERIFIER, compareVersions, confirmationCode, consentReturn, pageQuery, pendingApprovalsFrom, publisherBindingBytes, publisherBindingMessage, releaseConsent, releaseNotesUrl, stalledBy, upgradeConsent, verifyReleaseUrl, withdrawConsent, } from './approvals.js';
|
|
13
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';
|
|
14
14
|
export { DEFAULT_ORG_CAPABILITIES, ORG_CAPABILITIES, orgCapabilities } from './capabilities.js';
|
|
@@ -26,16 +26,32 @@ export interface PasskeyGetOptions {
|
|
|
26
26
|
* `"preferred"` is enough.
|
|
27
27
|
*/
|
|
28
28
|
userVerification?: PasskeyUserVerification;
|
|
29
|
+
/**
|
|
30
|
+
* Closes the open prompt when it aborts (a Cancel button, a page leaving).
|
|
31
|
+
* The browser provider then rejects as a dismissed prompt does
|
|
32
|
+
* (`NotAllowedError`), so the caller treats it as a cancel.
|
|
33
|
+
*/
|
|
34
|
+
signal?: AbortSignal;
|
|
35
|
+
}
|
|
36
|
+
/** What a passkey creation takes beyond the node's options. */
|
|
37
|
+
export interface PasskeyCreateOptions {
|
|
38
|
+
/** As {@link PasskeyGetOptions.signal}: aborting closes the prompt, rejecting as a cancel (`NotAllowedError`). */
|
|
39
|
+
signal?: AbortSignal;
|
|
29
40
|
}
|
|
30
41
|
/** Something that holds a passkey. */
|
|
31
42
|
export interface PasskeyProvider {
|
|
32
|
-
/**
|
|
33
|
-
|
|
43
|
+
/**
|
|
44
|
+
* Answer `navigator.credentials.create` for the node's options.
|
|
45
|
+
* `createOptions.signal` closes the prompt when it aborts; a provider with
|
|
46
|
+
* no prompt to close may ignore it.
|
|
47
|
+
*/
|
|
48
|
+
create(options: Record<string, unknown>, createOptions?: PasskeyCreateOptions): Promise<RegistrationJSON>;
|
|
34
49
|
/**
|
|
35
50
|
* Sign `challenge` (an assertion), optionally limited to
|
|
36
51
|
* `allowCredentials`. `options.userVerification` is `"required"` when the
|
|
37
52
|
* client answers a step-up confirmation (`"preferred"` for the sign-up's);
|
|
38
53
|
* a provider that always verifies the user may ignore it.
|
|
54
|
+
* `options.signal` closes the prompt when it aborts.
|
|
39
55
|
*/
|
|
40
56
|
get(challenge: Uint8Array, rpId: string, allowCredentials?: string[], options?: PasskeyGetOptions): Promise<AssertionJSON>;
|
|
41
57
|
}
|
package/dist/cloud-v2/passkey.js
CHANGED
|
@@ -25,11 +25,22 @@ function cancelled(message) {
|
|
|
25
25
|
error.name = "NotAllowedError";
|
|
26
26
|
return error;
|
|
27
27
|
}
|
|
28
|
-
async function ask(method, options) {
|
|
28
|
+
async function ask(method, options, signal, what) {
|
|
29
29
|
const container = navigator.credentials;
|
|
30
30
|
const own = globalThis.CredentialsContainer?.prototype?.[method];
|
|
31
31
|
const call = own ?? container[method];
|
|
32
|
-
|
|
32
|
+
try {
|
|
33
|
+
return await call.call(container, signal ? { ...options, signal } : options);
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
// An aborted `signal` rejects with its reason (an `AbortError` unless the
|
|
37
|
+
// caller gave another): the prompt was closed on purpose, so it reads as
|
|
38
|
+
// a dismissed one.
|
|
39
|
+
if (signal?.aborted || error?.name === "AbortError") {
|
|
40
|
+
throw Object.assign(cancelled(`${what} was cancelled`), { cause: error });
|
|
41
|
+
}
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
33
44
|
}
|
|
34
45
|
/**
|
|
35
46
|
* The node's algorithms with ES256 alone when it is among them. Some password
|
|
@@ -48,7 +59,7 @@ export function es256First(params) {
|
|
|
48
59
|
/** The platform authenticator (Touch ID, Face ID, Windows Hello, a security key, a synced passkey). */
|
|
49
60
|
export function browserPasskey() {
|
|
50
61
|
return {
|
|
51
|
-
async create(options) {
|
|
62
|
+
async create(options, createOptions) {
|
|
52
63
|
const o = options;
|
|
53
64
|
const cred = (await ask("create", {
|
|
54
65
|
publicKey: {
|
|
@@ -64,7 +75,7 @@ export function browserPasskey() {
|
|
|
64
75
|
attestation: "none",
|
|
65
76
|
timeout: o.timeout,
|
|
66
77
|
},
|
|
67
|
-
}));
|
|
78
|
+
}, createOptions?.signal, "passkey creation"));
|
|
68
79
|
if (!cred)
|
|
69
80
|
throw cancelled("passkey creation was cancelled");
|
|
70
81
|
const r = cred.response;
|
|
@@ -84,7 +95,7 @@ export function browserPasskey() {
|
|
|
84
95
|
id: buf(fromB64url(id)),
|
|
85
96
|
})),
|
|
86
97
|
},
|
|
87
|
-
}));
|
|
98
|
+
}, options?.signal, "passkey sign-in"));
|
|
88
99
|
if (!cred)
|
|
89
100
|
throw cancelled("passkey sign-in was cancelled");
|
|
90
101
|
const r = cred.response;
|