@cubist-labs/cubesigner-sdk 0.1.77 → 0.2.2

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.
@@ -7,15 +7,9 @@ import {
7
7
  getBytes,
8
8
  toBeHex,
9
9
  } from "ethers";
10
- import {
11
- BlobSignRequest,
12
- EvmSignRequest,
13
- MfaRequestInfo,
14
- SignerSession,
15
- SignResponse,
16
- } from "../signer_session";
10
+ import { SignerSession, CubeSignerResponse } from "../signer_session";
11
+ import { BlobSignRequest, EvmSignRequest, MfaRequestInfo } from "../schema_types";
17
12
  import { KeyInfo } from "../key";
18
- import { CubeSigner } from "..";
19
13
 
20
14
  /** Options for the signer */
21
15
  interface SignerOptions {
@@ -31,8 +25,6 @@ interface SignerOptions {
31
25
  * updates. Default is 1000ms
32
26
  */
33
27
  mfaPollIntervalMs?: number;
34
- /** Optional management session. Used to check for MFA updates */
35
- managementSession?: CubeSigner;
36
28
  }
37
29
 
38
30
  /**
@@ -57,9 +49,6 @@ export class Signer extends ethers.AbstractSigner {
57
49
  /** The amount of time to wait between checks for MFA updates */
58
50
  readonly #mfaPollIntervalMs: number;
59
51
 
60
- /** Optional management session, used for MFA flows */
61
- readonly #managementSession?: CubeSigner;
62
-
63
52
  /** Create new Signer instance
64
53
  * @param {KeyInfo | string} address The key or the eth address of the account to use.
65
54
  * @param {SignerSession} signerSession The underlying Signer session.
@@ -76,7 +65,6 @@ export class Signer extends ethers.AbstractSigner {
76
65
  this.#signerSession = signerSession;
77
66
  this.#onMfaPoll = options?.onMfaPoll ?? ((/* _mfaInfo: MfaRequestInfo */) => {}); // eslint-disable-line @typescript-eslint/no-empty-function
78
67
  this.#mfaPollIntervalMs = options?.mfaPollIntervalMs ?? 1000;
79
- this.#managementSession = options?.managementSession;
80
68
  }
81
69
 
82
70
  /** Resolves to the signer address. */
