@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.
- package/dist/apps/auth-service.d.ts +3 -2
- package/dist/apps/auth-service.js +2 -2
- package/dist/apps/auth-service.js.map +1 -1
- package/dist/apps/index.d.ts +10 -0
- package/dist/apps/index.js +10 -0
- package/dist/apps/index.js.map +1 -1
- package/dist/apps/mfa-service.d.ts +4 -0
- package/dist/apps/mfa-service.js +3 -0
- package/dist/apps/mfa-service.js.map +1 -0
- package/dist/apps/services/AWSCognitoClient.d.ts +39 -4
- package/dist/apps/services/AWSCognitoClient.js +238 -23
- package/dist/apps/services/AWSCognitoClient.js.map +1 -1
- package/dist/apps/services/cognito.d.ts +50 -41
- package/dist/apps/services/cognito.js +85 -48
- package/dist/apps/services/cognito.js.map +1 -1
- package/dist/components/mfa/MfaAuthenticatorAppDialog.d.ts +12 -0
- package/dist/components/mfa/MfaAuthenticatorAppDialog.js +64 -0
- package/dist/components/mfa/MfaAuthenticatorAppDialog.js.map +1 -0
- package/dist/components/mfa/MfaDisableDialog.d.ts +10 -0
- package/dist/components/mfa/MfaDisableDialog.js +31 -0
- package/dist/components/mfa/MfaDisableDialog.js.map +1 -0
- package/dist/components/mfa/MfaErrorSnackbar.d.ts +10 -0
- package/dist/components/mfa/MfaErrorSnackbar.js +17 -0
- package/dist/components/mfa/MfaErrorSnackbar.js.map +1 -0
- package/dist/components/mfa/MfaMethodRow.d.ts +20 -0
- package/dist/components/mfa/MfaMethodRow.js +10 -0
- package/dist/components/mfa/MfaMethodRow.js.map +1 -0
- package/dist/components/mfa/MfaPhoneNumberDialog.d.ts +11 -0
- package/dist/components/mfa/MfaPhoneNumberDialog.js +120 -0
- package/dist/components/mfa/MfaPhoneNumberDialog.js.map +1 -0
- package/dist/components/mfa/MfaRemovePhoneNumberDialog.d.ts +10 -0
- package/dist/components/mfa/MfaRemovePhoneNumberDialog.js +24 -0
- package/dist/components/mfa/MfaRemovePhoneNumberDialog.js.map +1 -0
- package/dist/components/mfa/MfaStatusChip.d.ts +10 -0
- package/dist/components/mfa/MfaStatusChip.js +29 -0
- package/dist/components/mfa/MfaStatusChip.js.map +1 -0
- package/dist/components/mfa/MfaSuccessSnackbar.d.ts +10 -0
- package/dist/components/mfa/MfaSuccessSnackbar.js +17 -0
- package/dist/components/mfa/MfaSuccessSnackbar.js.map +1 -0
- package/dist/components/mfa/MultiFactorAuthentication.d.ts +10 -10
- package/dist/components/mfa/MultiFactorAuthentication.js +46 -40
- package/dist/components/mfa/MultiFactorAuthentication.js.map +1 -1
- package/dist/hooks/useLogin.d.ts +14 -8
- package/dist/hooks/useLogin.js +16 -6
- package/dist/hooks/useLogin.js.map +1 -1
- package/dist/hooks/useMfa.d.ts +100 -31
- package/dist/hooks/useMfa.js +455 -68
- package/dist/hooks/useMfa.js.map +1 -1
- package/dist/index.d.ts +8 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/utils/joinArray.d.ts +1 -0
- package/dist/utils/joinArray.js +7 -0
- package/dist/utils/joinArray.js.map +1 -0
- package/dist/utils/mfa-requirement.d.ts +12 -0
- package/dist/utils/mfa-requirement.js +96 -0
- package/dist/utils/mfa-requirement.js.map +1 -0
- package/package.json +2 -2
- package/dist/components/mfa/MfaDialog.d.ts +0 -9
- package/dist/components/mfa/MfaDialog.js +0 -47
- package/dist/components/mfa/MfaDialog.js.map +0 -1
package/dist/hooks/useMfa.js
CHANGED
|
@@ -1,21 +1,83 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import * as React from 'react';
|
|
3
|
-
import {
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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,
|
|
37
|
-
* useUserMeetsMfaRequirement(
|
|
38
|
-
*
|
|
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
|
-
|
|
142
|
+
isSetupMethodDialogOpen: false,
|
|
143
|
+
isSettingPreferredMfaMethod: false,
|
|
144
|
+
isPhoneNumberDialogOpen: false,
|
|
145
|
+
isRemovePhoneNumberDialogOpen: false,
|
|
66
146
|
});
|
|
67
|
-
const
|
|
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
|
|
76
|
-
if (!
|
|
159
|
+
const mfaSettings = await mfaService.getMfaSettings(abortSignal);
|
|
160
|
+
if (!abortSignal.aborted) {
|
|
77
161
|
setState((currentState) => ({
|
|
78
162
|
...currentState,
|
|
79
163
|
isLoading: false,
|
|
80
|
-
|
|
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
|
|
187
|
+
const openMfaSetupMethodDialog = React.useCallback(() => {
|
|
99
188
|
setState((currentState) => ({
|
|
100
189
|
...currentState,
|
|
101
|
-
|
|
190
|
+
isSetupMethodDialogOpen: true,
|
|
102
191
|
}));
|
|
103
192
|
}, []);
|
|
104
|
-
const
|
|
193
|
+
const closeMfaSetupMethodDialog = React.useCallback(() => {
|
|
105
194
|
setState((currentState) => ({
|
|
106
195
|
...currentState,
|
|
107
|
-
|
|
108
|
-
mfaSetup: undefined,
|
|
196
|
+
isSetupMethodDialogOpen: false,
|
|
109
197
|
}));
|
|
110
198
|
}, []);
|
|
111
|
-
const
|
|
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
|
-
|
|
394
|
+
settingUpMfaMethod: mfaMethod,
|
|
395
|
+
mfaAuthenticatorAppSetup: undefined,
|
|
116
396
|
setupError: undefined,
|
|
117
397
|
}));
|
|
118
398
|
try {
|
|
119
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
455
|
+
disablingMfaMethod: undefined,
|
|
144
456
|
}));
|
|
145
457
|
}, []);
|
|
146
458
|
const completeDisablingMfa = React.useCallback(async () => {
|
|
147
|
-
|
|
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
|
-
|
|
151
|
-
|
|
468
|
+
disablingMfaMethod: undefined,
|
|
469
|
+
mfaSettings,
|
|
470
|
+
isMfaEnabled: getIsMfaEnabled(mfaSettings),
|
|
152
471
|
}));
|
|
153
472
|
}, []);
|
|
154
|
-
React.
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
|
|
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
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
-
|
|
179
|
-
|
|
180
|
-
|
|
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
|
|
191
|
-
* application.
|
|
192
|
-
*
|
|
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
|
-
*
|
|
564
|
+
* if (isLoading) {
|
|
565
|
+
* return <Loading />
|
|
566
|
+
* }
|
|
200
567
|
*
|
|
201
|
-
*
|
|
202
|
-
*
|
|
203
|
-
*
|
|
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
|
-
* @
|
|
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(
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
return
|
|
218
|
-
|
|
219
|
-
|
|
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
|