@oneblink/apps-react 10.3.1 → 11.0.0-beta.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.
Files changed (61) hide show
  1. package/dist/apps/auth-service.d.ts +3 -2
  2. package/dist/apps/auth-service.js +2 -2
  3. package/dist/apps/auth-service.js.map +1 -1
  4. package/dist/apps/index.d.ts +10 -0
  5. package/dist/apps/index.js +10 -0
  6. package/dist/apps/index.js.map +1 -1
  7. package/dist/apps/mfa-service.d.ts +4 -0
  8. package/dist/apps/mfa-service.js +3 -0
  9. package/dist/apps/mfa-service.js.map +1 -0
  10. package/dist/apps/services/AWSCognitoClient.d.ts +39 -4
  11. package/dist/apps/services/AWSCognitoClient.js +238 -23
  12. package/dist/apps/services/AWSCognitoClient.js.map +1 -1
  13. package/dist/apps/services/cognito.d.ts +50 -41
  14. package/dist/apps/services/cognito.js +85 -48
  15. package/dist/apps/services/cognito.js.map +1 -1
  16. package/dist/components/mfa/MfaAuthenticatorAppDialog.d.ts +12 -0
  17. package/dist/components/mfa/MfaAuthenticatorAppDialog.js +64 -0
  18. package/dist/components/mfa/MfaAuthenticatorAppDialog.js.map +1 -0
  19. package/dist/components/mfa/MfaDisableDialog.d.ts +10 -0
  20. package/dist/components/mfa/MfaDisableDialog.js +31 -0
  21. package/dist/components/mfa/MfaDisableDialog.js.map +1 -0
  22. package/dist/components/mfa/MfaErrorSnackbar.d.ts +10 -0
  23. package/dist/components/mfa/MfaErrorSnackbar.js +17 -0
  24. package/dist/components/mfa/MfaErrorSnackbar.js.map +1 -0
  25. package/dist/components/mfa/MfaMethodRow.d.ts +20 -0
  26. package/dist/components/mfa/MfaMethodRow.js +10 -0
  27. package/dist/components/mfa/MfaMethodRow.js.map +1 -0
  28. package/dist/components/mfa/MfaPhoneNumberDialog.d.ts +11 -0
  29. package/dist/components/mfa/MfaPhoneNumberDialog.js +120 -0
  30. package/dist/components/mfa/MfaPhoneNumberDialog.js.map +1 -0
  31. package/dist/components/mfa/MfaRemovePhoneNumberDialog.d.ts +10 -0
  32. package/dist/components/mfa/MfaRemovePhoneNumberDialog.js +24 -0
  33. package/dist/components/mfa/MfaRemovePhoneNumberDialog.js.map +1 -0
  34. package/dist/components/mfa/MfaStatusChip.d.ts +10 -0
  35. package/dist/components/mfa/MfaStatusChip.js +29 -0
  36. package/dist/components/mfa/MfaStatusChip.js.map +1 -0
  37. package/dist/components/mfa/MfaSuccessSnackbar.d.ts +10 -0
  38. package/dist/components/mfa/MfaSuccessSnackbar.js +17 -0
  39. package/dist/components/mfa/MfaSuccessSnackbar.js.map +1 -0
  40. package/dist/components/mfa/MultiFactorAuthentication.d.ts +10 -10
  41. package/dist/components/mfa/MultiFactorAuthentication.js +46 -40
  42. package/dist/components/mfa/MultiFactorAuthentication.js.map +1 -1
  43. package/dist/hooks/useLogin.d.ts +14 -8
  44. package/dist/hooks/useLogin.js +16 -6
  45. package/dist/hooks/useLogin.js.map +1 -1
  46. package/dist/hooks/useMfa.d.ts +100 -31
  47. package/dist/hooks/useMfa.js +455 -68
  48. package/dist/hooks/useMfa.js.map +1 -1
  49. package/dist/index.d.ts +8 -0
  50. package/dist/index.js +8 -0
  51. package/dist/index.js.map +1 -1
  52. package/dist/utils/joinArray.d.ts +1 -0
  53. package/dist/utils/joinArray.js +7 -0
  54. package/dist/utils/joinArray.js.map +1 -0
  55. package/dist/utils/mfa-requirement.d.ts +12 -0
  56. package/dist/utils/mfa-requirement.js +96 -0
  57. package/dist/utils/mfa-requirement.js.map +1 -0
  58. package/package.json +2 -2
  59. package/dist/components/mfa/MfaDialog.d.ts +0 -9
  60. package/dist/components/mfa/MfaDialog.js +0 -47
  61. package/dist/components/mfa/MfaDialog.js.map +0 -1
