@frontegg/redux-store 7.120.0 → 7.122.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.
@@ -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
- const numberOfWebAuthnDevices = +Boolean(mfaDevices.webauthn.length);
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
+ };
@@ -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;
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
@@ -1,4 +1,4 @@
1
- /** @license Frontegg v7.120.0
1
+ /** @license Frontegg v7.122.0
2
2
  *
3
3
  * This source code is licensed under the MIT license found in the
4
4
  * LICENSE file in the root directory of this source tree.
@@ -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
- const numberOfWebAuthnDevices = +Boolean(mfaDevices.webauthn.length);
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({
@@ -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
@@ -1,4 +1,4 @@
1
- /** @license Frontegg v7.120.0
1
+ /** @license Frontegg v7.122.0
2
2
  *
3
3
  * This source code is licensed under the MIT license found in the
4
4
  * LICENSE file in the root directory of this source tree.
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@frontegg/redux-store",
3
- "version": "7.120.0",
3
+ "version": "7.122.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.120.0",
10
+ "@frontegg/rest-api": "7.122.0",
11
11
  "fast-deep-equal": "3.1.3",
12
12
  "get-value": "^3.0.1",
13
13
  "proxy-compare": "^3.0.0",