@frontegg/redux-store 7.121.0 → 7.123.0-alpha.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/auth/LoginState/actions/mfaRequiredState.actions.js +1 -0
- package/auth/LoginState/actions/mfaWithAuthenticator.actions.js +2 -0
- package/auth/LoginState/actions/mfaWithEmailCode.actions.js +2 -0
- package/auth/LoginState/actions/mfaWithSMS.actions.js +2 -0
- package/auth/LoginState/actions/mfaWithWebAuthn.actions.js +7 -0
- package/auth/LoginState/helpers/persistPreferredMfaMethod.d.ts +12 -0
- package/auth/LoginState/helpers/persistPreferredMfaMethod.js +20 -0
- package/auth/LoginState/helpers.js +3 -2
- package/auth/LoginState/preferredMfaMethod.d.ts +53 -0
- package/auth/LoginState/preferredMfaMethod.js +151 -0
- package/auth/MfaState/actions.js +4 -2
- package/auth/MfaState/interfaces.d.ts +2 -0
- package/auth/TeamState/actions/index.d.ts +1 -0
- package/auth/TeamState/actions/users.actions.d.ts +2 -1
- package/auth/TeamState/actions/users.actions.js +23 -0
- package/auth/TeamState/interfaces.d.ts +3 -0
- package/auth/index.d.ts +1 -0
- package/auth/index.js +1 -0
- package/index.js +1 -1
- package/mocks/auth-mocks/teamActions.mocks.d.ts +1 -0
- package/node/auth/LoginState/actions/mfaRequiredState.actions.js +1 -0
- package/node/auth/LoginState/actions/mfaWithAuthenticator.actions.js +2 -0
- package/node/auth/LoginState/actions/mfaWithEmailCode.actions.js +2 -0
- package/node/auth/LoginState/actions/mfaWithSMS.actions.js +2 -0
- package/node/auth/LoginState/actions/mfaWithWebAuthn.actions.js +7 -0
- package/node/auth/LoginState/helpers/persistPreferredMfaMethod.js +27 -0
- package/node/auth/LoginState/helpers.js +2 -1
- package/node/auth/LoginState/preferredMfaMethod.js +166 -0
- package/node/auth/MfaState/actions.js +4 -2
- package/node/auth/TeamState/actions/users.actions.js +23 -0
- package/node/auth/index.js +12 -0
- package/node/index.js +1 -1
- package/package.json +2 -2
|
@@ -35,6 +35,7 @@ export default ((store, api, _sharedActions) => {
|
|
|
35
35
|
Object.assign(mfaState, {
|
|
36
36
|
step: getMfaStepForNotEnrolledUsers(user.mfaStrategies),
|
|
37
37
|
qrCode: user.qrCode,
|
|
38
|
+
secret: user.secret,
|
|
38
39
|
recoveryCode: user.recoveryCode,
|
|
39
40
|
loading: false,
|
|
40
41
|
mfaToken: user.mfaToken,
|
|
@@ -4,6 +4,7 @@ const _excluded = ["callback"];
|
|
|
4
4
|
import { LoginFlow, LoginStep } from '../interfaces';
|
|
5
5
|
import { errorHandler, GTMEventAction, reportGTMEvent } from '../../../helpers';
|
|
6
6
|
import { isResetPasswordRequired, shouldShowPasswordRotationPromptFunc } from '../../helpers';
|
|
7
|
+
import { persistPreferredMfaMethod } from '../helpers/persistPreferredMfaMethod';
|
|
7
8
|
export default ((store, api, sharedActions) => {
|
|
8
9
|
const actions = sharedActions;
|
|
9
10
|
|
|
@@ -113,6 +114,7 @@ export default ((store, api, sharedActions) => {
|
|
|
113
114
|
tenants = [],
|
|
114
115
|
activeTenant
|
|
115
116
|
} = await api.auth.loginWithMfaV2(loginWithMfaPayload);
|
|
117
|
+
persistPreferredMfaMethod(store, 'authenticatorApp');
|
|
116
118
|
const isAuthenticated = !!user.accessToken;
|
|
117
119
|
const additionalUpdate = await __buildPostAuthStateUpdate(tenants, isAuthenticated, isStepUp);
|
|
118
120
|
await actions.afterAuthenticationStateUpdate({
|
|
@@ -3,6 +3,7 @@ const _excluded = ["callback"],
|
|
|
3
3
|
_excluded2 = ["callback"];
|
|
4
4
|
import { MFAStep } from '../../MfaState/interfaces';
|
|
5
5
|
import { errorHandler } from '../../../helpers';
|
|
6
|
+
import { persistPreferredMfaMethod } from '../helpers/persistPreferredMfaMethod';
|
|
6
7
|
export default ((store, api, sharedActions) => {
|
|
7
8
|
const actions = sharedActions;
|
|
8
9
|
/**
|
|
@@ -59,6 +60,7 @@ export default ((store, api, sharedActions) => {
|
|
|
59
60
|
});
|
|
60
61
|
try {
|
|
61
62
|
const data = await api.auth.verifyMFAEmailCodeV2(payload);
|
|
63
|
+
persistPreferredMfaMethod(store, 'email');
|
|
62
64
|
await actions.handleVerifyMFAResponse(data);
|
|
63
65
|
setLoadingAction({
|
|
64
66
|
loading: false,
|
|
@@ -3,6 +3,7 @@ const _excluded = ["callback", "deviceId"],
|
|
|
3
3
|
_excluded2 = ["callback", "deviceId"];
|
|
4
4
|
import { MFAStep } from '../../MfaState/interfaces';
|
|
5
5
|
import { errorHandler } from '../../../helpers';
|
|
6
|
+
import { persistPreferredMfaMethod } from '../helpers/persistPreferredMfaMethod';
|
|
6
7
|
export default ((store, api, sharedActions) => {
|
|
7
8
|
const actions = sharedActions;
|
|
8
9
|
|
|
@@ -64,6 +65,7 @@ export default ((store, api, sharedActions) => {
|
|
|
64
65
|
});
|
|
65
66
|
try {
|
|
66
67
|
const data = await api.auth.verifyMFASMSForLoginV2(deviceId, payload);
|
|
68
|
+
persistPreferredMfaMethod(store, 'sms');
|
|
67
69
|
await actions.handleVerifyMFAResponse(data, isStepUp);
|
|
68
70
|
setLoadingAction({
|
|
69
71
|
loading: false,
|
|
@@ -3,6 +3,8 @@ import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWith
|
|
|
3
3
|
const _excluded = ["callback", "deviceId"],
|
|
4
4
|
_excluded2 = ["callback", "deviceId"];
|
|
5
5
|
import { base64urlDecode, errorHandler, publicKeyCredentialToJSON } from '../../../helpers';
|
|
6
|
+
import { findWebAuthnDeviceTypeById, getPreferredMfaMethodFromWebAuthnDeviceType } from '../preferredMfaMethod';
|
|
7
|
+
import { persistPreferredMfaMethod } from '../helpers/persistPreferredMfaMethod';
|
|
6
8
|
export default ((store, api, sharedActions) => {
|
|
7
9
|
const actions = sharedActions;
|
|
8
10
|
|
|
@@ -73,6 +75,11 @@ export default ((store, api, sharedActions) => {
|
|
|
73
75
|
const data = await api.auth.verifyMFAWebAuthnForLoginV2(deviceId, _extends({}, payload, {
|
|
74
76
|
options: publicKey
|
|
75
77
|
}));
|
|
78
|
+
const mfaDevices = isStepUp ? store.auth.stepUpState.mfaDevices : store.auth.mfaState.mfaDevices;
|
|
79
|
+
const deviceType = findWebAuthnDeviceTypeById(mfaDevices, deviceId);
|
|
80
|
+
if (deviceType) {
|
|
81
|
+
persistPreferredMfaMethod(store, getPreferredMfaMethodFromWebAuthnDeviceType(deviceType));
|
|
82
|
+
}
|
|
76
83
|
await actions.handleVerifyMFAResponse(data, isStepUp);
|
|
77
84
|
setLoadingAction({
|
|
78
85
|
loading: false,
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { FronteggState } from '../../../interfaces';
|
|
2
|
+
import { PreferredMfaMethod } from '../preferredMfaMethod';
|
|
3
|
+
/**
|
|
4
|
+
* Resolve a stable application key for preferred-MFA session storage.
|
|
5
|
+
* Prefers appId (multi-app), then clientId, then appName.
|
|
6
|
+
*/
|
|
7
|
+
export declare const getPreferredMfaAppKey: (store: FronteggState) => string;
|
|
8
|
+
/**
|
|
9
|
+
* Persist preferred MFA method after a successful challenge.
|
|
10
|
+
* Always writes; themeOptions.rememberLastMfaFactor only gates whether it is applied.
|
|
11
|
+
*/
|
|
12
|
+
export declare const persistPreferredMfaMethod: (store: FronteggState, method: PreferredMfaMethod) => void;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ContextHolder } from '@frontegg/rest-api';
|
|
2
|
+
import { setPreferredMfaMethod } from '../preferredMfaMethod';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Resolve a stable application key for preferred-MFA session storage.
|
|
6
|
+
* Prefers appId (multi-app), then clientId, then appName.
|
|
7
|
+
*/
|
|
8
|
+
export const getPreferredMfaAppKey = store => {
|
|
9
|
+
const appName = store.root.appName;
|
|
10
|
+
const context = ContextHolder.for(appName).getContext();
|
|
11
|
+
return context.appId || context.clientId || appName || 'default';
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Persist preferred MFA method after a successful challenge.
|
|
16
|
+
* Always writes; themeOptions.rememberLastMfaFactor only gates whether it is applied.
|
|
17
|
+
*/
|
|
18
|
+
export const persistPreferredMfaMethod = (store, method) => {
|
|
19
|
+
setPreferredMfaMethod(getPreferredMfaAppKey(store), method);
|
|
20
|
+
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ContextHolder, MFAStrategyEnum } from '@frontegg/rest-api';
|
|
1
|
+
import { ContextHolder, MFAStrategyEnum, WebAuthnDeviceType } from '@frontegg/rest-api';
|
|
2
2
|
import { MFAStep } from '../MfaState/interfaces';
|
|
3
3
|
export const isAbsoluteUrl = path => {
|
|
4
4
|
try {
|
|
@@ -76,7 +76,8 @@ export const getSearchParam = key => {
|
|
|
76
76
|
};
|
|
77
77
|
export const TENANT_ID_PARAM_KEY = 'tenantId';
|
|
78
78
|
export const getNumberOfMfaDevices = mfaDevices => {
|
|
79
|
-
|
|
79
|
+
// Count platform / cross-platform separately — matches the MFA picker options.
|
|
80
|
+
const numberOfWebAuthnDevices = Number(mfaDevices.webauthn.some(device => device.deviceType === WebAuthnDeviceType.Platform)) + Number(mfaDevices.webauthn.some(device => device.deviceType === WebAuthnDeviceType.CrossPlatform));
|
|
80
81
|
const numberOfPhoneDevices = +Boolean(mfaDevices.phones.length);
|
|
81
82
|
const numberOfAuthenticators = +Boolean(mfaDevices.authenticators.length);
|
|
82
83
|
const numberOfEmails = +Boolean(mfaDevices.emails.length);
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { UserMFADevicesResponse, WebAuthnDeviceType } from '@frontegg/rest-api';
|
|
2
|
+
import { MFAStep } from '../MfaState/interfaces';
|
|
3
|
+
export type PreferredMfaMethod = 'sms' | 'email' | 'authenticatorApp' | 'webauthnPlatform' | 'webauthnCrossPlatform';
|
|
4
|
+
export type PreferredMfaResolution = {
|
|
5
|
+
type: 'step';
|
|
6
|
+
step: MFAStep;
|
|
7
|
+
} | {
|
|
8
|
+
type: 'webauthn';
|
|
9
|
+
deviceType: WebAuthnDeviceType;
|
|
10
|
+
} | {
|
|
11
|
+
type: 'none';
|
|
12
|
+
};
|
|
13
|
+
export declare const getPreferredMfaStorageKey: (appKey: string) => string;
|
|
14
|
+
export declare const isPreferredMfaMethod: (value: unknown) => value is PreferredMfaMethod;
|
|
15
|
+
/**
|
|
16
|
+
* Persist the last successful MFA method for this browser session, keyed by application.
|
|
17
|
+
*/
|
|
18
|
+
export declare const setPreferredMfaMethod: (appKey: string, method: PreferredMfaMethod) => void;
|
|
19
|
+
/**
|
|
20
|
+
* Read the remembered MFA method for this browser session, if any.
|
|
21
|
+
*/
|
|
22
|
+
export declare const getPreferredMfaMethod: (appKey: string) => PreferredMfaMethod | null;
|
|
23
|
+
export declare const clearPreferredMfaMethod: (appKey: string) => void;
|
|
24
|
+
/**
|
|
25
|
+
* Map a stored preferred MFA method to a concrete MFA step / WebAuthn action,
|
|
26
|
+
* only when that method is still enrolled and available.
|
|
27
|
+
*/
|
|
28
|
+
export declare const resolvePreferredMfa: (preferred: PreferredMfaMethod | null | undefined, mfaDevices?: UserMFADevicesResponse) => PreferredMfaResolution;
|
|
29
|
+
export declare const getPreferredMfaMethodFromWebAuthnDeviceType: (deviceType: WebAuthnDeviceType) => PreferredMfaMethod;
|
|
30
|
+
export declare const findWebAuthnDeviceTypeById: (mfaDevices: UserMFADevicesResponse | undefined, deviceId: string) => WebAuthnDeviceType | undefined;
|
|
31
|
+
export type PreferredMfaApplyAction = {
|
|
32
|
+
action: 'setStep';
|
|
33
|
+
step: MFAStep;
|
|
34
|
+
} | {
|
|
35
|
+
action: 'webauthn';
|
|
36
|
+
deviceId: string;
|
|
37
|
+
} | {
|
|
38
|
+
action: 'none';
|
|
39
|
+
};
|
|
40
|
+
export interface ApplyPreferredMfaFactorInput {
|
|
41
|
+
rememberLastMfaFactor: boolean;
|
|
42
|
+
alreadyApplied: boolean;
|
|
43
|
+
step: MFAStep;
|
|
44
|
+
preferred: PreferredMfaMethod | null;
|
|
45
|
+
mfaDevices: UserMFADevicesResponse | undefined;
|
|
46
|
+
mfaWebAuthnPlatformDeviceId?: string;
|
|
47
|
+
mfaWebAuthnCrossPlatformDeviceId?: string;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Pure decision for whether / how to apply a stored preferred MFA factor.
|
|
51
|
+
* Used by the login-box MFAFlow when themeOptions.rememberLastMfaFactor is on.
|
|
52
|
+
*/
|
|
53
|
+
export declare const getPreferredMfaApplyAction: ({ rememberLastMfaFactor, alreadyApplied, step, preferred, mfaDevices, mfaWebAuthnPlatformDeviceId, mfaWebAuthnCrossPlatformDeviceId, }: ApplyPreferredMfaFactorInput) => PreferredMfaApplyAction;
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { WebAuthnDeviceType } from '@frontegg/rest-api';
|
|
2
|
+
import { MFAStep } from '../MfaState/interfaces';
|
|
3
|
+
const STORAGE_KEY_PREFIX = 'frontegg-preferred-mfa-method';
|
|
4
|
+
const PREFERRED_MFA_METHODS = new Set(['sms', 'email', 'authenticatorApp', 'webauthnPlatform', 'webauthnCrossPlatform']);
|
|
5
|
+
export const getPreferredMfaStorageKey = appKey => `${STORAGE_KEY_PREFIX}:${appKey}`;
|
|
6
|
+
export const isPreferredMfaMethod = value => typeof value === 'string' && PREFERRED_MFA_METHODS.has(value);
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Persist the last successful MFA method for this browser session, keyed by application.
|
|
10
|
+
*/
|
|
11
|
+
export const setPreferredMfaMethod = (appKey, method) => {
|
|
12
|
+
if (typeof window === 'undefined') {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
try {
|
|
16
|
+
window.sessionStorage.setItem(getPreferredMfaStorageKey(appKey), method);
|
|
17
|
+
} catch {
|
|
18
|
+
// sessionStorage may be unavailable (private mode / quota) — ignore
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Read the remembered MFA method for this browser session, if any.
|
|
24
|
+
*/
|
|
25
|
+
export const getPreferredMfaMethod = appKey => {
|
|
26
|
+
if (typeof window === 'undefined') {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
const value = window.sessionStorage.getItem(getPreferredMfaStorageKey(appKey));
|
|
31
|
+
return isPreferredMfaMethod(value) ? value : null;
|
|
32
|
+
} catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
export const clearPreferredMfaMethod = appKey => {
|
|
37
|
+
if (typeof window === 'undefined') {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
window.sessionStorage.removeItem(getPreferredMfaStorageKey(appKey));
|
|
42
|
+
} catch {
|
|
43
|
+
// ignore
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
const hasPhoneDevices = mfaDevices => mfaDevices.phones.length > 0;
|
|
47
|
+
const hasEmails = mfaDevices => mfaDevices.emails.length > 0;
|
|
48
|
+
const hasAuthenticators = mfaDevices => mfaDevices.authenticators.length > 0;
|
|
49
|
+
const hasWebAuthnDevice = (mfaDevices, deviceType) => mfaDevices.webauthn.some(device => device.deviceType === deviceType);
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Map a stored preferred MFA method to a concrete MFA step / WebAuthn action,
|
|
53
|
+
* only when that method is still enrolled and available.
|
|
54
|
+
*/
|
|
55
|
+
export const resolvePreferredMfa = (preferred, mfaDevices) => {
|
|
56
|
+
if (!preferred || !mfaDevices) {
|
|
57
|
+
return {
|
|
58
|
+
type: 'none'
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
switch (preferred) {
|
|
62
|
+
case 'sms':
|
|
63
|
+
return hasPhoneDevices(mfaDevices) ? {
|
|
64
|
+
type: 'step',
|
|
65
|
+
step: MFAStep.smsVerifyCode
|
|
66
|
+
} : {
|
|
67
|
+
type: 'none'
|
|
68
|
+
};
|
|
69
|
+
case 'email':
|
|
70
|
+
return hasEmails(mfaDevices) ? {
|
|
71
|
+
type: 'step',
|
|
72
|
+
step: MFAStep.emailVerifyCode
|
|
73
|
+
} : {
|
|
74
|
+
type: 'none'
|
|
75
|
+
};
|
|
76
|
+
case 'authenticatorApp':
|
|
77
|
+
return hasAuthenticators(mfaDevices) ? {
|
|
78
|
+
type: 'step',
|
|
79
|
+
step: MFAStep.authenticatorApp
|
|
80
|
+
} : {
|
|
81
|
+
type: 'none'
|
|
82
|
+
};
|
|
83
|
+
case 'webauthnPlatform':
|
|
84
|
+
return hasWebAuthnDevice(mfaDevices, WebAuthnDeviceType.Platform) ? {
|
|
85
|
+
type: 'webauthn',
|
|
86
|
+
deviceType: WebAuthnDeviceType.Platform
|
|
87
|
+
} : {
|
|
88
|
+
type: 'none'
|
|
89
|
+
};
|
|
90
|
+
case 'webauthnCrossPlatform':
|
|
91
|
+
return hasWebAuthnDevice(mfaDevices, WebAuthnDeviceType.CrossPlatform) ? {
|
|
92
|
+
type: 'webauthn',
|
|
93
|
+
deviceType: WebAuthnDeviceType.CrossPlatform
|
|
94
|
+
} : {
|
|
95
|
+
type: 'none'
|
|
96
|
+
};
|
|
97
|
+
default:
|
|
98
|
+
{
|
|
99
|
+
const _exhaustive = preferred;
|
|
100
|
+
return _exhaustive;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
export const getPreferredMfaMethodFromWebAuthnDeviceType = deviceType => {
|
|
105
|
+
return deviceType === WebAuthnDeviceType.Platform ? 'webauthnPlatform' : 'webauthnCrossPlatform';
|
|
106
|
+
};
|
|
107
|
+
export const findWebAuthnDeviceTypeById = (mfaDevices, deviceId) => {
|
|
108
|
+
var _mfaDevices$webauthn$;
|
|
109
|
+
return mfaDevices == null ? void 0 : (_mfaDevices$webauthn$ = mfaDevices.webauthn.find(device => device.id === deviceId)) == null ? void 0 : _mfaDevices$webauthn$.deviceType;
|
|
110
|
+
};
|
|
111
|
+
/**
|
|
112
|
+
* Pure decision for whether / how to apply a stored preferred MFA factor.
|
|
113
|
+
* Used by the login-box MFAFlow when themeOptions.rememberLastMfaFactor is on.
|
|
114
|
+
*/
|
|
115
|
+
export const getPreferredMfaApplyAction = ({
|
|
116
|
+
rememberLastMfaFactor,
|
|
117
|
+
alreadyApplied,
|
|
118
|
+
step,
|
|
119
|
+
preferred,
|
|
120
|
+
mfaDevices,
|
|
121
|
+
mfaWebAuthnPlatformDeviceId,
|
|
122
|
+
mfaWebAuthnCrossPlatformDeviceId
|
|
123
|
+
}) => {
|
|
124
|
+
if (!rememberLastMfaFactor || alreadyApplied || step !== MFAStep.verify) {
|
|
125
|
+
return {
|
|
126
|
+
action: 'none'
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
const resolution = resolvePreferredMfa(preferred, mfaDevices);
|
|
130
|
+
if (resolution.type === 'none') {
|
|
131
|
+
return {
|
|
132
|
+
action: 'none'
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
if (resolution.type === 'step') {
|
|
136
|
+
return {
|
|
137
|
+
action: 'setStep',
|
|
138
|
+
step: resolution.step
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
const deviceId = resolution.deviceType === WebAuthnDeviceType.Platform ? mfaWebAuthnPlatformDeviceId : mfaWebAuthnCrossPlatformDeviceId;
|
|
142
|
+
if (!deviceId) {
|
|
143
|
+
return {
|
|
144
|
+
action: 'none'
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
action: 'webauthn',
|
|
149
|
+
deviceId
|
|
150
|
+
};
|
|
151
|
+
};
|
package/auth/MfaState/actions.js
CHANGED
|
@@ -81,12 +81,14 @@ export default ((store, api, sharedActions) => {
|
|
|
81
81
|
});
|
|
82
82
|
try {
|
|
83
83
|
const {
|
|
84
|
-
qrCode
|
|
84
|
+
qrCode,
|
|
85
|
+
secret
|
|
85
86
|
} = await api.auth.enrollMfa();
|
|
86
87
|
setMfaState({
|
|
87
88
|
saving: false,
|
|
88
89
|
error: undefined,
|
|
89
|
-
qrCode
|
|
90
|
+
qrCode,
|
|
91
|
+
secret
|
|
90
92
|
});
|
|
91
93
|
} catch (e) {
|
|
92
94
|
setMfaState({
|
|
@@ -17,6 +17,8 @@ export interface MFAState {
|
|
|
17
17
|
error?: any;
|
|
18
18
|
recoveryCode?: string;
|
|
19
19
|
qrCode?: string | null;
|
|
20
|
+
/** TOTP setup key for manual authenticator enrollment (same secret as in qrCode). */
|
|
21
|
+
secret?: string | null;
|
|
20
22
|
mfaToken?: string;
|
|
21
23
|
otcToken?: string;
|
|
22
24
|
phoneNumber?: string;
|
|
@@ -23,6 +23,7 @@ declare const _default: (store: FronteggState, api: RestApi, sharedActions: Shar
|
|
|
23
23
|
loadUsersByGetUsersPhoneNumberV2: (payload: WithCallback<import("../../../interfaces").WithRetryConfig<import("../../../interfaces").WithSilentLoad<import("../interfaces").ILoadUsersByGetPhoneNumberV2Payload>>, import("@frontegg/rest-api").ITeamUser[]>) => Promise<void>;
|
|
24
24
|
loadUsersByGetUsernamesV1: (payload: WithCallback<import("../../../interfaces").WithRetryConfig<import("../../../interfaces").WithSilentLoad<import("../interfaces").ILoadUsersByGetUsernamesV1Payload>>, import("@frontegg/rest-api").ITeamUser[]>) => Promise<void>;
|
|
25
25
|
loadUsersByGetEmailsV1: (payload: WithCallback<import("../../../interfaces").WithRetryConfig<import("../../../interfaces").WithSilentLoad<import("../interfaces").ILoadUsersByGetEmailsV1Payload>>, import("@frontegg/rest-api").ITeamUser[]>) => Promise<void>;
|
|
26
|
+
loadUsersByRolesV1: (payload: WithCallback<import("../../../interfaces").WithRetryConfig<import("../../../interfaces").WithSilentLoad<import("../interfaces").ILoadUsersByRolesV1Payload>>, import("@frontegg/rest-api").ITeamUser[]>) => Promise<void>;
|
|
26
27
|
addUser: (payload: WithCallback<import("../interfaces").AddUserPayload, import("@frontegg/rest-api").ITeamUser>) => Promise<void>;
|
|
27
28
|
addUsersBulk: (payload: WithCallback<import("../interfaces").IAddUsers, import("../interfaces").BulkInvintationData>) => Promise<void>;
|
|
28
29
|
updateUser: (payload: WithCallback<import("@frontegg/rest-api").IUpdateUser, import("@frontegg/rest-api").ITeamUser>) => Promise<void>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { IDeleteUser, ILoadUsers, ITeamUser, IUpdateUser, IUpdateUserExpiration } from '@frontegg/rest-api';
|
|
2
2
|
import { FronteggState, RestApi, SharedActions, WithCallback, WithRetryConfig, WithSilentLoad } from '../../../interfaces';
|
|
3
|
-
import { AddUserPayload, BulkInvintationData, IAddUsers, IGetUsersV2Payload, IGetUsersV3Payload, ILoadUsersByGetPhoneNumberV2Payload, ILoadUsersByGetUsernamesV1Payload, ILoadUsersByGetEmailsV1Payload, LoadRolesAndPermissionsPayload } from '../interfaces';
|
|
3
|
+
import { AddUserPayload, BulkInvintationData, IAddUsers, IGetUsersV2Payload, IGetUsersV3Payload, ILoadUsersByGetPhoneNumberV2Payload, ILoadUsersByGetUsernamesV1Payload, ILoadUsersByGetEmailsV1Payload, ILoadUsersByRolesV1Payload, LoadRolesAndPermissionsPayload } from '../interfaces';
|
|
4
4
|
declare const _default: (store: FronteggState, api: RestApi, sharedActions: SharedActions) => {
|
|
5
5
|
loadRoles: (payload?: WithRetryConfig<LoadRolesAndPermissionsPayload>) => Promise<void>;
|
|
6
6
|
loadUsers: (payload: WithCallback<WithSilentLoad<ILoadUsers>, ITeamUser[]>) => Promise<void>;
|
|
@@ -9,6 +9,7 @@ declare const _default: (store: FronteggState, api: RestApi, sharedActions: Shar
|
|
|
9
9
|
loadUsersByGetUsersPhoneNumberV2: (payload: WithCallback<WithRetryConfig<WithSilentLoad<ILoadUsersByGetPhoneNumberV2Payload>>, ITeamUser[]>) => Promise<void>;
|
|
10
10
|
loadUsersByGetUsernamesV1: (payload: WithCallback<WithRetryConfig<WithSilentLoad<ILoadUsersByGetUsernamesV1Payload>>, ITeamUser[]>) => Promise<void>;
|
|
11
11
|
loadUsersByGetEmailsV1: (payload: WithCallback<WithRetryConfig<WithSilentLoad<ILoadUsersByGetEmailsV1Payload>>, ITeamUser[]>) => Promise<void>;
|
|
12
|
+
loadUsersByRolesV1: (payload: WithCallback<WithRetryConfig<WithSilentLoad<ILoadUsersByRolesV1Payload>>, ITeamUser[]>) => Promise<void>;
|
|
12
13
|
addUser: (payload: WithCallback<AddUserPayload, ITeamUser>) => Promise<void>;
|
|
13
14
|
addUsersBulk: (payload: WithCallback<IAddUsers, BulkInvintationData>) => Promise<void>;
|
|
14
15
|
updateUser: (payload: WithCallback<IUpdateUser, ITeamUser>) => Promise<void>;
|
|
@@ -900,6 +900,28 @@ export default ((store, api, sharedActions) => {
|
|
|
900
900
|
_limit: pageSize
|
|
901
901
|
}), shouldLoadRoles, shouldLoadApps);
|
|
902
902
|
};
|
|
903
|
+
const loadUsersByRolesV1 = async payload => {
|
|
904
|
+
const {
|
|
905
|
+
_roleIds,
|
|
906
|
+
shouldLoadRoles = true,
|
|
907
|
+
shouldLoadApps
|
|
908
|
+
} = payload;
|
|
909
|
+
if (!(_roleIds != null && _roleIds.length)) {
|
|
910
|
+
var _payload$callback3;
|
|
911
|
+
sharedActions.setTeamState({
|
|
912
|
+
users: [],
|
|
913
|
+
totalPages: 0,
|
|
914
|
+
totalItems: 0
|
|
915
|
+
});
|
|
916
|
+
(_payload$callback3 = payload.callback) == null ? void 0 : _payload$callback3.call(payload, []);
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
return __loadUsersBySearchCriteria(payload, (pageOffset, pageSize) => api.usersRoles.getUsersByRolesV1({
|
|
920
|
+
_roleIds,
|
|
921
|
+
_offset: pageOffset,
|
|
922
|
+
_limit: pageSize
|
|
923
|
+
}), shouldLoadRoles, shouldLoadApps);
|
|
924
|
+
};
|
|
903
925
|
return {
|
|
904
926
|
loadRoles,
|
|
905
927
|
loadUsers,
|
|
@@ -908,6 +930,7 @@ export default ((store, api, sharedActions) => {
|
|
|
908
930
|
loadUsersByGetUsersPhoneNumberV2,
|
|
909
931
|
loadUsersByGetUsernamesV1,
|
|
910
932
|
loadUsersByGetEmailsV1,
|
|
933
|
+
loadUsersByRolesV1,
|
|
911
934
|
addUser,
|
|
912
935
|
addUsersBulk,
|
|
913
936
|
updateUser,
|
|
@@ -148,6 +148,9 @@ export interface ILoadUsersByGetUsernamesV1Payload extends IBaseUserSearchPayloa
|
|
|
148
148
|
export interface ILoadUsersByGetEmailsV1Payload extends IBaseUserSearchPayload {
|
|
149
149
|
_email: string;
|
|
150
150
|
}
|
|
151
|
+
export interface ILoadUsersByRolesV1Payload extends IBaseUserSearchPayload {
|
|
152
|
+
_roleIds: string[];
|
|
153
|
+
}
|
|
151
154
|
export interface ISearchUsersByAnyFieldPayload extends Partial<IGetUsersV3Payload> {
|
|
152
155
|
callback?: (users: ITeamUser[] | null, error?: any) => void;
|
|
153
156
|
}
|
package/auth/index.d.ts
CHANGED
|
@@ -77,6 +77,7 @@ export * from './UsersEmailsPolicyState/interfaces';
|
|
|
77
77
|
export * from './interfaces';
|
|
78
78
|
export { FORGOT_PASSWORD_ERROR_KEYS };
|
|
79
79
|
export * from './LoginState/oauthStorage';
|
|
80
|
+
export * from './LoginState/preferredMfaMethod';
|
|
80
81
|
export * from './LoginState/helpers/tenantSelect';
|
|
81
82
|
export declare const createAuthState: (_overrideState?: DeepPartial<AuthState>) => AuthState;
|
|
82
83
|
export declare const buildAuthActions: (store: FronteggState, api: RestApi, actions: FronteggActions, snapshotAuthState: AuthState) => [AuthActions, AuthStateActions];
|
package/auth/index.js
CHANGED
|
@@ -81,6 +81,7 @@ export * from './UsersEmailsPolicyState/interfaces';
|
|
|
81
81
|
export * from './interfaces';
|
|
82
82
|
export { FORGOT_PASSWORD_ERROR_KEYS };
|
|
83
83
|
export * from './LoginState/oauthStorage';
|
|
84
|
+
export * from './LoginState/preferredMfaMethod';
|
|
84
85
|
export * from './LoginState/helpers/tenantSelect';
|
|
85
86
|
export const createAuthState = _overrideState => {
|
|
86
87
|
const _ref = _overrideState != null ? _overrideState : {},
|
package/index.js
CHANGED
|
@@ -23,6 +23,7 @@ declare const _default: (store: FronteggState, api: RestApi, actions: SharedActi
|
|
|
23
23
|
loadUsersByGetUsersPhoneNumberV2: (payload: WithCallback<import("../../interfaces").WithRetryConfig<WithSilentLoad<import("../../auth").ILoadUsersByGetPhoneNumberV2Payload>>, ITeamUser[]>) => Promise<void>;
|
|
24
24
|
loadUsersByGetUsernamesV1: (payload: WithCallback<import("../../interfaces").WithRetryConfig<WithSilentLoad<import("../../auth").ILoadUsersByGetUsernamesV1Payload>>, ITeamUser[]>) => Promise<void>;
|
|
25
25
|
loadUsersByGetEmailsV1: (payload: WithCallback<import("../../interfaces").WithRetryConfig<WithSilentLoad<import("../../auth").ILoadUsersByGetEmailsV1Payload>>, ITeamUser[]>) => Promise<void>;
|
|
26
|
+
loadUsersByRolesV1: (payload: WithCallback<import("../../interfaces").WithRetryConfig<WithSilentLoad<import("../../auth").ILoadUsersByRolesV1Payload>>, ITeamUser[]>) => Promise<void>;
|
|
26
27
|
addUser: (payload: WithCallback<import("../../auth").AddUserPayload, ITeamUser>) => Promise<void>;
|
|
27
28
|
addUsersBulk: (payload: WithCallback<import("../../auth").IAddUsers, import("../../auth").BulkInvintationData>) => Promise<void>;
|
|
28
29
|
updateUser: (payload: WithCallback<IUpdateUser, ITeamUser>) => Promise<void>;
|
|
@@ -42,6 +42,7 @@ var _default = (store, api, _sharedActions) => {
|
|
|
42
42
|
Object.assign(mfaState, {
|
|
43
43
|
step: (0, _helpers.getMfaStepForNotEnrolledUsers)(user.mfaStrategies),
|
|
44
44
|
qrCode: user.qrCode,
|
|
45
|
+
secret: user.secret,
|
|
45
46
|
recoveryCode: user.recoveryCode,
|
|
46
47
|
loading: false,
|
|
47
48
|
mfaToken: user.mfaToken,
|
|
@@ -10,6 +10,7 @@ var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends")
|
|
|
10
10
|
var _interfaces = require("../interfaces");
|
|
11
11
|
var _helpers = require("../../../helpers");
|
|
12
12
|
var _helpers2 = require("../../helpers");
|
|
13
|
+
var _persistPreferredMfaMethod = require("../helpers/persistPreferredMfaMethod");
|
|
13
14
|
const _excluded = ["callback"];
|
|
14
15
|
var _default = (store, api, sharedActions) => {
|
|
15
16
|
const actions = sharedActions;
|
|
@@ -120,6 +121,7 @@ var _default = (store, api, sharedActions) => {
|
|
|
120
121
|
tenants = [],
|
|
121
122
|
activeTenant
|
|
122
123
|
} = await api.auth.loginWithMfaV2(loginWithMfaPayload);
|
|
124
|
+
(0, _persistPreferredMfaMethod.persistPreferredMfaMethod)(store, 'authenticatorApp');
|
|
123
125
|
const isAuthenticated = !!user.accessToken;
|
|
124
126
|
const additionalUpdate = await __buildPostAuthStateUpdate(tenants, isAuthenticated, isStepUp);
|
|
125
127
|
await actions.afterAuthenticationStateUpdate({
|
|
@@ -8,6 +8,7 @@ exports.default = void 0;
|
|
|
8
8
|
var _objectWithoutPropertiesLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutPropertiesLoose"));
|
|
9
9
|
var _interfaces = require("../../MfaState/interfaces");
|
|
10
10
|
var _helpers = require("../../../helpers");
|
|
11
|
+
var _persistPreferredMfaMethod = require("../helpers/persistPreferredMfaMethod");
|
|
11
12
|
const _excluded = ["callback"],
|
|
12
13
|
_excluded2 = ["callback"];
|
|
13
14
|
var _default = (store, api, sharedActions) => {
|
|
@@ -66,6 +67,7 @@ var _default = (store, api, sharedActions) => {
|
|
|
66
67
|
});
|
|
67
68
|
try {
|
|
68
69
|
const data = await api.auth.verifyMFAEmailCodeV2(payload);
|
|
70
|
+
(0, _persistPreferredMfaMethod.persistPreferredMfaMethod)(store, 'email');
|
|
69
71
|
await actions.handleVerifyMFAResponse(data);
|
|
70
72
|
setLoadingAction({
|
|
71
73
|
loading: false,
|
|
@@ -8,6 +8,7 @@ exports.default = void 0;
|
|
|
8
8
|
var _objectWithoutPropertiesLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutPropertiesLoose"));
|
|
9
9
|
var _interfaces = require("../../MfaState/interfaces");
|
|
10
10
|
var _helpers = require("../../../helpers");
|
|
11
|
+
var _persistPreferredMfaMethod = require("../helpers/persistPreferredMfaMethod");
|
|
11
12
|
const _excluded = ["callback", "deviceId"],
|
|
12
13
|
_excluded2 = ["callback", "deviceId"];
|
|
13
14
|
var _default = (store, api, sharedActions) => {
|
|
@@ -71,6 +72,7 @@ var _default = (store, api, sharedActions) => {
|
|
|
71
72
|
});
|
|
72
73
|
try {
|
|
73
74
|
const data = await api.auth.verifyMFASMSForLoginV2(deviceId, payload);
|
|
75
|
+
(0, _persistPreferredMfaMethod.persistPreferredMfaMethod)(store, 'sms');
|
|
74
76
|
await actions.handleVerifyMFAResponse(data, isStepUp);
|
|
75
77
|
setLoadingAction({
|
|
76
78
|
loading: false,
|
|
@@ -8,6 +8,8 @@ exports.default = void 0;
|
|
|
8
8
|
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
|
|
9
9
|
var _objectWithoutPropertiesLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutPropertiesLoose"));
|
|
10
10
|
var _helpers = require("../../../helpers");
|
|
11
|
+
var _preferredMfaMethod = require("../preferredMfaMethod");
|
|
12
|
+
var _persistPreferredMfaMethod = require("../helpers/persistPreferredMfaMethod");
|
|
11
13
|
const _excluded = ["callback", "deviceId"],
|
|
12
14
|
_excluded2 = ["callback", "deviceId"];
|
|
13
15
|
var _default = (store, api, sharedActions) => {
|
|
@@ -80,6 +82,11 @@ var _default = (store, api, sharedActions) => {
|
|
|
80
82
|
const data = await api.auth.verifyMFAWebAuthnForLoginV2(deviceId, (0, _extends2.default)({}, payload, {
|
|
81
83
|
options: publicKey
|
|
82
84
|
}));
|
|
85
|
+
const mfaDevices = isStepUp ? store.auth.stepUpState.mfaDevices : store.auth.mfaState.mfaDevices;
|
|
86
|
+
const deviceType = (0, _preferredMfaMethod.findWebAuthnDeviceTypeById)(mfaDevices, deviceId);
|
|
87
|
+
if (deviceType) {
|
|
88
|
+
(0, _persistPreferredMfaMethod.persistPreferredMfaMethod)(store, (0, _preferredMfaMethod.getPreferredMfaMethodFromWebAuthnDeviceType)(deviceType));
|
|
89
|
+
}
|
|
83
90
|
await actions.handleVerifyMFAResponse(data, isStepUp);
|
|
84
91
|
setLoadingAction({
|
|
85
92
|
loading: false,
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.persistPreferredMfaMethod = exports.getPreferredMfaAppKey = void 0;
|
|
7
|
+
var _restApi = require("@frontegg/rest-api");
|
|
8
|
+
var _preferredMfaMethod = require("../preferredMfaMethod");
|
|
9
|
+
/**
|
|
10
|
+
* Resolve a stable application key for preferred-MFA session storage.
|
|
11
|
+
* Prefers appId (multi-app), then clientId, then appName.
|
|
12
|
+
*/
|
|
13
|
+
const getPreferredMfaAppKey = store => {
|
|
14
|
+
const appName = store.root.appName;
|
|
15
|
+
const context = _restApi.ContextHolder.for(appName).getContext();
|
|
16
|
+
return context.appId || context.clientId || appName || 'default';
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Persist preferred MFA method after a successful challenge.
|
|
21
|
+
* Always writes; themeOptions.rememberLastMfaFactor only gates whether it is applied.
|
|
22
|
+
*/
|
|
23
|
+
exports.getPreferredMfaAppKey = getPreferredMfaAppKey;
|
|
24
|
+
const persistPreferredMfaMethod = (store, method) => {
|
|
25
|
+
(0, _preferredMfaMethod.setPreferredMfaMethod)(getPreferredMfaAppKey(store), method);
|
|
26
|
+
};
|
|
27
|
+
exports.persistPreferredMfaMethod = persistPreferredMfaMethod;
|
|
@@ -92,7 +92,8 @@ exports.getSearchParam = getSearchParam;
|
|
|
92
92
|
const TENANT_ID_PARAM_KEY = 'tenantId';
|
|
93
93
|
exports.TENANT_ID_PARAM_KEY = TENANT_ID_PARAM_KEY;
|
|
94
94
|
const getNumberOfMfaDevices = mfaDevices => {
|
|
95
|
-
|
|
95
|
+
// Count platform / cross-platform separately — matches the MFA picker options.
|
|
96
|
+
const numberOfWebAuthnDevices = Number(mfaDevices.webauthn.some(device => device.deviceType === _restApi.WebAuthnDeviceType.Platform)) + Number(mfaDevices.webauthn.some(device => device.deviceType === _restApi.WebAuthnDeviceType.CrossPlatform));
|
|
96
97
|
const numberOfPhoneDevices = +Boolean(mfaDevices.phones.length);
|
|
97
98
|
const numberOfAuthenticators = +Boolean(mfaDevices.authenticators.length);
|
|
98
99
|
const numberOfEmails = +Boolean(mfaDevices.emails.length);
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.setPreferredMfaMethod = exports.resolvePreferredMfa = exports.isPreferredMfaMethod = exports.getPreferredMfaStorageKey = exports.getPreferredMfaMethodFromWebAuthnDeviceType = exports.getPreferredMfaMethod = exports.getPreferredMfaApplyAction = exports.findWebAuthnDeviceTypeById = exports.clearPreferredMfaMethod = void 0;
|
|
7
|
+
var _restApi = require("@frontegg/rest-api");
|
|
8
|
+
var _interfaces = require("../MfaState/interfaces");
|
|
9
|
+
const STORAGE_KEY_PREFIX = 'frontegg-preferred-mfa-method';
|
|
10
|
+
const PREFERRED_MFA_METHODS = new Set(['sms', 'email', 'authenticatorApp', 'webauthnPlatform', 'webauthnCrossPlatform']);
|
|
11
|
+
const getPreferredMfaStorageKey = appKey => `${STORAGE_KEY_PREFIX}:${appKey}`;
|
|
12
|
+
exports.getPreferredMfaStorageKey = getPreferredMfaStorageKey;
|
|
13
|
+
const isPreferredMfaMethod = value => typeof value === 'string' && PREFERRED_MFA_METHODS.has(value);
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Persist the last successful MFA method for this browser session, keyed by application.
|
|
17
|
+
*/
|
|
18
|
+
exports.isPreferredMfaMethod = isPreferredMfaMethod;
|
|
19
|
+
const setPreferredMfaMethod = (appKey, method) => {
|
|
20
|
+
if (typeof window === 'undefined') {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
window.sessionStorage.setItem(getPreferredMfaStorageKey(appKey), method);
|
|
25
|
+
} catch {
|
|
26
|
+
// sessionStorage may be unavailable (private mode / quota) — ignore
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Read the remembered MFA method for this browser session, if any.
|
|
32
|
+
*/
|
|
33
|
+
exports.setPreferredMfaMethod = setPreferredMfaMethod;
|
|
34
|
+
const getPreferredMfaMethod = appKey => {
|
|
35
|
+
if (typeof window === 'undefined') {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
const value = window.sessionStorage.getItem(getPreferredMfaStorageKey(appKey));
|
|
40
|
+
return isPreferredMfaMethod(value) ? value : null;
|
|
41
|
+
} catch {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
exports.getPreferredMfaMethod = getPreferredMfaMethod;
|
|
46
|
+
const clearPreferredMfaMethod = appKey => {
|
|
47
|
+
if (typeof window === 'undefined') {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
try {
|
|
51
|
+
window.sessionStorage.removeItem(getPreferredMfaStorageKey(appKey));
|
|
52
|
+
} catch {
|
|
53
|
+
// ignore
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
exports.clearPreferredMfaMethod = clearPreferredMfaMethod;
|
|
57
|
+
const hasPhoneDevices = mfaDevices => mfaDevices.phones.length > 0;
|
|
58
|
+
const hasEmails = mfaDevices => mfaDevices.emails.length > 0;
|
|
59
|
+
const hasAuthenticators = mfaDevices => mfaDevices.authenticators.length > 0;
|
|
60
|
+
const hasWebAuthnDevice = (mfaDevices, deviceType) => mfaDevices.webauthn.some(device => device.deviceType === deviceType);
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Map a stored preferred MFA method to a concrete MFA step / WebAuthn action,
|
|
64
|
+
* only when that method is still enrolled and available.
|
|
65
|
+
*/
|
|
66
|
+
const resolvePreferredMfa = (preferred, mfaDevices) => {
|
|
67
|
+
if (!preferred || !mfaDevices) {
|
|
68
|
+
return {
|
|
69
|
+
type: 'none'
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
switch (preferred) {
|
|
73
|
+
case 'sms':
|
|
74
|
+
return hasPhoneDevices(mfaDevices) ? {
|
|
75
|
+
type: 'step',
|
|
76
|
+
step: _interfaces.MFAStep.smsVerifyCode
|
|
77
|
+
} : {
|
|
78
|
+
type: 'none'
|
|
79
|
+
};
|
|
80
|
+
case 'email':
|
|
81
|
+
return hasEmails(mfaDevices) ? {
|
|
82
|
+
type: 'step',
|
|
83
|
+
step: _interfaces.MFAStep.emailVerifyCode
|
|
84
|
+
} : {
|
|
85
|
+
type: 'none'
|
|
86
|
+
};
|
|
87
|
+
case 'authenticatorApp':
|
|
88
|
+
return hasAuthenticators(mfaDevices) ? {
|
|
89
|
+
type: 'step',
|
|
90
|
+
step: _interfaces.MFAStep.authenticatorApp
|
|
91
|
+
} : {
|
|
92
|
+
type: 'none'
|
|
93
|
+
};
|
|
94
|
+
case 'webauthnPlatform':
|
|
95
|
+
return hasWebAuthnDevice(mfaDevices, _restApi.WebAuthnDeviceType.Platform) ? {
|
|
96
|
+
type: 'webauthn',
|
|
97
|
+
deviceType: _restApi.WebAuthnDeviceType.Platform
|
|
98
|
+
} : {
|
|
99
|
+
type: 'none'
|
|
100
|
+
};
|
|
101
|
+
case 'webauthnCrossPlatform':
|
|
102
|
+
return hasWebAuthnDevice(mfaDevices, _restApi.WebAuthnDeviceType.CrossPlatform) ? {
|
|
103
|
+
type: 'webauthn',
|
|
104
|
+
deviceType: _restApi.WebAuthnDeviceType.CrossPlatform
|
|
105
|
+
} : {
|
|
106
|
+
type: 'none'
|
|
107
|
+
};
|
|
108
|
+
default:
|
|
109
|
+
{
|
|
110
|
+
const _exhaustive = preferred;
|
|
111
|
+
return _exhaustive;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
exports.resolvePreferredMfa = resolvePreferredMfa;
|
|
116
|
+
const getPreferredMfaMethodFromWebAuthnDeviceType = deviceType => {
|
|
117
|
+
return deviceType === _restApi.WebAuthnDeviceType.Platform ? 'webauthnPlatform' : 'webauthnCrossPlatform';
|
|
118
|
+
};
|
|
119
|
+
exports.getPreferredMfaMethodFromWebAuthnDeviceType = getPreferredMfaMethodFromWebAuthnDeviceType;
|
|
120
|
+
const findWebAuthnDeviceTypeById = (mfaDevices, deviceId) => {
|
|
121
|
+
var _mfaDevices$webauthn$;
|
|
122
|
+
return mfaDevices == null ? void 0 : (_mfaDevices$webauthn$ = mfaDevices.webauthn.find(device => device.id === deviceId)) == null ? void 0 : _mfaDevices$webauthn$.deviceType;
|
|
123
|
+
};
|
|
124
|
+
exports.findWebAuthnDeviceTypeById = findWebAuthnDeviceTypeById;
|
|
125
|
+
/**
|
|
126
|
+
* Pure decision for whether / how to apply a stored preferred MFA factor.
|
|
127
|
+
* Used by the login-box MFAFlow when themeOptions.rememberLastMfaFactor is on.
|
|
128
|
+
*/
|
|
129
|
+
const getPreferredMfaApplyAction = ({
|
|
130
|
+
rememberLastMfaFactor,
|
|
131
|
+
alreadyApplied,
|
|
132
|
+
step,
|
|
133
|
+
preferred,
|
|
134
|
+
mfaDevices,
|
|
135
|
+
mfaWebAuthnPlatformDeviceId,
|
|
136
|
+
mfaWebAuthnCrossPlatformDeviceId
|
|
137
|
+
}) => {
|
|
138
|
+
if (!rememberLastMfaFactor || alreadyApplied || step !== _interfaces.MFAStep.verify) {
|
|
139
|
+
return {
|
|
140
|
+
action: 'none'
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
const resolution = resolvePreferredMfa(preferred, mfaDevices);
|
|
144
|
+
if (resolution.type === 'none') {
|
|
145
|
+
return {
|
|
146
|
+
action: 'none'
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
if (resolution.type === 'step') {
|
|
150
|
+
return {
|
|
151
|
+
action: 'setStep',
|
|
152
|
+
step: resolution.step
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
const deviceId = resolution.deviceType === _restApi.WebAuthnDeviceType.Platform ? mfaWebAuthnPlatformDeviceId : mfaWebAuthnCrossPlatformDeviceId;
|
|
156
|
+
if (!deviceId) {
|
|
157
|
+
return {
|
|
158
|
+
action: 'none'
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
action: 'webauthn',
|
|
163
|
+
deviceId
|
|
164
|
+
};
|
|
165
|
+
};
|
|
166
|
+
exports.getPreferredMfaApplyAction = getPreferredMfaApplyAction;
|
|
@@ -88,12 +88,14 @@ var _default = (store, api, sharedActions) => {
|
|
|
88
88
|
});
|
|
89
89
|
try {
|
|
90
90
|
const {
|
|
91
|
-
qrCode
|
|
91
|
+
qrCode,
|
|
92
|
+
secret
|
|
92
93
|
} = await api.auth.enrollMfa();
|
|
93
94
|
setMfaState({
|
|
94
95
|
saving: false,
|
|
95
96
|
error: undefined,
|
|
96
|
-
qrCode
|
|
97
|
+
qrCode,
|
|
98
|
+
secret
|
|
97
99
|
});
|
|
98
100
|
} catch (e) {
|
|
99
101
|
setMfaState({
|
|
@@ -907,6 +907,28 @@ var _default = (store, api, sharedActions) => {
|
|
|
907
907
|
_limit: pageSize
|
|
908
908
|
}), shouldLoadRoles, shouldLoadApps);
|
|
909
909
|
};
|
|
910
|
+
const loadUsersByRolesV1 = async payload => {
|
|
911
|
+
const {
|
|
912
|
+
_roleIds,
|
|
913
|
+
shouldLoadRoles = true,
|
|
914
|
+
shouldLoadApps
|
|
915
|
+
} = payload;
|
|
916
|
+
if (!(_roleIds != null && _roleIds.length)) {
|
|
917
|
+
var _payload$callback3;
|
|
918
|
+
sharedActions.setTeamState({
|
|
919
|
+
users: [],
|
|
920
|
+
totalPages: 0,
|
|
921
|
+
totalItems: 0
|
|
922
|
+
});
|
|
923
|
+
(_payload$callback3 = payload.callback) == null ? void 0 : _payload$callback3.call(payload, []);
|
|
924
|
+
return;
|
|
925
|
+
}
|
|
926
|
+
return __loadUsersBySearchCriteria(payload, (pageOffset, pageSize) => api.usersRoles.getUsersByRolesV1({
|
|
927
|
+
_roleIds,
|
|
928
|
+
_offset: pageOffset,
|
|
929
|
+
_limit: pageSize
|
|
930
|
+
}), shouldLoadRoles, shouldLoadApps);
|
|
931
|
+
};
|
|
910
932
|
return {
|
|
911
933
|
loadRoles,
|
|
912
934
|
loadUsers,
|
|
@@ -915,6 +937,7 @@ var _default = (store, api, sharedActions) => {
|
|
|
915
937
|
loadUsersByGetUsersPhoneNumberV2,
|
|
916
938
|
loadUsersByGetUsernamesV1,
|
|
917
939
|
loadUsersByGetEmailsV1,
|
|
940
|
+
loadUsersByRolesV1,
|
|
918
941
|
addUser,
|
|
919
942
|
addUsersBulk,
|
|
920
943
|
updateUser,
|
package/node/auth/index.js
CHANGED
|
@@ -525,6 +525,18 @@ Object.keys(_oauthStorage).forEach(function (key) {
|
|
|
525
525
|
}
|
|
526
526
|
});
|
|
527
527
|
});
|
|
528
|
+
var _preferredMfaMethod = require("./LoginState/preferredMfaMethod");
|
|
529
|
+
Object.keys(_preferredMfaMethod).forEach(function (key) {
|
|
530
|
+
if (key === "default" || key === "__esModule") return;
|
|
531
|
+
if (Object.prototype.hasOwnProperty.call(_exportNames, key)) return;
|
|
532
|
+
if (key in exports && exports[key] === _preferredMfaMethod[key]) return;
|
|
533
|
+
Object.defineProperty(exports, key, {
|
|
534
|
+
enumerable: true,
|
|
535
|
+
get: function () {
|
|
536
|
+
return _preferredMfaMethod[key];
|
|
537
|
+
}
|
|
538
|
+
});
|
|
539
|
+
});
|
|
528
540
|
var _tenantSelect = require("./LoginState/helpers/tenantSelect");
|
|
529
541
|
Object.keys(_tenantSelect).forEach(function (key) {
|
|
530
542
|
if (key === "default" || key === "__esModule") return;
|
package/node/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frontegg/redux-store",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.123.0-alpha.0",
|
|
4
4
|
"main": "./node/index.js",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Frontegg LTD",
|
|
7
7
|
"dependencies": {
|
|
8
8
|
"@babel/runtime": "^7.18.6",
|
|
9
9
|
"@frontegg/entitlements-javascript-commons": "1.1.2",
|
|
10
|
-
"@frontegg/rest-api": "7.
|
|
10
|
+
"@frontegg/rest-api": "7.123.0-alpha.0",
|
|
11
11
|
"fast-deep-equal": "3.1.3",
|
|
12
12
|
"get-value": "^3.0.1",
|
|
13
13
|
"proxy-compare": "^3.0.0",
|