@@ -1,21 +1,83 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import * as React from 'react';
3
- import { authService } from '../apps';
3
+ import { mfaService } from '../apps';
4
+ import useLoadDataEffect from './useLoadDataEffect';
4
5
  export const MfaContext = React.createContext({
5
6
  isExternalIdentityProviderUser: false,
6
7
  isLoading: true,
7
8
  isMfaEnabled: false,
9
+ mfaSettings: mfaService.DEFAULT_MFA_SETTINGS,
10
+ isSetupSuccessOpen: false,
8
11
  isSettingUpMfa: false,
9
- isDisablingMfa: false,
10
- beginMfaSetup: () => { },
11
- cancelMfaSetup: () => { },
12
- completeMfaSetup: () => { },
13
- beginDisablingMfa: () => { },
12
+ isSetupMethodDialogOpen: false,
13
+ isSettingPreferredMfaMethod: false,
14
+ isPhoneNumberDialogOpen: false,
15
+ isRemovePhoneNumberDialogOpen: false,
16
+ beginMfaSetup: async () => { },
17
+ openMfaSetupMethodDialog: () => { },
18
+ closeMfaSetupMethodDialog: () => { },
19
+ beginDisablingMfaMethod: () => { },
20
+ setPreferredMfaMethod: async () => { },
21
+ openPhoneNumberDialog: () => { },
22
+ closePhoneNumberDialog: () => { },
23
+ savePhoneNumber: async () => { },
24
+ verifyPhoneNumber: async () => { },
25
+ resendPhoneNumberVerificationCode: async () => { },
26
+ beginRemovingPhoneNumber: () => { },
27
+ cancelRemovingPhoneNumber: () => { },
28
+ completeRemovingPhoneNumber: async () => { },
29
+ hideSetupSuccess: () => { },
30
+ cancelMfaAuthenticatorAppSetup: () => { },
31
+ completeMfaAuthenticatorAppSetup: async () => { },
14
32
  cancelDisablingMfa: () => { },
15
- completeDisablingMfa: () => { },
33
+ completeDisablingMfa: async () => { },
16
34
  clearMfaSetupError: () => { },
17
35
  loadMfa: () => { },
18
36
  });
