@cubist-labs/cubesigner-sdk 0.3.1 → 0.3.11

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.
Files changed (62) hide show
  1. package/dist/cjs/package.json +1 -1
  2. package/dist/cjs/src/api.d.ts +27 -9
  3. package/dist/cjs/src/api.js +58 -22
  4. package/dist/cjs/src/client.d.ts +72 -3
  5. package/dist/cjs/src/client.js +79 -2
  6. package/dist/cjs/src/error.d.ts +26 -0
  7. package/dist/cjs/src/error.js +64 -1
  8. package/dist/cjs/src/events.d.ts +31 -9
  9. package/dist/cjs/src/events.js +56 -25
  10. package/dist/cjs/src/index.js +3 -2
  11. package/dist/cjs/src/key.d.ts +19 -2
  12. package/dist/cjs/src/key.js +22 -2
  13. package/dist/cjs/src/mfa.d.ts +6 -3
  14. package/dist/cjs/src/mfa.js +8 -5
  15. package/dist/cjs/src/response.d.ts +14 -1
  16. package/dist/cjs/src/response.js +65 -26
  17. package/dist/cjs/src/role.d.ts +6 -0
  18. package/dist/cjs/src/role.js +9 -1
  19. package/dist/cjs/src/schema.d.ts +339 -45
  20. package/dist/cjs/src/schema.js +1 -1
  21. package/dist/cjs/src/schema_types.d.ts +3 -0
  22. package/dist/cjs/src/schema_types.js +1 -1
  23. package/dist/cjs/src/session/signer_session_manager.js +3 -3
  24. package/dist/cjs/src/util.js +3 -2
  25. package/dist/esm/package.json +1 -1
  26. package/dist/esm/src/api.d.ts +27 -9
  27. package/dist/esm/src/api.js +56 -20
  28. package/dist/esm/src/client.d.ts +72 -3
  29. package/dist/esm/src/client.js +79 -2
  30. package/dist/esm/src/error.d.ts +26 -0
  31. package/dist/esm/src/error.js +64 -1
  32. package/dist/esm/src/events.d.ts +31 -9
  33. package/dist/esm/src/events.js +53 -23
  34. package/dist/esm/src/index.js +2 -2
  35. package/dist/esm/src/key.d.ts +19 -2
  36. package/dist/esm/src/key.js +22 -2
  37. package/dist/esm/src/mfa.d.ts +6 -3
  38. package/dist/esm/src/mfa.js +8 -5
  39. package/dist/esm/src/response.d.ts +14 -1
  40. package/dist/esm/src/response.js +65 -26
  41. package/dist/esm/src/role.d.ts +6 -0
  42. package/dist/esm/src/role.js +9 -1
  43. package/dist/esm/src/schema.d.ts +339 -45
  44. package/dist/esm/src/schema.js +1 -1
  45. package/dist/esm/src/schema_types.d.ts +3 -0
  46. package/dist/esm/src/schema_types.js +1 -1
  47. package/dist/esm/src/session/signer_session_manager.js +3 -3
  48. package/dist/esm/src/util.js +3 -2
  49. package/package.json +1 -1
  50. package/src/api.ts +66 -19
  51. package/src/client.ts +94 -2
  52. package/src/error.ts +73 -0
  53. package/src/events.ts +53 -24
  54. package/src/key.ts +31 -2
  55. package/src/mfa.ts +8 -4
  56. package/src/response.ts +50 -4
  57. package/src/role.ts +9 -0
  58. package/src/schema.ts +597 -45
  59. package/src/schema_types.ts +3 -0
  60. package/src/session/signer_session_manager.ts +2 -2
  61. package/src/util.ts +2 -3
  62. package/tsconfig.json +3 -3
package/src/events.ts CHANGED
@@ -3,8 +3,11 @@ import { ErrResponse } from "./error";
3
3
  export type EventHandler<T> = (event: T) => Promise<void>;
4
4
  export type ErrorEvent = ErrResponse;
5
5
 
6
- /* eslint-disable-next-line @typescript-eslint/no-empty-interface */
7
- export interface SessionExpiredEvent {}
6
+ /** Event emitted when a request fails because of an expired/invalid session */
7
+ export class SessionExpiredEvent {}
8
+
9
+ /** Event emitted when a request fails because user failed to answer an MFA challenge */
10
+ export class UserMfaFailedEvent extends ErrResponse {}
8
11
 