@@ -180,15 +168,15 @@ export class Signer extends ethers.AbstractSigner {
180
168
  /**
181
169
  * If the sign request requires MFA, this method waits for approvals
182
170
  *
183
- * @param {SignResponse<U>} res The response of a sign request
171
+ * @param {CubeSignerResponse<U>} res The response of a sign request
184
172
  * @return {Promise<U>} The sign data after MFA approvals
185
173
  */
186
- async #handleMfa<U>(res: SignResponse<U>): Promise<U> {
174
+ async #handleMfa<U>(res: CubeSignerResponse<U>): Promise<U> {
187
175
  while (res.requiresMfa()) {
188
176
  await new Promise((resolve) => setTimeout(resolve, this.#mfaPollIntervalMs));
189
177
 
190
178
  const mfaId = res.mfaId();
191
- const mfaInfo = await this.#signerSession.getMfaInfo(this.#managementSession!, mfaId);
179
+ const mfaInfo = await this.#signerSession.getMfaInfo(mfaId);
192
180
  this.#onMfaPoll(mfaInfo);
193
181
  if (mfaInfo.receipt) {
194
182
  res = await res.signWithMfaApproval({
package/src/index.ts CHANGED
@@ -1,15 +1,21 @@
1
1
  import { envs, EnvInterface } from "./env";
2
- import { components, Client, paths } from "./client";
2
+ import { Client, CubeSignerClient, OidcClient } from "./client";
3
3
  import { Org } from "./org";
4
4
  import { JsonFileSessionStorage } from "./session/session_storage";
5
5
 
6
6
  import { SignerSessionStorage, SignerSessionManager } from "./session/signer_session_manager";
7
- import { AcceptedResponse, MfaRequestInfo, SignResponse, SignerSession } from "./signer_session";
7
+ import { CubeSignerResponse, SignerSession } from "./signer_session";
8
8
  import { CognitoSessionManager, CognitoSessionStorage } from "./session/cognito_manager";
9
- import { assertOk, configDir } from "./util";
9
+ import { configDir } from "./util";
10
10
  import * as path from "path";
11
- import createClient from "openapi-fetch";
12
- import { AddFidoChallenge, ApiAddFidoChallenge, PublicKeyCredential } from "./fido";
11
+ import { MfaReceipt } from "./mfa";
12
+ import {
13
+ IdentityProof,
14
+ MfaRequestInfo,
15
+ OidcAuthResponse,
16
+ RatchetConfig,
17
+ UserInfo,
18
+ } from "./schema_types";
13
19
 
14
20
  /** CubeSigner constructor options */
15
21
  export interface CubeSignerOptions {
@@ -21,61 +27,32 @@ export interface CubeSignerOptions {
21
27
  orgId?: string;
22
28
  }
23
29
 
24
- export type UserInfo = components["schemas"]["UserInfo"];
25
- export type TotpInfo = components["responses"]["TotpInfo"]["content"]["application/json"];
26
- export type ConfiguredMfa = components["schemas"]["ConfiguredMfa"];
27
- export type RatchetConfig = components["schemas"]["RatchetConfig"];
28
- export type IdentityProof = components["schemas"]["IdentityProof"];
29
-
30
- type OidcAuthResponse =
31
- paths["/v0/org/{org_id}/oidc"]["post"]["responses"]["200"]["content"]["application/json"];
32
-
33
- /** TOTP challenge that must be answered before user's TOTP is updated */
34
- export class TotpChallenge {
35
- readonly #cs: CubeSigner;
36
- readonly #totpInfo: TotpInfo;
37
- /** The id of the challenge */
38
- get totpId() {
39
- return this.#totpInfo.totp_id;
40
- }
41
- /** The new TOTP configuration */
42
- get totpUrl() {
43
- return this.#totpInfo.totp_url;
44
- }
45
- /**
46
- * @param {CubeSigner} cs Used when answering the challenge.
47
- * @param {TotpInfo} totpInfo TOTP challenge information.
48
- */
49
- constructor(cs: CubeSigner, totpInfo: TotpInfo) {
50
- this.#cs = cs;
51
- this.#totpInfo = totpInfo;
52
- }
53
- /**
54
- * Answer the challenge with the code that corresponds to this `this.totpUrl`.
55
- * @param {string} code 6-digit code that corresponds to this `this.totpUrl`.
56
- */
57
- async answer(code: string) {
58
- await this.#cs.resetTotpComplete(this.totpId, code);
59
- }
60
- }
61
-
62
- /** CubeSigner client */
30
+ /**
31
+ * CubeSigner client
32
+ *
33
+ * @deprecated Use {@link CubeSignerClient} instead.
34
+ */
63
35
  export class CubeSigner {
64
36
  readonly #env: EnvInterface;
65
37
  readonly sessionMgr?: CognitoSessionManager | SignerSessionManager;
66
- #orgId?: string;
38
+ #csc: CubeSignerClient;
67
39
 
68
40
  /** @return {EnvInterface} The CubeSigner environment of this client */
69
41
  get env(): EnvInterface {
70
42
  return this.#env;
71
43
  }
72
44
 
45
+ /** Organization ID */
46
+ get orgId() {
47
+ return this.#csc.orgId;
48
+ }
49
+
73
50
  /**
74
51
  * Set the organization ID
75
52
  * @param {string} orgId The new organization id.
76
53
  */
77
54
  setOrgId(orgId: string) {
78
- this.#orgId = orgId;
55
+ this.#csc = this.#csc.withOrg(orgId);
79
56
  }
80
57
 
81
58
  /**
@@ -111,7 +88,7 @@ export class CubeSigner {
111
88
 
112
89
  /**
113
90
  * Create a new CubeSigner instance.
114
- * @param {CubeSignerOptions} options The optional configuraiton options for the CubeSigner instance.
91
+ * @param {CubeSignerOptions} options The optional configuration options for the CubeSigner instance.
115
92
  */
116
93
  constructor(options?: CubeSignerOptions) {
117
94
  let env = options?.env;
@@ -120,11 +97,21 @@ export class CubeSigner {
120
97
  env = env ?? this.sessionMgr.env;
121
98
  }
122
99
  this.#env = env ?? envs["gamma"];
123
- this.#orgId = options?.orgId;
100
+ this.#csc = new CubeSignerClient(
101
+ // HACK: ignore that sessionMgr may be a CognitoSessionManager and pretend that it
102
+ // is a SignerSessionManager; that's fine because the CubeSignerClient will
103
+ // almost always just call `await token()` on it, which works in both cases.
104
+ //
105
+ // This is done here for backward compatibility reasons only; in the future,
106
+ // we should deprecate this class and people should start using `CubeSingerClient` directly.
107
+ options?.sessionMgr as unknown as SignerSessionManager,
108
+ options?.orgId,
109
+ );
124
110
  }
125
111
 
126
112
  /**
127
113
  * Authenticate an OIDC user and create a new session manager for them.
114
+ *
128
115
  * @param {string} oidcToken The OIDC token
129
116
  * @param {string} orgId The id of the organization that the user is in
130
117
  * @param {List<string>} scopes The scopes of the resulting session
@@ -149,17 +136,7 @@ export class CubeSigner {
149
136
  * @return {Promise<UserInfo>} User information.
150
137
  */
151
138
  async aboutMe(): Promise<UserInfo> {
152
- const client = await this.management();
153
- const resp = this.#orgId
154
- ? await client.get("/v0/org/{org_id}/user/me", {
155
- params: { path: { org_id: this.#orgId } },
156
- parseAs: "json",
157
- })
158
- : await client.get("/v0/about_me", {
159
- parseAs: "json",
160
- });
161
- const data = assertOk(resp);
162
- return data;
139
+ return await this.#csc.userGet();
163
140
  }
164
141
 
165
142
  /**
@@ -170,12 +147,7 @@ export class CubeSigner {
170
147
  * @return {Promise<MfaRequestInfo>} MFA request information
171
148
  */
172
149
  async mfaGet(orgId: string, mfaId: string): Promise<MfaRequestInfo> {
173
- const resp = await (
174
- await this.management()
175
- ).get("/v0/org/{org_id}/mfa/{mfa_id}", {
176
- params: { path: { org_id: orgId, mfa_id: mfaId } },
177
- });
178
- return assertOk(resp);
150
+ return await this.#csc.withOrg(orgId).mfaGet(mfaId);
179
151
  }
180
152
 
181
153
  /**
@@ -184,12 +156,7 @@ export class CubeSigner {
184
156
  * @return {Promise<MfaRequestInfo[]>} The MFA requests.
185
157
  */
186
158
  async mfaList(orgId: string): Promise<MfaRequestInfo[]> {
187
- const resp = await (
188
- await this.management()
189
- ).get("/v0/org/{org_id}/mfa", {
190
- params: { path: { org_id: orgId } },
191
- });
192
- return assertOk(resp).mfa_requests;
159
+ return await this.#csc.withOrg(orgId).mfaList();
193
160
  }
194
161
 
195
162
  /**
@@ -200,158 +167,50 @@ export class CubeSigner {
200
167
  * @return {Promise<MfaRequestInfo>} The result of the MFA request
201
168
  */
202
169
  async mfaApprove(orgId: string, mfaId: string): Promise<MfaRequestInfo> {
203
- const resp = await (
204
- await this.management()
205
- ).patch("/v0/org/{org_id}/mfa/{mfa_id}", {
206
- params: { path: { org_id: orgId, mfa_id: mfaId } },
207
- });
208
- return assertOk(resp);
170
+ return await this.#csc.withOrg(orgId).mfaApprove(mfaId);
209
171
  }
210
172
 
211
- /**
212
- * Initiate adding a new FIDO device. MFA may be required.
213
- * @param {string} name The name of the new device.
214
- * @param {MfaReceipt} mfaReceipt Optional MFA receipt to include in HTTP headers
215
- * @return {Promise<SignResponse<AddFidoChallenge>>} A challenge that must be answered in order to complete FIDO registration.
216
- */
217
- async addFidoStart(
218
- name: string,
219
- mfaReceipt?: MfaReceipt,
220
- ): Promise<SignResponse<AddFidoChallenge>> {
221
- const orgId = this.#orgId || mfaReceipt?.mfaOrgId;
222
- if (!orgId) {
223
- throw new Error("Org ID must be set");
224
- }
225
- const addFidoFn = async (headers?: HeadersInit) => {
226
- const client = await this.management();
227
- const resp = await client.post("/v0/org/{org_id}/user/me/fido", {
228
- headers,
229
- params: { path: { org_id: orgId } },
230
- body: { name },
231
- parseAs: "json",
232
- });
233
- const x = assertOk(resp);
234
- // TODO: add mapFn to SignResponse
235
- if ((x as AcceptedResponse).accepted?.MfaRequired) {
236
- return x as AcceptedResponse;
237
- } else {
238
- return new AddFidoChallenge(this, x as ApiAddFidoChallenge);
239
- }
240
- };
241
- return await SignResponse.create(addFidoFn, mfaReceipt);
173
+ /** Initiate adding a new FIDO device. MFA may be required. */
174
+ get addFidoStart() {
175
+ return this.#csc.userRegisterFidoInit.bind(this.#csc);
242
176
  }
243
177
 
244
- /**
245
- * Complete a previously initiated request to add a new FIDO device.
246
- * @param {string} challengeId The ID of the challenge returned by the remote end.
247
- * @param {PublicKeyCredential} credential The answer to the challenge.
248
- */
249
- async addFidoComplete(challengeId: string, credential: PublicKeyCredential) {
250
- const orgId = this.#orgId;
251
- if (!orgId) {
252
- throw new Error("Org ID must be set");
253
- }
254
- const client = await this.management();
255
- const resp = await client.patch("/v0/org/{org_id}/user/me/fido", {
256
- params: { path: { org_id: orgId } },
257
- body: {
258
- challenge_id: challengeId,
259
- credential,
260
- },
261
- parseAs: "json",
262
- });
263
- assertOk(resp);
178
+ /** Complete a previously initiated request to add a new FIDO device. */
179
+ get addFidoComplete() {
180
+ return this.#csc.userRegisterFidoComplete.bind(this.#csc);
264
181
  }
265
182
 
266
183
  /**
267
184
  * Creates a request to change user's TOTP. This request returns a new TOTP challenge
268
185
  * that must be answered by calling `resetTotpComplete`
269
- *
270
- * @param {MfaReceipt} mfaReceipt MFA receipt to include in HTTP headers
271
186
  */
272
- async resetTotpStart(mfaReceipt?: MfaReceipt): Promise<SignResponse<TotpChallenge>> {
273
- const resetTotpFn = async (headers?: HeadersInit) => {
274
- const orgId = this.#orgId || mfaReceipt?.mfaOrgId;
275
- const client = await this.management();
276
- const resp = orgId
277
- ? await client.post("/v0/org/{org_id}/user/me/totp", {
278
- headers,
279
- params: { path: { org_id: orgId } },
280
- body: null,
281
- parseAs: "json",
282
- })
283
- : await client.post("/v0/user/me/totp", {
284
- headers,
285
- body: null,
286
- parseAs: "json",
287
- });
288
- const x = assertOk(resp);
289
- // TODO: add mapFn to SignResponse
290
- if ((x as AcceptedResponse).accepted?.MfaRequired) {
291
- return x as AcceptedResponse;
292
- } else {
293
- return new TotpChallenge(this, x as TotpInfo);
294
- }
295
- };
296
- return await SignResponse.create(resetTotpFn, mfaReceipt);
187
+ get resetTotpStart() {
188
+ return this.#csc.userResetTotpInit.bind(this.#csc);
297
189
  }
298
190
 
299
191
  /**
300
192
  * Answer the TOTP challenge issued by `resetTotpStart`. If successful, user's
301
- * TOTP configuration will be updated to that of the TOTP challenge.
302
- *
303
- * @param {string} totpId - The ID of the TOTP challenge
304
- * @param {string} code - The TOTP code that should verify against the TOTP configuration from the challenge.
193
+ * TOTP configuration will be updated to that of the TOTP challenge.he TOTP configuration from the challenge.
305
194
  */
306
- async resetTotpComplete(totpId: string, code: string): Promise<void> {
307
- const client = await this.management();
308
- const resp = this.#orgId
309
- ? await client.patch("/v0/org/{org_id}/user/me/totp", {
310
- parseAs: "json",
311
- params: { path: { org_id: this.#orgId } },
312
- body: { totp_id: totpId, code },
313
- })
314
- : await client.patch("/v0/user/me/totp", {
315
- parseAs: "json",
316
- body: { totp_id: totpId, code },
317
- });
318
- assertOk(resp);
195
+ get resetTotpComplete() {
196
+ return this.#csc.userResetTotpComplete.bind(this.#csc);
319
197
  }
320
198
 
321
199
  /**
322
200
  * Verifies a given TOTP code against the current user's TOTP configuration.
323
201
  * Throws an error if the verification fails.
324
- * @param {string} code Current TOTP code
325
202
  */
326
- async verifyTotp(code: string) {
327
- const client = await this.management();
328
- const resp = this.#orgId
329
- ? await client.post("/v0/org/{org_id}/user/me/totp/verify", {
330
- params: { path: { org_id: this.#orgId } },
331
- body: { code },
332
- parseAs: "json",
333
- })
334
- : await client.post("/v0/user/me/totp/verify", {
335
- body: { code },
336
- parseAs: "json",
337
- });
338
- assertOk(resp);
203
+ get verifyTotp() {
204
+ return this.#csc.userVerifyTotp.bind(this.#csc);
339
205
  }
340
206
 
341
207
  /** Retrieves information about an organization.
342
208
  * @param {string} orgId The ID or name of the organization.
343
209
  * @return {Org} The organization.
344
210
  * */
345
- async getOrg(orgId: string): Promise<Org> {
346
- const resp = await (
347
- await this.management()
348
- ).get("/v0/org/{org_id}", {
349
- params: { path: { org_id: orgId } },
350
- parseAs: "json",
351
- });
352
-
353
- const data = assertOk(resp);
354
- return new Org(this, data);
211
+ async getOrg(orgId?: string): Promise<Org> {
212
+ const orgInfo = await this.#csc.withOrg(orgId).orgGet();
213
+ return new Org(this.#csc, orgInfo);
355
214
  }
356
215
 
357
216
  /**
@@ -360,13 +219,7 @@ export class CubeSigner {
360
219
  * @param {string} keyId - Key id
361
220
  */
362
221
  async deleteKey(orgId: string, keyId: string) {
363
- const resp = await (
364
- await this.management()
365
- ).del("/v0/org/{org_id}/keys/{key_id}", {
366
- params: { path: { org_id: orgId, key_id: keyId } },
367
- parseAs: "json",
368
- });
369
- assertOk(resp);
222
+ await this.#csc.withOrg(orgId).keyDelete(keyId);
370
223
  }
371
224
 
372
225
  /** Get the management client.
@@ -387,12 +240,7 @@ export class CubeSigner {
387
240
  * @return {Promise<IdentityProof>} Proof of authentication
388
241
  */
389
242
  async proveIdentity(orgId: string): Promise<IdentityProof> {
390
- const client = await this.management();
391
- const resp = await client.post("/v0/org/{org_id}/identity/prove", {
392
- params: { path: { org_id: orgId } },
393
- parseAs: "json",
394
- });
395
- return assertOk(resp);
243
+ return await this.#csc.withOrg(orgId).identityProve();
396
244
  }
397
245
 
398
246
  /**
@@ -403,17 +251,8 @@ export class CubeSigner {
403
251
  * @return {Promise<IdentityProof>} Proof of authentication
404
252
  */
405
253
  async oidcProveIdentity(oidcToken: string, orgId: string): Promise<IdentityProof> {
406
- const client = createClient<paths>({
407
- baseUrl: this.env.SignerApiRoot,
408
- headers: {
409
- Authorization: oidcToken,
410
- },
411
- });
412
- const resp = await client.post("/v0/org/{org_id}/identity/prove/oidc", {
413
- params: { path: { org_id: orgId } },
414
- parseAs: "json",
415
- });
416
- return assertOk(resp);
254
+ const oidcClient = new OidcClient(this.#env, orgId, oidcToken);
255
+ return await oidcClient.identityProve();
417
256
  }
418
257
 
419
258
  /**
@@ -423,14 +262,7 @@ export class CubeSigner {
423
262
  * @param {IdentityProof} identityProof The proof of authentication.
424
263
  */
425
264
  async verifyIdentity(orgId: string, identityProof: IdentityProof) {
426
- const resp = await (
427
- await this.management()
428
- ).post("/v0/org/{org_id}/identity/verify", {
429
- params: { path: { org_id: orgId } },
430
- body: identityProof,
431
- parseAs: "json",
432
- });
433
- assertOk(resp);
265
+ await this.#csc.withOrg(orgId).identityVerify(identityProof);
434
266
  }
435
267
 
436
268
  /**
@@ -440,7 +272,7 @@ export class CubeSigner {
440
272
  * @param {List<string>} scopes The scopes of the resulting session
441
273
  * @param {RatchetConfig} lifetimes Lifetimes of the new session.
442
274
  * @param {MfaReceipt} mfaReceipt Optional MFA receipt (id + confirmation code)
443
- * @return {Promise<SignResponse<OidcAuthResponse>>} The session data.
275
+ * @return {Promise<CubeSignerResponse<OidcAuthResponse>>} The session data.
444
276
  */
445
277
  async oidcLogin(
446
278
  oidcToken: string,
@@ -448,41 +280,14 @@ export class CubeSigner {
448
280
  scopes: Array<string>,
449
281
  lifetimes?: RatchetConfig,
450
282
  mfaReceipt?: MfaReceipt,
451
- ): Promise<SignResponse<OidcAuthResponse>> {
452
- const client = createClient<paths>({
453
- baseUrl: this.env.SignerApiRoot,
454
- headers: {
455
- Authorization: oidcToken,
456
- },
457
- });
458
- const loginFn = async (headers?: HeadersInit) => {
459
- const resp = await client.post("/v0/org/{org_id}/oidc", {
460
- params: { path: { org_id: orgId } },
461
- headers,
462
- body: {
463
- scopes,
464
- tokens: lifetimes,
465
- },
466
- parseAs: "json",
467
- });
468
- return assertOk(resp);
469
- };
470
-
471
- const h1 = mfaReceipt ? SignResponse.getMfaHeaders(mfaReceipt) : undefined;
472
- return new SignResponse(loginFn, await loginFn(h1));
283
+ ): Promise<CubeSignerResponse<OidcAuthResponse>> {
284
+ const oidcClient = new OidcClient(this.#env, orgId, oidcToken);
285
+ return await oidcClient.sessionCreate(scopes, lifetimes, mfaReceipt);
473
286
  }
474
287
  }
475
288
 
476
- /** MFA receipt */
477
- export interface MfaReceipt {
478
- /** MFA request ID */
479
- mfaId: string;
480
- /** Corresponding org ID */
481
- mfaOrgId: string;
482
- /** MFA confirmation code */
483
- mfaConf: string;
484
- }
485
-
289
+ /** Client */
290
+ export * from "./client";
486
291
  /** Organizations */
487
292
  export * from "./org";
488
293
  /** Keys */
@@ -492,9 +297,11 @@ export * from "./role";
492
297
  /** Env */
493
298
  export * from "./env";
494
299
  /** Fido */
495
- export * from "./fido";
300
+ export * from "./mfa";
496
301
  /** Pagination */
497
302
  export * from "./paginator";
303
+ /** Types */
304
+ export * from "./schema_types";
498
305
  /** Sessions */
499
306
  export * from "./signer_session";
500
307
  /** Session storage */