37
+ function getIsMfaEnabled(mfaSettings) {
38
+ return mfaSettings.authenticator.enabled || mfaSettings.sms.enabled;
39
+ }
40
+ function hasPreferredMfaMethod(mfaSettings) {
41
+ return ((mfaSettings.authenticator.enabled &&
42
+ mfaSettings.authenticator.preferred) ||
43
+ (mfaSettings.sms.enabled && mfaSettings.sms.preferred));
44
+ }
45
+ function enableSmsMfaInSettings(mfaSettings, smsPreferred) {
46
+ return {
47
+ ...mfaSettings,
48
+ sms: {
49
+ ...mfaSettings.sms,
50
+ enabled: true,
51
+ preferred: smsPreferred,
52
+ },
53
+ };
54
+ }
55
+ function enableAuthenticatorMfaInSettings(mfaSettings, authenticatorPreferred) {
56
+ return {
57
+ ...mfaSettings,
58
+ authenticator: {
59
+ enabled: true,
60
+ preferred: authenticatorPreferred,
61
+ },
62
+ sms: {
63
+ ...mfaSettings.sms,
64
+ preferred: authenticatorPreferred ? false : mfaSettings.sms.preferred,
65
+ },
66
+ };
67
+ }
68
+ function setPreferredMfaMethodInSettings(mfaSettings, mfaMethod) {
69
+ return {
70
+ ...mfaSettings,
71
+ authenticator: {
72
+ ...mfaSettings.authenticator,
73
+ preferred: mfaMethod === 'authenticator',
74
+ },
75
+ sms: {
76
+ ...mfaSettings.sms,
77
+ preferred: mfaMethod === 'sms',
78
+ },
79
+ };
80
+ }
19
81
  /**
20
82
  * React Component that provides the context for the
21
83
  * `useUserMeetsMfaRequirement()` hook and `<MultiFactorAuthentication />`
@@ -32,23 +94,36 @@ export const MfaContext = React.createContext({
32
94
  * useUserMeetsMfaRequirement,
33
95
  * } from '@oneblink/apps-react'
34
96
  *
35
- * function Component() {
36
- * const { isLoading, userMeetsMfaRequirement } =
37
- * useUserMeetsMfaRequirement(true)
38
- * // use MFA Requirement details here
97
+ * function Component({ teamMemberMfaRequirement }) {
98
+ * const { mfaSetupRequired, isLoading, loadingError, refreshMfa } =
99
+ * useUserMeetsMfaRequirement(teamMemberMfaRequirement)
100
+ *
101
+ * if (isLoading) {
102
+ * return <Loading />
103
+ * }
104
+ *
105
+ * if (loadingError) {
106
+ * return <Error onRetry={refreshMfa} />
107
+ * }
108
+ *
109
+ * if (mfaSetupRequired) {
110
+ * return <ConfigureMfa />
111
+ * }
112
+ *
113
+ * return <Application />
39
114
  * }
40
115
  *
41
- * function App() {
116
+ * function App({ teamMemberMfaRequirement }) {
42
117
  * return (
43
118
  * <MfaProvider isExternalIdentityProviderUser={false}>
44
- * <Component />
119
+ * <Component teamMemberMfaRequirement={teamMemberMfaRequirement} />
45
120
  * </MfaProvider>
46
121
  * )
47
122
  * }
48
123
  *
49
124
  * const root = document.getElementById('root')
50
125
  * if (root) {
51
- * ReactDOM.render(<App />, root)
126
+ * ReactDOM.render(<App teamMemberMfaRequirement={mfaRequirement} />, root)
52
127
  * }
53
128
  * ```
54
129
  *
@@ -61,112 +136,383 @@ export function MfaProvider({ children, isExternalIdentityProviderUser, }) {
61
136
  isExternalIdentityProviderUser,
62
137
  isLoading: !isExternalIdentityProviderUser,
63
138
  isMfaEnabled: false,
139
+ mfaSettings: mfaService.DEFAULT_MFA_SETTINGS,
140
+ isSetupSuccessOpen: false,
64
141
  isSettingUpMfa: false,
65
- isDisablingMfa: false,
142
+ isSetupMethodDialogOpen: false,
143
+ isSettingPreferredMfaMethod: false,
144
+ isPhoneNumberDialogOpen: false,
145
+ isRemovePhoneNumberDialogOpen: false,
66
146
  });
67
- const loadMfa = React.useCallback(async (abortSignal) => {
147
+ const handleLoadMfa = React.useCallback(async (abortSignal) => {
148
+ if (isExternalIdentityProviderUser) {
149
+ return;
150
+ }
68
151
  setState((currentState) => ({
69
152
  ...currentState,
70
153
  isLoading: true,
71
154
  isMfaEnabled: false,
155
+ mfaSettings: mfaService.DEFAULT_MFA_SETTINGS,
72
156
  loadingError: undefined,
73
157
  }));
74
158
  try {
75
- const newIsMfaEnabled = await authService.checkIsMfaEnabled();
76
- if (!(abortSignal === null || abortSignal === void 0 ? void 0 : abortSignal.aborted)) {
159
+ const mfaSettings = await mfaService.getMfaSettings(abortSignal);
160
+ if (!abortSignal.aborted) {
77
161
  setState((currentState) => ({
78
162
  ...currentState,
79
163
  isLoading: false,
80
- isMfaEnabled: newIsMfaEnabled,
164
+ mfaSettings,
165
+ isMfaEnabled: getIsMfaEnabled(mfaSettings),
81
166
  }));
82
167
  }
83
168
  }
84
169
  catch (error) {
170
+ if (abortSignal.aborted) {
171
+ return;
172
+ }
85
173
  setState((currentState) => ({
86
174
  ...currentState,
87
175
  isLoading: false,
88
176
  loadingError: error,
89
177
  }));
90
178
  }
91
- }, []);
179
+ }, [isExternalIdentityProviderUser]);
180
+ const loadMfa = useLoadDataEffect(handleLoadMfa);
92
181
  const clearMfaSetupError = React.useCallback(() => {
93
182
  setState((currentState) => ({
94
183
  ...currentState,
95
184
  setupError: undefined,
96
185
  }));
97
186
  }, []);
98
- const cancelMfaSetup = React.useCallback(() => {
187
+ const openMfaSetupMethodDialog = React.useCallback(() => {
99
188
  setState((currentState) => ({
100
189
  ...currentState,
101
- mfaSetup: undefined,
190
+ isSetupMethodDialogOpen: true,
102
191
  }));
103
192
  }, []);
104
- const completeMfaSetup = React.useCallback(() => {
193
+ const closeMfaSetupMethodDialog = React.useCallback(() => {
105
194
  setState((currentState) => ({
106
195
  ...currentState,
107
- isMfaEnabled: true,
108
- mfaSetup: undefined,
196
+ isSetupMethodDialogOpen: false,
109
197
  }));
110
198
  }, []);
111
- const beginMfaSetup = React.useCallback(async () => {
199
+ const hideSetupSuccess = React.useCallback(() => {
200
+ setState((currentState) => ({
201
+ ...currentState,
202
+ isSetupSuccessOpen: false,
203
+ }));
204
+ }, []);
205
+ const cancelMfaAuthenticatorAppSetup = React.useCallback(() => {
206
+ setState((currentState) => ({
207
+ ...currentState,
208
+ mfaAuthenticatorAppSetup: undefined,
209
+ settingUpMfaMethod: undefined,
210
+ }));
211
+ }, []);
212
+ const completeMfaAuthenticatorAppSetup = React.useCallback(async () => {
213
+ setState((currentState) => {
214
+ const authenticatorPreferred = !hasPreferredMfaMethod(currentState.mfaSettings);
215
+ const mfaSettings = enableAuthenticatorMfaInSettings(currentState.mfaSettings, authenticatorPreferred);
216
+ return {
217
+ ...currentState,
218
+ isSetupSuccessOpen: true,
219
+ isMfaEnabled: getIsMfaEnabled(mfaSettings),
220
+ mfaSettings,
221
+ mfaAuthenticatorAppSetup: undefined,
222
+ settingUpMfaMethod: undefined,
223
+ };
224
+ });
225
+ }, []);
226
+ const openPhoneNumberDialog = React.useCallback(() => {
227
+ setState((currentState) => ({
228
+ ...currentState,
229
+ isPhoneNumberDialogOpen: true,
230
+ phoneVerificationCodeSentAt: undefined,
231
+ setupError: undefined,
232
+ }));
233
+ }, []);
234
+ const closePhoneNumberDialog = React.useCallback(() => {
235
+ setState((currentState) => ({
236
+ ...currentState,
237
+ isPhoneNumberDialogOpen: false,
238
+ phoneVerificationCodeSentAt: undefined,
239
+ }));
240
+ }, []);
241
+ const setupSmsMfaMethod = React.useCallback(async () => {
242
+ let smsPreferred = false;
243
+ setState((currentState) => {
244
+ smsPreferred = !hasPreferredMfaMethod(currentState.mfaSettings);
245
+ return currentState;
246
+ });
247
+ await mfaService.setupSmsMfa({
248
+ preferred: smsPreferred,
249
+ });
250
+ setState((currentState) => {
251
+ const mfaSettings = enableSmsMfaInSettings(currentState.mfaSettings, smsPreferred);
252
+ return {
253
+ ...currentState,
254
+ isSetupSuccessOpen: true,
255
+ isSettingUpMfa: false,
256
+ settingUpMfaMethod: undefined,
257
+ isMfaEnabled: getIsMfaEnabled(mfaSettings),
258
+ mfaSettings,
259
+ };
260
+ });
261
+ }, []);
262
+ const savePhoneNumber = React.useCallback(async (phoneNumber) => {
263
+ setState((currentState) => ({
264
+ ...currentState,
265
+ setupError: undefined,
266
+ }));
267
+ try {
268
+ const { isPhoneNumberVerified } = await mfaService.updateUserPhoneNumber(phoneNumber);
269
+ if (!isPhoneNumberVerified) {
270
+ setState((currentState) => ({
271
+ ...currentState,
272
+ phoneVerificationCodeSentAt: Date.now(),
273
+ mfaSettings: {
274
+ ...currentState.mfaSettings,
275
+ sms: {
276
+ ...currentState.mfaSettings.sms,
277
+ phoneNumber,
278
+ isPhoneNumberVerified: false,
279
+ },
280
+ },
281
+ }));
282
+ return;
283
+ }
284
+ setState((currentState) => ({
285
+ ...currentState,
286
+ mfaSettings: {
287
+ ...currentState.mfaSettings,
288
+ sms: {
289
+ ...currentState.mfaSettings.sms,
290
+ phoneNumber,
291
+ isPhoneNumberVerified,
292
+ },
293
+ },
294
+ isPhoneNumberDialogOpen: false,
295
+ phoneVerificationCodeSentAt: undefined,
296
+ }));
297
+ await setupSmsMfaMethod();
298
+ }
299
+ catch (error) {
300
+ setState((currentState) => ({
301
+ ...currentState,
302
+ setupError: error,
303
+ }));
304
+ }
305
+ }, [setupSmsMfaMethod]);
306
+ const verifyPhoneNumber = React.useCallback(async (code) => {
307
+ setState((currentState) => ({
308
+ ...currentState,
309
+ setupError: undefined,
310
+ }));
311
+ try {
312
+ await mfaService.verifyUserPhoneNumber(code);
313
+ setState((currentState) => ({
314
+ ...currentState,
315
+ mfaSettings: {
316
+ ...currentState.mfaSettings,
317
+ sms: {
318
+ ...currentState.mfaSettings.sms,
319
+ isPhoneNumberVerified: true,
320
+ },
321
+ },
322
+ isPhoneNumberDialogOpen: false,
323
+ phoneVerificationCodeSentAt: undefined,
324
+ }));
325
+ await setupSmsMfaMethod();
326
+ }
327
+ catch (error) {
328
+ setState((currentState) => ({
329
+ ...currentState,
330
+ setupError: error,
331
+ }));
332
+ }
333
+ }, [setupSmsMfaMethod]);
334
+ const resendPhoneNumberVerificationCode = React.useCallback(async () => {
335
+ setState((currentState) => ({
336
+ ...currentState,
337
+ setupError: undefined,
338
+ phoneVerificationCodeSentAt: Date.now(),
339
+ }));
340
+ try {
341
+ await mfaService.sendPhoneNumberVerificationCode();
342
+ }
343
+ catch (error) {
344
+ setState((currentState) => ({
345
+ ...currentState,
346
+ setupError: error,
347
+ }));
348
+ }
349
+ }, []);
350
+ const beginRemovingPhoneNumber = React.useCallback(() => {
351
+ setState((currentState) => ({
352
+ ...currentState,
353
+ isRemovePhoneNumberDialogOpen: true,
354
+ setupError: undefined,
355
+ }));
356
+ }, []);
357
+ const cancelRemovingPhoneNumber = React.useCallback(() => {
358
+ setState((currentState) => ({
359
+ ...currentState,
360
+ isRemovePhoneNumberDialogOpen: false,
361
+ }));
362
+ }, []);
363
+ const completeRemovingPhoneNumber = React.useCallback(async () => {
364
+ setState((currentState) => ({
365
+ ...currentState,
366
+ setupError: undefined,
367
+ }));
368
+ try {
369
+ await mfaService.removeUserPhoneNumber();
370
+ setState((currentState) => ({
371
+ ...currentState,
372
+ isRemovePhoneNumberDialogOpen: false,
373
+ mfaSettings: {
374
+ ...currentState.mfaSettings,
375
+ sms: {
376
+ ...currentState.mfaSettings.sms,
377
+ phoneNumber: undefined,
378
+ isPhoneNumberVerified: false,
379
+ },
380
+ },
381
+ }));
382
+ }
383
+ catch (error) {
384
+ setState((currentState) => ({
385
+ ...currentState,
386
+ setupError: error,
387
+ }));
388
+ }
389
+ }, []);
390
+ const beginMfaSetup = React.useCallback(async (mfaMethod) => {
112
391
  setState((currentState) => ({
113
392
  ...currentState,
114
393
  isSettingUpMfa: true,
115
- mfaSetup: undefined,
394
+ settingUpMfaMethod: mfaMethod,
395
+ mfaAuthenticatorAppSetup: undefined,
116
396
  setupError: undefined,
117
397
  }));
118
398
  try {
119
- const newMfaSetup = await authService.setupMfa();
399
+ let mfaSettings = mfaService.DEFAULT_MFA_SETTINGS;
400
+ setState((currentState) => {
401
+ mfaSettings = currentState.mfaSettings;
402
+ return currentState;
403
+ });
404
+ if (mfaMethod === 'sms') {
405
+ if (!mfaSettings.sms.phoneNumber ||
406
+ !mfaSettings.sms.isPhoneNumberVerified) {
407
+ setState((currentState) => ({
408
+ ...currentState,
409
+ isSettingUpMfa: false,
410
+ settingUpMfaMethod: undefined,
411
+ isSetupMethodDialogOpen: false,
412
+ isPhoneNumberDialogOpen: true,
413
+ }));
414
+ return;
415
+ }
416
+ await setupSmsMfaMethod();
417
+ setState((currentState) => ({
418
+ ...currentState,
419
+ isSetupMethodDialogOpen: false,
420
+ }));
421
+ return;
422
+ }
423
+ const hasPreferredMethod = hasPreferredMfaMethod(mfaSettings);
424
+ const newMfaAuthenticatorAppSetup = await mfaService.setupMfaAuthenticatorApp({
425
+ preferred: !hasPreferredMethod,
426
+ });
120
427
  setState((currentState) => ({
121
428
  ...currentState,
122
429
  isSettingUpMfa: false,
123
- mfaSetup: newMfaSetup,
430
+ isSetupMethodDialogOpen: false,
431
+ mfaAuthenticatorAppSetup: newMfaAuthenticatorAppSetup,
124
432
  }));
125
433
  }
126
434
  catch (error) {
127
435
  setState((currentState) => ({
128
436
  ...currentState,
129
437
  isSettingUpMfa: false,
438
+ settingUpMfaMethod: undefined,
130
439
  setupError: error,
131
440
  }));
132
441
  }
133
- }, []);
134
- const beginDisablingMfa = React.useCallback(() => {
442
+ }, [setupSmsMfaMethod]);
443
+ const disablingMfaMethodRef = React.useRef(undefined);
444
+ const beginDisablingMfaMethod = React.useCallback((mfaMethod) => {
445
+ disablingMfaMethodRef.current = mfaMethod;
135
446
  setState((currentState) => ({
136
447
  ...currentState,
137
- isDisablingMfa: true,
448
+ disablingMfaMethod: mfaMethod,
138
449
  }));
139
450
  }, []);
140
451
  const cancelDisablingMfa = React.useCallback(() => {
452
+ disablingMfaMethodRef.current = undefined;
141
453
  setState((currentState) => ({
142
454
  ...currentState,
143
- isDisablingMfa: false,
455
+ disablingMfaMethod: undefined,
144
456
  }));
145
457
  }, []);
146
458
  const completeDisablingMfa = React.useCallback(async () => {
147
- await authService.disableMfa();
459
+ const disablingMfaMethod = disablingMfaMethodRef.current;
460
+ if (!disablingMfaMethod) {
461
+ return;
462
+ }
463
+ await mfaService.disableMfaMethod(disablingMfaMethod);
464
+ const mfaSettings = await mfaService.getMfaSettings();
465
+ disablingMfaMethodRef.current = undefined;
148
466
  setState((currentState) => ({
149
467
  ...currentState,
150
- isDisablingMfa: false,
151
- isMfaEnabled: false,
468
+ disablingMfaMethod: undefined,
469
+ mfaSettings,
470
+ isMfaEnabled: getIsMfaEnabled(mfaSettings),
152
471
  }));
153
472
  }, []);
154
- React.useEffect(() => {
155
- if (isExternalIdentityProviderUser) {
156
- return;
473
+ const setPreferredMfaMethod = React.useCallback(async (mfaMethod) => {
474
+ setState((currentState) => ({
475
+ ...currentState,
476
+ isSettingPreferredMfaMethod: true,
477
+ setupError: undefined,
478
+ }));
479
+ try {
480
+ await mfaService.setPreferredMfaMethod(mfaMethod);
481
+ setState((currentState) => ({
482
+ ...currentState,
483
+ isSettingPreferredMfaMethod: false,
484
+ mfaSettings: setPreferredMfaMethodInSettings(currentState.mfaSettings, mfaMethod),
485
+ }));
486
+ }
487
+ catch (error) {
488
+ setState((currentState) => ({
489
+ ...currentState,
490
+ isSettingPreferredMfaMethod: false,
491
+ setupError: error,
492
+ }));
157
493
  }
158
- loadMfa();
159
- return () => { };
160
- }, [isExternalIdentityProviderUser, loadMfa]);
494
+ }, []);
161
495
  const value = React.useMemo(() => {
162
496
  return {
163
497
  ...state,
164
498
  clearMfaSetupError,
165
499
  loadMfa,
166
500
  beginMfaSetup,
167
- cancelMfaSetup,
168
- completeMfaSetup,
169
- beginDisablingMfa,
501
+ openMfaSetupMethodDialog,
502
+ closeMfaSetupMethodDialog,
503
+ beginDisablingMfaMethod,
504
+ setPreferredMfaMethod,
505
+ openPhoneNumberDialog,
506
+ closePhoneNumberDialog,
507
+ savePhoneNumber,
508
+ verifyPhoneNumber,
509
+ resendPhoneNumberVerificationCode,
510
+ beginRemovingPhoneNumber,
511
+ cancelRemovingPhoneNumber,
512
+ completeRemovingPhoneNumber,
513
+ hideSetupSuccess,
514
+ cancelMfaAuthenticatorAppSetup,
515
+ completeMfaAuthenticatorAppSetup,
170
516
  cancelDisablingMfa,
171
517
  completeDisablingMfa,
172
518
  };
@@ -175,9 +521,21 @@ export function MfaProvider({ children, isExternalIdentityProviderUser, }) {
175
521
  clearMfaSetupError,
176
522
  loadMfa,
177
523
  beginMfaSetup,
178
- cancelMfaSetup,
179
- completeMfaSetup,
180
- beginDisablingMfa,
524
+ openMfaSetupMethodDialog,
525
+ closeMfaSetupMethodDialog,
526
+ beginDisablingMfaMethod,
527
+ setPreferredMfaMethod,
528
+ openPhoneNumberDialog,
529
+ closePhoneNumberDialog,
530
+ savePhoneNumber,
531
+ verifyPhoneNumber,
532
+ resendPhoneNumberVerificationCode,
533
+ beginRemovingPhoneNumber,
534
+ cancelRemovingPhoneNumber,
535
+ completeRemovingPhoneNumber,
536
+ hideSetupSuccess,
537
+ cancelMfaAuthenticatorAppSetup,
538
+ completeMfaAuthenticatorAppSetup,
181
539
  cancelDisablingMfa,
182
540
  completeDisablingMfa,
183
541
  ]);
@@ -187,35 +545,64 @@ export default function useMfa() {
187
545
  return React.useContext(MfaContext);
188
546
  }
189
547
  /**
190
- * React hook to check if the logged in user meets the MFA requirement of your
191
- * application. Will throw an Error if used outside of the `<MfaProvider />`
192
- * component.
548
+ * React hook to determine whether the logged in user must set up MFA before
549
+ * accessing your application. Reads MFA settings from the `<MfaProvider />`
550
+ * context. Will throw an Error if used outside of `<MfaProvider />`.
551
+ *
552
+ * Users signed in via an external identity provider are not required to set up
553
+ * MFA.
193
554
  *
194
- * Example
555
+ * #### Example
195
556
  *
196
557
  * ```js
197
- * import { useUserMeetsMfaRequirement } from '@oneblink/apps-react'
558
+ * import { MfaProvider, useUserMeetsMfaRequirement } from '@oneblink/apps-react'
559
+ *
560
+ * function Component({ teamMemberMfaRequirement }) {
561
+ * const { mfaSetupRequired, isLoading, loadingError, refreshMfa } =
562
+ * useUserMeetsMfaRequirement(teamMemberMfaRequirement)
198
563
  *
199
- * const isMfaRequired = true
564
+ * if (isLoading) {
565
+ * return <Loading />
566
+ * }
200
567
  *
201
- * function Component() {
202
- * const userMeetsMfaRequirement =
203
- * useUserMeetsMfaRequirement(isMfaRequired)
568
+ * if (loadingError) {
569
+ * return <Error onRetry={refreshMfa} />
570
+ * }
571
+ *
572
+ * if (mfaSetupRequired) {
573
+ * return <ConfigureMfa />
574
+ * }
575
+ *
576
+ * return <Application />
204
577
  * }
205
578
  * ```
206
579
  *
207
- * @returns
580
+ * @param mfaRequirement - The MFA requirement to enforce, e.g. from your
581
+ * organisation or app settings.
582
+ * @returns Whether MFA setup is required, along with loading state from the MFA
583
+ * provider.
208
584
  * @group Hooks
209
585
  */