9
12
  /**
10
13
  * Dispatcher for a single event type.
@@ -58,31 +61,13 @@ class EventDispatcher<T> {
58
61
  }
59
62
  }
60
63
 
61
- const SessionExpiredRegexes = [
62
- /^Session '(?<purpose>[^']*)' for '(?<identity>[^']*)' has expired$/,
63
- /^Session '(?<purpose>[^']*)' for '(?<identity>[^']*)' has been revoked$/,
64
- /^Auth token for epoch (?<epoch>\d+) has expired$/,
65
- /^Refresh token for epoch (?<epoch_num>\d+) has expired$/,
66
- /^Outdated session$/,
67
- ];
68
-
69
- /**
70
- * Whether an error message matches one of several different "session expired" responses.
71
- *
72
- * @param {string} msg The string to test.
73
- * @return {boolean} Whether the string matches.
74
- * @internal Exported only so that it can be called from a unit test
75
- */
76
- export function messageMatchesSessionExpired(msg: string): boolean {
77
- return SessionExpiredRegexes.some((re) => re.test(msg));
78
- }
79
-
80
64
  /**
81
65
  * Class for registering and unregistering event handlers.
82
66
  */
83
67
  export class Events {
84
68
  readonly #onError = new EventDispatcher<ErrorEvent>();
85
69
  readonly #onSessionExpired = new EventDispatcher<SessionExpiredEvent>();
70
+ readonly #onUserMfaFailed = new EventDispatcher<UserMfaFailedEvent>();
86
71
 
87
72
  /**
88
73
  * Register a handler for {@link ErrorEvent}: triggered every time a request to
@@ -104,6 +89,17 @@ export class Events {
104
89
  this.#onSessionExpired.register(handler);
105
90
  }
106
91
 
92
+ /**
93
+ * Register a handler for {@link UserMfaFailedEvent}: triggered every time a
94
+ * request to a CubeSigner API endpoint fails because the user failed to
95
+ * answer an MFA challenge.
96
+ *
97
+ * @param {EventHandler<UserMfaFailedEvent>} handler The handler to register.
98
+ */
99
+ onUserMfaFailed(handler: EventHandler<UserMfaFailedEvent>) {
100
+ this.#onUserMfaFailed.register(handler);
101
+ }
102
+
107
103
  /**
108
104
  * Unregister a handler for {@link ErrorEvent}.
109
105
  *
@@ -124,9 +120,27 @@ export class Events {
124
120
  return this.#onSessionExpired.unregister(handler);
125
121
  }
126
122
 
123
+ /**
124
+ * Unregister a handler for {@link UserMfaFailedEvent}.
125
+ *
126
+ * @param {EventHandler<UserMfaFailedEvent>} handler The handler to unregister.
127
+ * @return {boolean} Whether the handler was found (and unregistered).
128
+ */
129
+ unregisterOnUserMfaFailed(handler: EventHandler<UserMfaFailedEvent>): boolean {
130
+ return this.#onUserMfaFailed.unregister(handler);
131
+ }
132
+
127
133
  /** @internal */
128
134
  async triggerSessionExpired() {
129
- await this.#onSessionExpired.dispatch(<SessionExpiredEvent>{});
135
+ await this.#onSessionExpired.dispatch(new SessionExpiredEvent());
136
+ }
137
+
138
+ /**
139
+ * @param {UserMfaFailedEvent} ev The event to emit
140
+ * @internal
141
+ */
142
+ async triggerUserMfaFailed(ev: UserMfaFailedEvent) {
143
+ await this.#onUserMfaFailed.dispatch(ev);
130
144
  }
131
145
 
132
146
  /**
@@ -168,13 +182,17 @@ export class EventEmitter {
168
182
  await ev.triggerErrorEvent(err);
169
183
  }
170
184
 
171
- // if status is 403 and error matches one of the SessionExpiredRegexes trigger onSessionExpired
185
+ if (err.isUserMfaError()) {
186
+ await this.emitUserMfaFailed(err);
187
+ }
188
+
189
+ // if status is 403 and error matches one of the "invalid session" error codes trigger onSessionExpired
172
190
  //
173
191
  // TODO: because errors returned by the authorizer lambda are not forwarded to the client
174
192
  // we also trigger onSessionExpired when "signerSessionRefresh" fails
175
193
  if (
176
194
  err.status === 403 &&
177
- (messageMatchesSessionExpired(err.message) || err.operation == "signerSessionRefresh")
195
+ (err.isSessionExpiredError() || err.operation == "signerSessionRefresh")
178
196
  ) {
179
197
  await this.emitSessionExpired();
180
198
  }
@@ -191,6 +209,17 @@ export class EventEmitter {
191
209
  await e.triggerSessionExpired();
192
210
  }
193
211
  }
212
+
213
+ /**
214
+ * Emits {@link UserMfaFailedEvent} to all subscribers
215
+ *
216
+ * @param {UserMfaFailedEvent} ev The event to emit.
217
+ */
218
+ private async emitUserMfaFailed(ev: UserMfaFailedEvent) {
219
+ for (const e of this.#events) {
220
+ await e.triggerUserMfaFailed(ev);
221
+ }
222
+ }
194
223
  }
195
224
 
196
225
  /**
package/src/key.ts CHANGED
@@ -1,5 +1,12 @@
1
1
  import { KeyPolicy } from "./role";
2
- import { KeyInfoApi, KeyTypeApi, UpdateKeyRequest, SchemaKeyType } from "./schema_types";
2
+ import { PageOpts } from "./paginator";
3
+ import {
4
+ KeyInfoApi,
5
+ KeyTypeApi,
6
+ UpdateKeyRequest,
7
+ SchemaKeyType,
8
+ KeyInRoleInfo,
9
+ } from "./schema_types";
3
10
  import { CubeSignerClient } from "./client";
4
11
 
5
12
  /** Secp256k1 key type */
@@ -138,6 +145,15 @@ export class Key {
138
145
  await this.update({ enabled: false });
139
146
  }
140
147
 
148
+ /**
149
+ * The list roles this key is in.
150
+ * @param {PageOpts} page Optional pagination options; by default, retrieves all roles this key is in.
151
+ * @return {Promise<KeyInRoleInfo[]>} Roles this key is in.
152
+ */
153
+ async roles(page?: PageOpts): Promise<KeyInRoleInfo[]> {
154
+ return await this.csc.keyRolesList(this.id, page).fetch();
155
+ }
156
+
141
157
  /**
142
158
  * Set new policy (overwriting any policies previously set for this key)
143
159
  * @param {KeyPolicy} policy The new policy to set
@@ -147,7 +163,20 @@ export class Key {
147
163
  }
148
164
 
149
165
  /**
150
- * Append to existing key policy. This append is not atomic -- it uses {@link policy} to fetch the current policy and then {@link setPolicy} to set the policy -- and should not be used in across concurrent sessions.
166
+ * Set key metadata. The metadata must be at most 1024 characters
167
+ * and must match the following regex: ^[A-Za-z0-9_=+/ \-\.\,]{0,1024}$.
168
+ *
169
+ * @param {string} metadata The new metadata to set.
170
+ */
171
+ async setMetadata(metadata: string) {
172
+ await this.update({ metadata });
173
+ }
174
+
175
+ /**
176
+ * Append to existing key policy. This append is not atomic -- it uses {@link policy}
177
+ * to fetch the current policy and then {@link setPolicy} to set the policy -- and
178
+ * should not be used in across concurrent sessions.
179
+ *
151
180
  * @param {KeyPolicy} policy The policy to append to the existing one.
152
181
  */