210
- export function useUserMeetsMfaRequirement(isMfaRequired) {
211
- const context = React.useContext(MfaContext);
212
- if (!context) {
213
- throw new Error(`"useUserMeetsMfaRequirement" hook was used outside of the "<MfaProvider />" component's children.`);
214
- }
215
- const { isMfaEnabled, isExternalIdentityProviderUser } = context;
216
- if (!isMfaRequired || isExternalIdentityProviderUser) {
217
- return true;
218
- }
219
- return isMfaEnabled;
586
+ export function useUserMeetsMfaRequirement(mfaRequirement) {
587
+ const { isLoading, loadingError, mfaSettings, loadMfa, isExternalIdentityProviderUser, } = useMfa();
588
+ return React.useMemo(() => {
589
+ const mfaRequired = mfaService.isMfaRequired(mfaRequirement);
590
+ const shouldCheckMfa = mfaRequired && !isExternalIdentityProviderUser;
591
+ const meetsRequirement = mfaService.userMeetsMfaRequirement(mfaRequirement, mfaSettings);
592
+ const mfaSetupRequired = shouldCheckMfa && !isLoading && !loadingError && !meetsRequirement;
593
+ return {
594
+ mfaSetupRequired,
595
+ isLoading: shouldCheckMfa && isLoading,
596
+ loadingError: shouldCheckMfa ? loadingError : undefined,
597
+ refreshMfa: loadMfa,
598
+ };
599
+ }, [
600
+ mfaRequirement,
601
+ isExternalIdentityProviderUser,
602
+ mfaSettings,
603
+ isLoading,
604
+ loadingError,
605
+ loadMfa,
606
+ ]);
220
607
  }
221
608
  //# sourceMappingURL=useMfa.js.map