153
182
  async appendPolicy(policy: KeyPolicy) {
package/src/mfa.ts CHANGED
@@ -4,6 +4,7 @@ import {
4
4
  ApiAddFidoChallenge,
5
5
  ApiMfaFidoChallenge,
6
6
  MfaRequestInfo,
7
+ MfaVote,
7
8
  PublicKeyCredential,
8
9
  TotpInfo,
9
10
  } from "./schema_types";
@@ -159,10 +160,12 @@ export class MfaFidoChallenge {
159
160
  /**
160
161
  * Answers this challenge by using the `CredentialsContainer` API to get a credential
161
162
  * based on the the public key credential request options from this challenge.
163
+ *
164
+ * @param {MfaVote} vote Approve or reject the MFA request. Defaults to "approve".
162
165
  */
163
- async createCredentialAndAnswer(): Promise<MfaRequestInfo> {
166
+ async createCredentialAndAnswer(vote?: MfaVote): Promise<MfaRequestInfo> {
164
167
  const cred = await navigator.credentials.get({ publicKey: this.options });
165
- return await this.answer(cred);
168
+ return await this.answer(cred, vote);
166
169
  }
167
170
 
168
171
  /**
@@ -175,8 +178,9 @@ export class MfaFidoChallenge {
175
178
  *
176
179
  * @param {any} cred Credential created by calling the `CredentialContainer`'s `get` method
177
180
  * based on the public key credential request options from this challenge.
181
+ * @param {MfaVote} vote Approve or reject. Defaults to "approve".
178
182
  */
179
- async answer(cred: any): Promise<MfaRequestInfo> {
183
+ async answer(cred: any, vote: MfaVote = "approve"): Promise<MfaRequestInfo> {
180
184
  const answer = <PublicKeyCredential>{
181
185
  id: cred.id,
182
186
  response: {
@@ -185,6 +189,6 @@ export class MfaFidoChallenge {
185
189
  signature: encodeToBase64Url(cred.response.signature),
186
190
  },
187
191
  };
188
- return await this.#api.mfaApproveFidoComplete(this.mfaId, this.challengeId, answer);
192
+ return await this.#api.mfaVoteFidoComplete(this.mfaId, vote, this.challengeId, answer);
189
193
  }
190
194
  }
package/src/response.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { CubeSignerClient, SignerSession } from ".";
1
+ import { CubeSignerClient, MfaVote, SignerSession } from ".";
2
2
  import { MfaReceipt } from "./mfa";
3
3
  import { AcceptedResponse, NewSessionResponse } from "./schema_types";
4
4
 
@@ -90,13 +90,39 @@ export class CubeSignerResponse<U> {
90
90
  * @return {CubeSignerResponse<U>} The result of signing with the approval
91
91
  */
92
92
  async approveTotp(session: SignerSession, code: string): Promise<CubeSignerResponse<U>> {
93
+ return await this.#mfaVoteTotp(session, code, "approve");
94
+ }
95
+
96
+ /**
97
+ * Reject the MFA request using a given session and a TOTP code.
98
+ *
99
+ * @param {SignerSession} session Signer session to use
100
+ * @param {string} code 6-digit TOTP code
101
+ */
102
+ async rejectTotp(session: SignerSession, code: string) {
103
+ await this.#mfaVoteTotp(session, code, "reject");
104
+ }
105
+
106
+ /**
107
+ * Approve or reject an MFA request using a given session and a TOTP code.
108
+ *
109
+ * @param {SignerSession} session Signer session to use
110
+ * @param {string} code 6-digit TOTP code
111
+ * @param {MfaVote} vote Approve or reject
112
+ * @return {CubeSignerResponse<U>} The result of signing with the approval
113
+ */
114
+ async #mfaVoteTotp(
115
+ session: SignerSession,
116
+ code: string,
117
+ vote: MfaVote,
118
+ ): Promise<CubeSignerResponse<U>> {
93
119
  if (!this.requiresMfa()) {
94
120
  return this;
95
121
  }
96
122
 
97
123
  const mfaId = this.mfaId();
98
124
  const mfaOrgId = this.#mfaRequired!.org_id;
99
- const mfaApproval = await session.mfaApproveTotp(mfaId, code);
125
+ const mfaApproval = await session.mfaVoteTotp(mfaId, code, vote);
100
126
  const mfaConf = mfaApproval.receipt?.confirmation;
101
127
 
102
128
  if (!mfaConf) {
@@ -107,12 +133,32 @@ export class CubeSignerResponse<U> {
107
133
  }
108
134
 
109
135
  /**
110
- * Approve the MFA request using a given `CubeSignerClient` instance (i.e., its session).
136
+ * Approve the MFA request using a given {@link CubeSignerClient} instance (i.e., its session).
111
137
  *
112
138
  * @param {CubeSignerClient} cs CubeSigner whose session to use
113
139
  * @return {CubeSignerResponse<U>} The result of signing with the approval
114
140
  */
115
141
  async approve(cs: CubeSignerClient): Promise<CubeSignerResponse<U>> {
142
+ return await this.#mfaVoteCs(cs, "approve");
143
+ }
144
+
145
+ /**
146
+ * Reject the MFA request using a given {@link CubeSignerClient} instance (i.e., its session).
147
+ *
148
+ * @param {CubeSignerClient} cs CubeSigner client whose session to use
149
+ */
150
+ async reject(cs: CubeSignerClient) {
151
+ await this.#mfaVoteCs(cs, "reject");
152
+ }
153
+
154
+ /**
155
+ * Approve or reject an MFA request using a given {@link CubeSignerClient} instance (i.e., its session).
156
+ *
157
+ * @param {CubeSignerClient} cs CubeSigner whose session to use
158
+ * @param {MfaVote} mfaVote Approve or reject
159
+ * @return {CubeSignerResponse<U>} The result of signing with the approval
160
+ */
161
+ async #mfaVoteCs(cs: CubeSignerClient, mfaVote: MfaVote): Promise<CubeSignerResponse<U>> {
116
162
  if (!this.requiresMfa()) {
117
163
  return this;
118
164
  }
@@ -120,7 +166,7 @@ export class CubeSignerResponse<U> {
120
166
  const mfaId = this.#mfaRequired!.id;
121
167
  const mfaOrgId = this.#mfaRequired!.org_id;
122
168
 
123
- const mfaApproval = await cs.mfaApprove(mfaId);
169
+ const mfaApproval = await cs.mfaVoteCs(mfaId, mfaVote);
124
170
  const mfaConf = mfaApproval.receipt?.confirmation;
125
171
 
126
172
  if (!mfaConf) {
package/src/role.ts CHANGED
@@ -278,6 +278,15 @@ export class Role {
278
278
  await this.#csc.roleUserAdd(this.id, userId);
279
279
  }
280
280
 
281
+ /**
282
+ * Remove an existing user from an existing role.
283
+ *
284
+ * @param {string} userId The user-id of the user to remove from the role.
285
+ */
286
+ async removeUser(userId: string) {
287
+ await this.#csc.roleUserRemove(this.id, userId);
288
+ }
289
+
281
290
  /**
282
291
  * The list of keys in the role.
283
292
  * @example [