@frontegg/redux-store 7.129.0-alpha.0 → 7.129.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.
@@ -1,8 +1,7 @@
1
1
  import _extends from "@babel/runtime/helpers/esm/extends";
2
- import { ENFORCE_SSO_ON_TENANT_ACCESS_FF, FeatureFlags } from '@frontegg/rest-api';
3
2
  import { getPathAndSearchParamsFromUrl, getRedirectUrl, isAbsoluteUrl } from '../helpers';
4
3
  import { shouldPromptChooseTenant } from '../helpers/tenantSelect';
5
- import { FRONTEGG_AFTER_AUTH_REDIRECT_URL, FRONTEGG_PENDING_SWITCH_TENANT } from '../../../constants';
4
+ import { FRONTEGG_AFTER_AUTH_REDIRECT_URL } from '../../../constants';
6
5
  import { LoginStep } from '../interfaces';
7
6
  import { SHOULD_STEP_UP_KEY } from '../../StepUpState/consts';
8
7
  import { delay } from '../../../helpers';
@@ -57,33 +56,6 @@ export default ((store, api, sharedActions) => {
57
56
  }, additionalUpdate));
58
57
  }
59
58
 
60
- /**
61
- * A switch that needed SSO saved its target tenant (in this tab) before sending the user to SSO.
62
- * Once logged in again, switch to it once if the login landed in another tenant.
63
- * @returns true when the switch left the user without a session (MFA step for the target tenant, or logout)
64
- */
65
- async function switchToPendingSsoTenant() {
66
- var _store$auth$user, _store$auth$user2;
67
- const [isEnforceSsoOnTenantAccessEnabled] = FeatureFlags.getFeatureFlags([ENFORCE_SSO_ON_TENANT_ACCESS_FF], store.root.appName);
68
- const pendingTenantId = window.sessionStorage.getItem(FRONTEGG_PENDING_SWITCH_TENANT);
69
- if (!isEnforceSsoOnTenantAccessEnabled || !pendingTenantId || !((_store$auth$user = store.auth.user) != null && _store$auth$user.accessToken)) {
70
- return false;
71
- }
72
- if (store.auth.user.tenantId === pendingTenantId) {
73
- window.sessionStorage.removeItem(FRONTEGG_PENDING_SWITCH_TENANT);
74
- return false;
75
- }
76
- // silentReload keeps switchingTenant set, so the switch's own refresh doesn't re-run this navigation
77
- await actions.switchTenant({
78
- tenantId: pendingTenantId,
79
- silentReload: true,
80
- keepLoginState: true
81
- });
82
- // removed only after the switch: a refused re-run must not send the user to SSO again
83
- window.sessionStorage.removeItem(FRONTEGG_PENDING_SWITCH_TENANT);
84
- return !((_store$auth$user2 = store.auth.user) != null && _store$auth$user2.accessToken);
85
- }
86
-
87
59
  /**
88
60
  * @param customLoginAuthenticatedUrl custom login authenticated url if exists
89
61
  * @returns the authenticated url to redirect to after auth navigation
@@ -108,15 +80,19 @@ export default ((store, api, sharedActions) => {
108
80
  logoutUrl,
109
81
  socialLoginCallbackUrl,
110
82
  activateUrl,
111
- activateWithOTCUrl
83
+ activateWithOTCUrl,
84
+ oidcRedirectUrl
112
85
  } = routes;
113
86
  let finalUrl = urlFromLocalStorage;
114
87
  if (!finalUrl || [loginUrl, logoutUrl, socialLoginCallbackUrl, activateUrl, activateWithOTCUrl].includes(finalUrl)) {
115
88
  finalUrl = authenticatedUrl;
116
89
  }
90
+
91
+ // The OIDC callback's query is the IdP's code and state; the user's own query was saved with the URL.
92
+ const isOidcCallback = !!oidcRedirectUrl && window.location.pathname.endsWith(oidcRedirectUrl);
117
93
  const redirectUrl = getRedirectUrl({
118
94
  authenticatedUrl: finalUrl,
119
- includeQueryParam,
95
+ includeQueryParam: includeQueryParam && !isOidcCallback,
120
96
  enforceRedirectToSameSite,
121
97
  allowedRedirectOrigins
122
98
  });
@@ -187,7 +163,7 @@ export default ((store, api, sharedActions) => {
187
163
  * until the resolved-guard, the LD flag, and the option all pass.
188
164
  */
189
165
  const shouldRouteToChooseTenant = async () => {
190
- var _store$auth$tenantSel, _store$auth$user3, _store$auth$user3$act, _store$auth$tenantsSt;
166
+ var _store$auth$tenantSel, _store$auth$user, _store$auth$user$act, _store$auth$tenantsSt;
191
167
  const loginState = store.auth.loginState;
192
168
  // Guard first so re-entry after the choice is resolved finalizes without re-reading the flag.
193
169
  if (loginState.tenantChoiceResolved) {
@@ -203,7 +179,7 @@ export default ((store, api, sharedActions) => {
203
179
  return false; // feature off — never read tenant data
204
180
  }
205
181
 
206
- const isImpersonation = !!((_store$auth$user3 = store.auth.user) != null && (_store$auth$user3$act = _store$auth$user3.act) != null && _store$auth$user3$act.sub);
182
+ const isImpersonation = !!((_store$auth$user = store.auth.user) != null && (_store$auth$user$act = _store$auth$user.act) != null && _store$auth$user$act.sub);
207
183
  if (isImpersonation) {
208
184
  return false;
209
185
  }
@@ -281,14 +257,6 @@ export default ((store, api, sharedActions) => {
281
257
  }
282
258
  try {
283
259
  var _window4, _payload$preventRedir;
284
- const isSessionLostInPendingSwitch = await switchToPendingSsoTenant();
285
- if (isSessionLostInPendingSwitch) {
286
- // MFA for the target tenant or a failed refresh: continue on the login page
287
- store.auth.onRedirectTo(store.auth.routes.loginUrl, {
288
- preserveQueryParams: true
289
- });
290
- return;
291
- }
292
260
  const {
293
261
  customLoginAuthenticatedUrl,
294
262
  stepUpUrl
@@ -1,7 +1,6 @@
1
1
  import _extends from "@babel/runtime/helpers/esm/extends";
2
2
  import { createRandomString, generateCodeChallenge, GTMEventAction, refreshTokenWithRetry, reportGTMEvent } from '../../../helpers';
3
3
  import { HOSTED_LOGIN_VERIFIER_KEY } from '../../../constants';
4
- import { ENFORCE_SSO_ON_TENANT_ACCESS_FF, FeatureFlags } from '@frontegg/rest-api';
5
4
  import { getBaseNameWithoutSlashSuffix, getSearchParam, isOauthCallbackRoute, TENANT_ID_PARAM_KEY } from '../helpers';
6
5
  import { getUri, getUrlDerivedStepUp, isSteppedUp, redirectByStepUpUrl } from '../../helpers';
7
6
  import { SHOULD_STEP_UP_KEY } from '../../StepUpState/consts';
@@ -80,10 +79,7 @@ export default ((store, api, sharedActions) => {
80
79
  code_challenge_method: 'S256',
81
80
  nonce
82
81
  }, additionalParams);
83
- const [isEnforceSsoOnTenantAccessEnabled] = FeatureFlags.getFeatureFlags([ENFORCE_SSO_ON_TENANT_ACCESS_FF], store.root.appName);
84
- // the tenant asked for (SSO for a refused switch or refresh) wins over the resolved and the page's tenant
85
- const isTenantRequested = isEnforceSsoOnTenantAccessEnabled && !!(additionalParams != null && additionalParams.tenantId);
86
- if (context.tenantResolver && !isTenantRequested) {
82
+ if (context.tenantResolver) {
87
83
  var _context$tenantResolv;
88
84
  const resolvedTenantResult = await ((_context$tenantResolv = context.tenantResolver) == null ? void 0 : _context$tenantResolv.call(context));
89
85
  if (resolvedTenantResult != null && resolvedTenantResult.tenant) {
@@ -91,7 +87,7 @@ export default ((store, api, sharedActions) => {
91
87
  }
92
88
  }
93
89
  const tenantId = getSearchParam(TENANT_ID_PARAM_KEY);
94
- if (tenantId && !isTenantRequested) {
90
+ if (tenantId) {
95
91
  params['tenantId'] = tenantId;
96
92
  }
97
93
  if (FronteggNativeModule.isAvailable('closeWindow')) {
@@ -75,9 +75,5 @@ declare const _default: (store: FronteggState, api: RestApi, sharedActions: Shar
75
75
  resetBreachedPassword: (payload: IForgotPassword) => Promise<void>;
76
76
  refreshTokenForSocialLogins: () => Promise<void>;
77
77
  __refreshToken: () => Promise<void>;
78
- __redirectToSsoForTenant: ({ tenantId, address }: {
79
- tenantId: string;
80
- address: string;
81
- }) => Promise<void>;
82
78
  };
83
79
  export default _default;
@@ -23,85 +23,28 @@ import mfaWithEmailCodeActions from './mfaWithEmailCode.actions';
23
23
  import mfaWithSMSActions from './mfaWithSMS.actions';
24
24
  import mfaWithWebAuthnActions from './mfaWithWebAuthn.actions';
25
25
  import { LoginFlow, LoginStep } from '../interfaces';
26
- import { base64urlDecode, deepResetState, delay, errorHandler, errorTraceId, GTMEventAction, isSsoEnforcementError, publicKeyCredentialToJSON, refreshTokenWithRetry, reportGTMEvent, retryIfNeeded, withRetryConfig } from '../../../helpers';
26
+ import { base64urlDecode, deepResetState, delay, errorHandler, errorTraceId, GTMEventAction, publicKeyCredentialToJSON, refreshTokenWithRetry, reportGTMEvent, retryIfNeeded, withRetryConfig } from '../../../helpers';
27
27
  import { initialState } from '../state';
28
28
  import { getSearchParam, isEmailPayload, isUsernamePayload, shouldShowPasswordRotationPromptFunc, TENANT_ID_PARAM_KEY } from '../helpers';
29
- import { AuthStrategyEnum, ContextHolder, ENFORCE_SSO_ON_TENANT_ACCESS_FF, FeatureFlags, getTabTenantFromSessionStorage, removeTabTenantFromSessionStorage, setTabTenantInSessionStorage, WebAuthnDeviceType, PreLoginAddressType } from '@frontegg/rest-api';
29
+ import { AuthStrategyEnum, ContextHolder, FeatureFlags, getTabTenantFromSessionStorage, removeTabTenantFromSessionStorage, WebAuthnDeviceType, PreLoginAddressType } from '@frontegg/rest-api';
30
30
  import hostedLoginAuthorizeActions from './hostedLoginAuthorize.actions';
31
31
  import { clearHostedOAuthState } from '../oauthStorage';
32
32
  import { SHOULD_STEP_UP_KEY } from '../../StepUpState/consts';
33
33
  import { FronteggNativeModule, isEntitlementsDeeplyEqual } from '../../../toolkit';
34
34
  import { REQUEST_NAME, UserVerifiedOriginTypes } from '../../interfaces';
35
- import { authStrategyLoginStepMap } from '../consts';
36
- import { isMfaRequired, isResetPasswordRequired, setAfterAuthRedirectUrlForStepUp } from '../../helpers';
35
+ import { authStrategyLoginStepMap, defaultOnRedirectTo } from '../consts';
36
+ import { isMfaRequired, isResetPasswordRequired } from '../../helpers';
37
37
  import { MFAStep } from '../../MfaState/interfaces';
38
38
  import { SamlVendors } from '../../SSOState/interfaces';
39
- import { DEFAULT_RETRY_CONFIG, FRONTEGG_PENDING_SWITCH_TENANT } from '../../../constants';
39
+ import { DEFAULT_RETRY_CONFIG } from '../../../constants';
40
+ const SSO_REDIRECT_DELAY_MS = 2000;
41
+ const isUsableRedirect = onRedirectTo => typeof onRedirectTo === 'function' && onRedirectTo !== defaultOnRedirectTo;
40
42
  export default ((store, api, sharedActions) => {
41
43
  const actions = sharedActions;
42
44
  const contextHolder = ContextHolder.for(store.root.appName);
43
45
 
44
- /**
45
- * Sends the user to log in through the tenant's SSO and back to the current page afterwards.
46
- * @protected
47
- */
48
- const __redirectToSsoForTenant = async ({
49
- tenantId,
50
- address
51
- }) => {
52
- // on the login page (tenant chooser) keep the destination the login already saved
53
- if (!window.location.pathname.endsWith(store.auth.routes.loginUrl)) {
54
- setAfterAuthRedirectUrlForStepUp();
55
- }
56
- if (store.auth.hostedLoginBox) {
57
- await actions.requestHostedLoginAuthorize({
58
- prompt: 'login',
59
- tenantId
60
- });
61
- return;
62
- }
63
- store.auth.onRedirectTo(address, {
64
- refresh: true
65
- });
66
- };
67
- /**
68
- * Handles a token refresh refused because the tenant requires SSO.
69
- * ssoRequired: true sends the user to SSO. ssoRequired: false renews once more for the tenant of the
70
- * current token, which keeps the session.
71
- * @returns true when handled, false when the caller should log out
72
- * @private
73
- */
74
- const __handleSsoEnforcementRefreshError = async (e, isSsoEnforcementRetry, refreshForTenant) => {
75
- var _store$auth$user;
76
- const [isEnforceSsoOnTenantAccessEnabled] = FeatureFlags.getFeatureFlags([ENFORCE_SSO_ON_TENANT_ACCESS_FF], store.root.appName);
77
- if (!isEnforceSsoOnTenantAccessEnabled || !isSsoEnforcementError(e)) {
78
- return false;
79
- }
80
- const {
81
- errorContext
82
- } = e;
83
- // the switch re-run after coming back from SSO keeps the session instead of sending the user to SSO again
84
- const isPendingSwitchRerun = !!window.sessionStorage.getItem(FRONTEGG_PENDING_SWITCH_TENANT);
85
- if (errorContext.ssoRequired && !isPendingSwitchRerun) {
86
- await __redirectToSsoForTenant(errorContext);
87
- return true;
88
- }
89
- if (isSsoEnforcementRetry) {
90
- return false;
91
- }
92
- const currentTenantId = (_store$auth$user = store.auth.user) == null ? void 0 : _store$auth$user.tenantId;
93
- if (currentTenantId && contextHolder.isSessionPerTenantEnabled()) {
94
- setTabTenantInSessionStorage(currentTenantId);
95
- }
96
- await refreshForTenant(currentTenantId);
97
- return true;
98
- };
99
-
100
46
  /** @private */
101
- const __refreshTokenHosted = async ({
102
- tenantId,
103
- isSsoEnforcementRetry = false
104
- } = {}) => {
47
+ const __refreshTokenHosted = async () => {
105
48
  const user = store.auth.user;
106
49
  if (!(user != null && user.refreshToken)) {
107
50
  contextHolder.setAccessToken(null);
@@ -113,7 +56,7 @@ export default ((store, api, sharedActions) => {
113
56
  return;
114
57
  }
115
58
  try {
116
- const tabTenantId = tenantId != null ? tenantId : getTabTenantFromSessionStorage(store.root.appName);
59
+ const tabTenantId = getTabTenantFromSessionStorage(store.root.appName);
117
60
  const body = _extends({
118
61
  grant_type: 'refresh_token',
119
62
  refresh_token: user == null ? void 0 : user.refreshToken
@@ -128,13 +71,6 @@ export default ((store, api, sharedActions) => {
128
71
  isAuthenticated: true
129
72
  });
130
73
  } catch (e) {
131
- const retry = tenantId => __refreshTokenHosted({
132
- tenantId,
133
- isSsoEnforcementRetry: true
134
- });
135
- if (await __handleSsoEnforcementRefreshError(e, isSsoEnforcementRetry, retry)) {
136
- return;
137
- }
138
74
  contextHolder.setAccessToken(null);
139
75
  contextHolder.setUser(null);
140
76
  actions.setAuthState({
@@ -154,10 +90,7 @@ export default ((store, api, sharedActions) => {
154
90
  };
155
91
 
156
92
  /** @private */
157
- const __refreshTokenEmbedded = async ({
158
- tenantId,
159
- isSsoEnforcementRetry = false
160
- } = {}) => {
93
+ const __refreshTokenEmbedded = async () => {
161
94
  try {
162
95
  const {
163
96
  routes,
@@ -168,7 +101,7 @@ export default ((store, api, sharedActions) => {
168
101
  user,
169
102
  tenants = [],
170
103
  activeTenant
171
- } = await refreshTokenWithRetry(() => api.auth.refreshTokenV3(tenantId));
104
+ } = await refreshTokenWithRetry(() => api.auth.refreshTokenV3());
172
105
  if (isMfaRequired(user, store.root.appName)) {
173
106
  const mfaRequiredState = await actions.getMfaRequiredState(user);
174
107
  actions.setAuthState(mfaRequiredState);
@@ -199,13 +132,6 @@ export default ((store, api, sharedActions) => {
199
132
  });
200
133
  }
201
134
  } catch (e) {
202
- const retry = tenantId => __refreshTokenEmbedded({
203
- tenantId,
204
- isSsoEnforcementRetry: true
205
- });
206
- if (await __handleSsoEnforcementRefreshError(e, isSsoEnforcementRetry, retry)) {
207
- return;
208
- }
209
135
  contextHolder.setAccessToken(null);
210
136
  contextHolder.setUser(null);
211
137
  actions.setAuthState({
@@ -216,9 +142,7 @@ export default ((store, api, sharedActions) => {
216
142
  };
217
143
 
218
144
  /** @private */
219
- const __refreshTokenWithNativeTokens = async ({
220
- isSsoEnforcementRetry = false
221
- } = {}) => {
145
+ const __refreshTokenWithNativeTokens = async () => {
222
146
  try {
223
147
  const authApi = api.auth;
224
148
  const response = await refreshTokenWithRetry(async () => {
@@ -232,13 +156,6 @@ export default ((store, api, sharedActions) => {
232
156
  isAuthenticated: true
233
157
  });
234
158
  } catch (e) {
235
- // native tokens can't name a tenant: the retry relies on the tab tenant only
236
- const retry = () => __refreshTokenWithNativeTokens({
237
- isSsoEnforcementRetry: true
238
- });
239
- if (await __handleSsoEnforcementRefreshError(e, isSsoEnforcementRetry, retry)) {
240
- return;
241
- }
242
159
  contextHolder.setAccessToken(null);
243
160
  contextHolder.setUser(null);
244
161
  actions.setAuthState({
@@ -272,9 +189,9 @@ export default ((store, api, sharedActions) => {
272
189
  * @private
273
190
  */
274
191
  const __handleUnnecessaryEntitlementsUpdate = async updatedUser => {
275
- var _store$auth$user2;
192
+ var _store$auth$user;
276
193
  // @ts-ignore TODO: fix entitlements state type
277
- const oldEntitlements = (_store$auth$user2 = store.auth.user) == null ? void 0 : _store$auth$user2.entitlements;
194
+ const oldEntitlements = (_store$auth$user = store.auth.user) == null ? void 0 : _store$auth$user.entitlements;
278
195
  if (isEntitlementsDeeplyEqual(oldEntitlements, updatedUser == null ? void 0 : updatedUser.entitlements)) {
279
196
  // set the previous entitlements object
280
197
  return _extends({}, updatedUser, {
@@ -472,6 +389,29 @@ export default ((store, api, sharedActions) => {
472
389
  callback == null ? void 0 : callback();
473
390
  }
474
391
  };
392
+
393
+ // v6 parity (takeLeading): a preLogin called while another is in flight is dropped.
394
+ let preLoginInFlight = false;
395
+ let latestPreLoginAttempt = 0;
396
+
397
+ // Read on use: the login box installs onRedirectTo lazily, logout() clears it and the store default is a no-op.
398
+ const redirectToFullPage = url => {
399
+ const onRedirectTo = [store.auth.onRedirectTo, contextHolder.onRedirectTo].find(isUsableRedirect);
400
+ if (onRedirectTo) {
401
+ onRedirectTo(url, {
402
+ refresh: true
403
+ });
404
+ } else {
405
+ window.location.href = url;
406
+ }
407
+ };
408
+ const scheduleSsoRedirect = (ssoRedirectUrl, attempt) => {
409
+ setTimeout(() => {
410
+ if (attempt === latestPreLoginAttempt && store.auth.loginState.step === LoginStep.redirectToSSO) {
411
+ redirectToFullPage(ssoRedirectUrl);
412
+ }
413
+ }, SSO_REDIRECT_DELAY_MS);
414
+ };
475
415
  const preLogin = async payload => {
476
416
  const {
477
417
  email,
@@ -480,11 +420,17 @@ export default ((store, api, sharedActions) => {
480
420
  invitationToken,
481
421
  callback
482
422
  } = payload;
423
+ if (preLoginInFlight) {
424
+ callback == null ? void 0 : callback();
425
+ return;
426
+ }
427
+ preLoginInFlight = true;
428
+ latestPreLoginAttempt += 1;
429
+ const attempt = latestPreLoginAttempt;
483
430
  setLoginState({
484
431
  loading: true
485
432
  });
486
433
  try {
487
- const onRedirectTo = store.auth.onRedirectTo;
488
434
  const tenantId = getSearchParam(TENANT_ID_PARAM_KEY);
489
435
  const preLoginResult = await api.auth.preLoginV2(_extends({}, email ? {
490
436
  email
@@ -505,9 +451,7 @@ export default ((store, api, sharedActions) => {
505
451
  setLoginState({
506
452
  loading: false
507
453
  });
508
- onRedirectTo(address, {
509
- refresh: true
510
- });
454
+ redirectToFullPage(address);
511
455
  return;
512
456
  }
513
457
  let ssoRedirectUrl = address;
@@ -530,11 +474,7 @@ export default ((store, api, sharedActions) => {
530
474
  loading: false,
531
475
  ssoRedirectUrl
532
476
  });
533
- setTimeout(() => {
534
- onRedirectTo(ssoRedirectUrl, {
535
- refresh: true
536
- });
537
- }, 2000);
477
+ scheduleSsoRedirect(ssoRedirectUrl, attempt);
538
478
  }
539
479
  } else {
540
480
  await ssoPreloginFailed(_extends({}, username ? {
@@ -558,6 +498,8 @@ export default ((store, api, sharedActions) => {
558
498
  invitationToken,
559
499
  error: e
560
500
  }));
501
+ } finally {
502
+ preLoginInFlight = false;
561
503
  }
562
504
  };
563
505
  const postLogin = async payload => {
@@ -744,7 +686,6 @@ export default ((store, api, sharedActions) => {
744
686
  if (!keepHostedOAuthState) {
745
687
  clearHostedOAuthState();
746
688
  }
747
- window.sessionStorage.removeItem(FRONTEGG_PENDING_SWITCH_TENANT);
748
689
  };
749
690
  const logout = async payload => {
750
691
  const hostedLoginBox = store.auth.hostedLoginBox;
@@ -1447,10 +1388,7 @@ export default ((store, api, sharedActions) => {
1447
1388
  };
1448
1389
 
1449
1390
  /** @private */
1450
- const __refreshTokenHostedSocialLogins = async ({
1451
- tenantId,
1452
- isSsoEnforcementRetry = false
1453
- } = {}) => {
1391
+ const __refreshTokenHostedSocialLogins = async () => {
1454
1392
  const {
1455
1393
  user: currentUser
1456
1394
  } = store.auth;
@@ -1464,7 +1402,7 @@ export default ((store, api, sharedActions) => {
1464
1402
  return;
1465
1403
  }
1466
1404
  try {
1467
- const tabTenantId = tenantId != null ? tenantId : getTabTenantFromSessionStorage(store.root.appName);
1405
+ const tabTenantId = getTabTenantFromSessionStorage(store.root.appName);
1468
1406
  const body = _extends({
1469
1407
  grant_type: 'refresh_token',
1470
1408
  refresh_token: currentUser == null ? void 0 : currentUser.refreshToken
@@ -1479,13 +1417,6 @@ export default ((store, api, sharedActions) => {
1479
1417
  isAuthenticated: true
1480
1418
  });
1481
1419
  } catch (e) {
1482
- const retry = tenantId => __refreshTokenHostedSocialLogins({
1483
- tenantId,
1484
- isSsoEnforcementRetry: true
1485
- });
1486
- if (await __handleSsoEnforcementRefreshError(e, isSsoEnforcementRetry, retry)) {
1487
- return;
1488
- }
1489
1420
  contextHolder.setAccessToken(null);
1490
1421
  contextHolder.setUser(null);
1491
1422
  actions.setAuthState({
@@ -1495,10 +1426,7 @@ export default ((store, api, sharedActions) => {
1495
1426
  }
1496
1427
  };
1497
1428
  /** @private */
1498
- const __refreshTokenEmbeddedSocialLogins = async ({
1499
- tenantId,
1500
- isSsoEnforcementRetry = false
1501
- } = {}) => {
1429
+ const __refreshTokenEmbeddedSocialLogins = async () => {
1502
1430
  try {
1503
1431
  const {
1504
1432
  routes,
@@ -1509,7 +1437,7 @@ export default ((store, api, sharedActions) => {
1509
1437
  user,
1510
1438
  tenants = [],
1511
1439
  activeTenant
1512
- } = await refreshTokenWithRetry(() => api.auth.refreshTokenV3(tenantId));
1440
+ } = await refreshTokenWithRetry(() => api.auth.refreshTokenV3());
1513
1441
  if (isMfaRequired(user, store.root.appName)) {
1514
1442
  const mfaRequiredState = await actions.getMfaRequiredState(user);
1515
1443
  actions.setAuthState(mfaRequiredState);
@@ -1559,13 +1487,6 @@ export default ((store, api, sharedActions) => {
1559
1487
  }
1560
1488
  }
1561
1489
  } catch (e) {
1562
- const retry = tenantId => __refreshTokenEmbeddedSocialLogins({
1563
- tenantId,
1564
- isSsoEnforcementRetry: true
1565
- });
1566
- if (await __handleSsoEnforcementRefreshError(e, isSsoEnforcementRetry, retry)) {
1567
- return;
1568
- }
1569
1490
  contextHolder.setAccessToken(null);
1570
1491
  contextHolder.setUser(null);
1571
1492
  actions.setAuthState({
@@ -1612,7 +1533,6 @@ export default ((store, api, sharedActions) => {
1612
1533
  resetBreachedPassword,
1613
1534
  refreshTokenForSocialLogins,
1614
1535
  // protected
1615
- __refreshToken,
1616
- __redirectToSsoForTenant
1536
+ __refreshToken
1617
1537
  }, hostedLoginAuthorizeActions(store, api, sharedActions), afterAuthNavigationActions(store, api, sharedActions), handleVerifyMFAResponseActions(store, api, sharedActions), mfaRequiredStateActions(store, api, sharedActions), mfaWithAuthenticatorActions(store, api, sharedActions), mfaWithEmailCodeActions(store, api, sharedActions), mfaWithSMSActions(store, api, sharedActions), mfaWithWebAuthnActions(store, api, sharedActions));
1618
1538
  });
@@ -6,5 +6,6 @@
6
6
  import { AuthStrategyEnum } from '@frontegg/rest-api';
7
7
  import { LoginStep } from './interfaces';
8
8
  import { AuthPageRoutes } from '../interfaces';
9
+ export declare const defaultOnRedirectTo: () => undefined;
9
10
  export declare const authStrategyLoginStepMap: Record<Exclude<AuthStrategyEnum, AuthStrategyEnum.NoLocalAuthentication | AuthStrategyEnum.WebAuthN>, LoginStep>;
10
11
  export declare const defaultFronteggRoutes: AuthPageRoutes;
@@ -5,6 +5,7 @@
5
5
  */
6
6
  import { AuthStrategyEnum } from '@frontegg/rest-api';
7
7
  import { LoginStep } from './interfaces';
8
+ export const defaultOnRedirectTo = () => void 0;
8
9
  export const authStrategyLoginStepMap = {
9
10
  [AuthStrategyEnum.Code]: LoginStep.loginWithOtc,
10
11
  [AuthStrategyEnum.EmailAndPassword]: LoginStep.loginWithPassword,
@@ -1,7 +1,6 @@
1
1
  import { initialState } from './state';
2
- import { deepResetState, isSsoEnforcementError } from '../../helpers';
3
- import { ENFORCE_SSO_ON_TENANT_ACCESS_FF, FeatureFlags, getCurrentUserTenantsFunction } from '@frontegg/rest-api';
4
- import { FRONTEGG_PENDING_SWITCH_TENANT } from '../../constants';
2
+ import { deepResetState } from '../../helpers';
3
+ import { getCurrentUserTenantsFunction } from '@frontegg/rest-api';
5
4
  export default ((store, api, sharedActions) => {
6
5
  const actions = sharedActions;
7
6
  const setTenantsState = state => {
@@ -52,15 +51,6 @@ export default ((store, api, sharedActions) => {
52
51
  }) : actions.setAuthState({
53
52
  isLoading: false
54
53
  });
55
- const [isEnforceSsoOnTenantAccessEnabled] = FeatureFlags.getFeatureFlags([ENFORCE_SSO_ON_TENANT_ACCESS_FF], store.root.appName);
56
- // a switch re-run after coming back from SSO doesn't send the user to SSO again
57
- const isPendingSwitchRerun = window.sessionStorage.getItem(FRONTEGG_PENDING_SWITCH_TENANT) === tenantId;
58
- if (isEnforceSsoOnTenantAccessEnabled && isSsoEnforcementError(e) && e.errorContext.ssoRequired && !isPendingSwitchRerun) {
59
- // saved to finish the switch after the SSO login, which may land in another tenant
60
- window.sessionStorage.setItem(FRONTEGG_PENDING_SWITCH_TENANT, tenantId);
61
- await actions.__redirectToSsoForTenant(e.errorContext);
62
- return;
63
- }
64
54
  callback == null ? void 0 : callback(false, e);
65
55
  }
66
56
  };
package/auth/index.js CHANGED
@@ -37,7 +37,7 @@ import { buildTeamActions, createTeamState } from './TeamState';
37
37
  import { buildTenantsActions, createTenantsState } from './TenantsState';
38
38
  import { buildUsernamesActions, createUsernamesState } from './UsernamesState';
39
39
  import { buildUserEmailPolicyActions, createUserEmailPolicyState } from './UsersEmailsPolicyState';
40
- import { defaultFronteggRoutes } from './LoginState/consts';
40
+ import { defaultFronteggRoutes, defaultOnRedirectTo } from './LoginState/consts';
41
41
  import { deepResetState, isProxy } from '../helpers';
42
42
  import { buildEntitlementsActions } from './Entitlements';
43
43
  import { createProxy } from '../toolkit/proxy';
@@ -91,7 +91,7 @@ export const createAuthState = _overrideState => {
91
91
  overrideState = _objectWithoutPropertiesLoose(_ref, _excluded);
92
92
  return createProxy(_extends({
93
93
  routes: _extends({}, defaultFronteggRoutes, routes),
94
- onRedirectTo: () => void 0,
94
+ onRedirectTo: defaultOnRedirectTo,
95
95
  isAuthenticated: false,
96
96
  isLoading: true,
97
97
  isSSOAuth: false,
package/constants.d.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  export declare const FRONTEGG_AFTER_AUTH_REDIRECT_URL = "FRONTEGG_AFTER_AUTH_REDIRECT_URL";
2
- export declare const FRONTEGG_PENDING_SWITCH_TENANT = "FRONTEGG_PENDING_SWITCH_TENANT";
3
2
  export declare const HOSTED_LOGIN_VERIFIER_KEY = "HOSTED_LOGIN_VERIFIER_KEY";
4
3
  export declare const DEFAULT_RETRY_CONFIG: {
5
4
  retryCount: number;
package/constants.js CHANGED
@@ -1,5 +1,4 @@
1
1
  export const FRONTEGG_AFTER_AUTH_REDIRECT_URL = 'FRONTEGG_AFTER_AUTH_REDIRECT_URL';
2
- export const FRONTEGG_PENDING_SWITCH_TENANT = 'FRONTEGG_PENDING_SWITCH_TENANT';
3
2
  export const HOSTED_LOGIN_VERIFIER_KEY = 'HOSTED_LOGIN_VERIFIER_KEY';
4
3
  export const DEFAULT_RETRY_CONFIG = {
5
4
  retryCount: 3
@@ -1,7 +1,4 @@
1
- import { FronteggApiError, SsoEnforcementErrorContext } from '@frontegg/rest-api';
1
+ import { FronteggApiError } from '@frontegg/rest-api';
2
2
  export declare const isFronteggApiError: (error: unknown | Error | FronteggApiError) => error is FronteggApiError;
3
- export declare const isSsoEnforcementError: (error: unknown) => error is FronteggApiError & {
4
- errorContext: SsoEnforcementErrorContext;
5
- };
6
3
  export declare const isError: (error: any) => error is Error;
7
4
  export declare const errorHandler: <T>(error: unknown, fallback?: T) => FronteggApiError | T;
@@ -1,8 +1,5 @@
1
- import { FronteggApiError, GENERIC_ERROR_MESSAGE, GENERIC_ERROR_CODE, SSO_REQUIRED_FOR_TENANT_ERROR_CODE } from '@frontegg/rest-api';
1
+ import { FronteggApiError, GENERIC_ERROR_MESSAGE, GENERIC_ERROR_CODE } from '@frontegg/rest-api';
2
2
  export const isFronteggApiError = error => Boolean(error == null ? void 0 : error.statusCode);
3
-
4
- // ER-01079 (disabled user / expired tenant) is not this: it still logs out
5
- export const isSsoEnforcementError = error => isFronteggApiError(error) && error.errorCode === SSO_REQUIRED_FOR_TENANT_ERROR_CODE;
6
3
  export const isError = error => {
7
4
  if (error instanceof Error) {
8
5
  return true;
package/index.js CHANGED
@@ -1,4 +1,4 @@
1
- /** @license Frontegg v7.129.0-alpha.0
1
+ /** @license Frontegg v7.129.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.
@@ -71,9 +71,5 @@ declare const _default: (store: FronteggState, api: RestApi, sharedActions: Shar
71
71
  resetBreachedPassword: (payload: import("dist/@frontegg/rest-api").IForgotPassword) => Promise<void>;
72
72
  refreshTokenForSocialLogins: () => Promise<void>;
73
73
  __refreshToken: () => Promise<void>;
74
- __redirectToSsoForTenant: ({ tenantId, address }: {
75
- tenantId: string;
76
- address: string;
77
- }) => Promise<void>;
78
74
  };
79
75
  export default _default;
@@ -6,7 +6,6 @@ Object.defineProperty(exports, "__esModule", {
6
6
  });
7
7
  exports.default = void 0;
8
8
  var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
9
- var _restApi = require("@frontegg/rest-api");
10
9
  var _helpers = require("../helpers");
11
10
  var _tenantSelect = require("../helpers/tenantSelect");
12
11
  var _constants = require("../../../constants");
@@ -64,33 +63,6 @@ var _default = (store, api, sharedActions) => {
64
63
  }, additionalUpdate));
65
64
  }
66
65
 
67
- /**
68
- * A switch that needed SSO saved its target tenant (in this tab) before sending the user to SSO.
69
- * Once logged in again, switch to it once if the login landed in another tenant.
70
- * @returns true when the switch left the user without a session (MFA step for the target tenant, or logout)
71
- */
72
- async function switchToPendingSsoTenant() {
73
- var _store$auth$user, _store$auth$user2;
74
- const [isEnforceSsoOnTenantAccessEnabled] = _restApi.FeatureFlags.getFeatureFlags([_restApi.ENFORCE_SSO_ON_TENANT_ACCESS_FF], store.root.appName);
75
- const pendingTenantId = window.sessionStorage.getItem(_constants.FRONTEGG_PENDING_SWITCH_TENANT);
76
- if (!isEnforceSsoOnTenantAccessEnabled || !pendingTenantId || !((_store$auth$user = store.auth.user) != null && _store$auth$user.accessToken)) {
77
- return false;
78
- }
79
- if (store.auth.user.tenantId === pendingTenantId) {
80
- window.sessionStorage.removeItem(_constants.FRONTEGG_PENDING_SWITCH_TENANT);
81
- return false;
82
- }
83
- // silentReload keeps switchingTenant set, so the switch's own refresh doesn't re-run this navigation
84
- await actions.switchTenant({
85
- tenantId: pendingTenantId,
86
- silentReload: true,
87
- keepLoginState: true
88
- });
89
- // removed only after the switch: a refused re-run must not send the user to SSO again
90
- window.sessionStorage.removeItem(_constants.FRONTEGG_PENDING_SWITCH_TENANT);
91
- return !((_store$auth$user2 = store.auth.user) != null && _store$auth$user2.accessToken);
92
- }
93
-
94
66
  /**
95
67
  * @param customLoginAuthenticatedUrl custom login authenticated url if exists
96
68
  * @returns the authenticated url to redirect to after auth navigation
@@ -115,15 +87,19 @@ var _default = (store, api, sharedActions) => {
115
87
  logoutUrl,
116
88
  socialLoginCallbackUrl,
117
89
  activateUrl,
118
- activateWithOTCUrl
90
+ activateWithOTCUrl,
91
+ oidcRedirectUrl
119
92
  } = routes;
120
93
  let finalUrl = urlFromLocalStorage;
121
94
  if (!finalUrl || [loginUrl, logoutUrl, socialLoginCallbackUrl, activateUrl, activateWithOTCUrl].includes(finalUrl)) {
122
95
  finalUrl = authenticatedUrl;
123
96
  }
97
+
98
+ // The OIDC callback's query is the IdP's code and state; the user's own query was saved with the URL.
99
+ const isOidcCallback = !!oidcRedirectUrl && window.location.pathname.endsWith(oidcRedirectUrl);
124
100
  const redirectUrl = (0, _helpers.getRedirectUrl)({
125
101
  authenticatedUrl: finalUrl,
126
- includeQueryParam,
102
+ includeQueryParam: includeQueryParam && !isOidcCallback,
127
103
  enforceRedirectToSameSite,
128
104
  allowedRedirectOrigins
129
105
  });
@@ -194,7 +170,7 @@ var _default = (store, api, sharedActions) => {
194
170
  * until the resolved-guard, the LD flag, and the option all pass.
195
171
  */
196
172
  const shouldRouteToChooseTenant = async () => {
197
- var _store$auth$tenantSel, _store$auth$user3, _store$auth$user3$act, _store$auth$tenantsSt;
173
+ var _store$auth$tenantSel, _store$auth$user, _store$auth$user$act, _store$auth$tenantsSt;
198
174
  const loginState = store.auth.loginState;
199
175
  // Guard first so re-entry after the choice is resolved finalizes without re-reading the flag.
200
176
  if (loginState.tenantChoiceResolved) {
@@ -210,7 +186,7 @@ var _default = (store, api, sharedActions) => {
210
186
  return false; // feature off — never read tenant data
211
187
  }
212
188
 
213
- const isImpersonation = !!((_store$auth$user3 = store.auth.user) != null && (_store$auth$user3$act = _store$auth$user3.act) != null && _store$auth$user3$act.sub);
189
+ const isImpersonation = !!((_store$auth$user = store.auth.user) != null && (_store$auth$user$act = _store$auth$user.act) != null && _store$auth$user$act.sub);
214
190
  if (isImpersonation) {
215
191
  return false;
216
192
  }
@@ -288,14 +264,6 @@ var _default = (store, api, sharedActions) => {
288
264
  }
289
265
  try {
290
266
  var _window4, _payload$preventRedir;
291
- const isSessionLostInPendingSwitch = await switchToPendingSsoTenant();
292
- if (isSessionLostInPendingSwitch) {
293
- // MFA for the target tenant or a failed refresh: continue on the login page
294
- store.auth.onRedirectTo(store.auth.routes.loginUrl, {
295
- preserveQueryParams: true
296
- });
297
- return;
298
- }
299
267
  const {
300
268
  customLoginAuthenticatedUrl,
301
269
  stepUpUrl
@@ -8,7 +8,6 @@ exports.default = void 0;
8
8
  var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
9
9
  var _helpers = require("../../../helpers");
10
10
  var _constants = require("../../../constants");
11
- var _restApi = require("@frontegg/rest-api");
12
11
  var _helpers2 = require("../helpers");
13
12
  var _helpers3 = require("../../helpers");
14
13
  var _consts = require("../../StepUpState/consts");
@@ -87,10 +86,7 @@ var _default = (store, api, sharedActions) => {
87
86
  code_challenge_method: 'S256',
88
87
  nonce
89
88
  }, additionalParams);
90
- const [isEnforceSsoOnTenantAccessEnabled] = _restApi.FeatureFlags.getFeatureFlags([_restApi.ENFORCE_SSO_ON_TENANT_ACCESS_FF], store.root.appName);
91
- // the tenant asked for (SSO for a refused switch or refresh) wins over the resolved and the page's tenant
92
- const isTenantRequested = isEnforceSsoOnTenantAccessEnabled && !!(additionalParams != null && additionalParams.tenantId);
93
- if (context.tenantResolver && !isTenantRequested) {
89
+ if (context.tenantResolver) {
94
90
  var _context$tenantResolv;
95
91
  const resolvedTenantResult = await ((_context$tenantResolv = context.tenantResolver) == null ? void 0 : _context$tenantResolv.call(context));
96
92
  if (resolvedTenantResult != null && resolvedTenantResult.tenant) {
@@ -98,7 +94,7 @@ var _default = (store, api, sharedActions) => {
98
94
  }
99
95
  }
100
96
  const tenantId = (0, _helpers2.getSearchParam)(_helpers2.TENANT_ID_PARAM_KEY);
101
- if (tenantId && !isTenantRequested) {
97
+ if (tenantId) {
102
98
  params['tenantId'] = tenantId;
103
99
  }
104
100
  if (_toolkit.FronteggNativeModule.isAvailable('closeWindow')) {
@@ -44,71 +44,14 @@ const _excluded = ["callback", "error"],
44
44
  _excluded13 = ["callback"],
45
45
  _excluded14 = ["callback"],
46
46
  _excluded15 = ["callback"];
47
+ const SSO_REDIRECT_DELAY_MS = 2000;
48
+ const isUsableRedirect = onRedirectTo => typeof onRedirectTo === 'function' && onRedirectTo !== _consts2.defaultOnRedirectTo;
47
49
  var _default = (store, api, sharedActions) => {
48
50
  const actions = sharedActions;
49
51
  const contextHolder = _restApi.ContextHolder.for(store.root.appName);
50
52
 
51
- /**
52
- * Sends the user to log in through the tenant's SSO and back to the current page afterwards.
53
- * @protected
54
- */
55
- const __redirectToSsoForTenant = async ({
56
- tenantId,
57
- address
58
- }) => {
59
- // on the login page (tenant chooser) keep the destination the login already saved
60
- if (!window.location.pathname.endsWith(store.auth.routes.loginUrl)) {
61
- (0, _helpers3.setAfterAuthRedirectUrlForStepUp)();
62
- }
63
- if (store.auth.hostedLoginBox) {
64
- await actions.requestHostedLoginAuthorize({
65
- prompt: 'login',
66
- tenantId
67
- });
68
- return;
69
- }
70
- store.auth.onRedirectTo(address, {
71
- refresh: true
72
- });
73
- };
74
- /**
75
- * Handles a token refresh refused because the tenant requires SSO.
76
- * ssoRequired: true sends the user to SSO. ssoRequired: false renews once more for the tenant of the
77
- * current token, which keeps the session.
78
- * @returns true when handled, false when the caller should log out
79
- * @private
80
- */
81
- const __handleSsoEnforcementRefreshError = async (e, isSsoEnforcementRetry, refreshForTenant) => {
82
- var _store$auth$user;
83
- const [isEnforceSsoOnTenantAccessEnabled] = _restApi.FeatureFlags.getFeatureFlags([_restApi.ENFORCE_SSO_ON_TENANT_ACCESS_FF], store.root.appName);
84
- if (!isEnforceSsoOnTenantAccessEnabled || !(0, _helpers.isSsoEnforcementError)(e)) {
85
- return false;
86
- }
87
- const {
88
- errorContext
89
- } = e;
90
- // the switch re-run after coming back from SSO keeps the session instead of sending the user to SSO again
91
- const isPendingSwitchRerun = !!window.sessionStorage.getItem(_constants.FRONTEGG_PENDING_SWITCH_TENANT);
92
- if (errorContext.ssoRequired && !isPendingSwitchRerun) {
93
- await __redirectToSsoForTenant(errorContext);
94
- return true;
95
- }
96
- if (isSsoEnforcementRetry) {
97
- return false;
98
- }
99
- const currentTenantId = (_store$auth$user = store.auth.user) == null ? void 0 : _store$auth$user.tenantId;
100
- if (currentTenantId && contextHolder.isSessionPerTenantEnabled()) {
101
- (0, _restApi.setTabTenantInSessionStorage)(currentTenantId);
102
- }
103
- await refreshForTenant(currentTenantId);
104
- return true;
105
- };
106
-
107
53
  /** @private */
108
- const __refreshTokenHosted = async ({
109
- tenantId,
110
- isSsoEnforcementRetry = false
111
- } = {}) => {
54
+ const __refreshTokenHosted = async () => {
112
55
  const user = store.auth.user;
113
56
  if (!(user != null && user.refreshToken)) {
114
57
  contextHolder.setAccessToken(null);
@@ -120,7 +63,7 @@ var _default = (store, api, sharedActions) => {
120
63
  return;
121
64
  }
122
65
  try {
123
- const tabTenantId = tenantId != null ? tenantId : (0, _restApi.getTabTenantFromSessionStorage)(store.root.appName);
66
+ const tabTenantId = (0, _restApi.getTabTenantFromSessionStorage)(store.root.appName);
124
67
  const body = (0, _extends2.default)({
125
68
  grant_type: 'refresh_token',
126
69
  refresh_token: user == null ? void 0 : user.refreshToken
@@ -135,13 +78,6 @@ var _default = (store, api, sharedActions) => {
135
78
  isAuthenticated: true
136
79
  });
137
80
  } catch (e) {
138
- const retry = tenantId => __refreshTokenHosted({
139
- tenantId,
140
- isSsoEnforcementRetry: true
141
- });
142
- if (await __handleSsoEnforcementRefreshError(e, isSsoEnforcementRetry, retry)) {
143
- return;
144
- }
145
81
  contextHolder.setAccessToken(null);
146
82
  contextHolder.setUser(null);
147
83
  actions.setAuthState({
@@ -161,10 +97,7 @@ var _default = (store, api, sharedActions) => {
161
97
  };
162
98
 
163
99
  /** @private */
164
- const __refreshTokenEmbedded = async ({
165
- tenantId,
166
- isSsoEnforcementRetry = false
167
- } = {}) => {
100
+ const __refreshTokenEmbedded = async () => {
168
101
  try {
169
102
  const {
170
103
  routes,
@@ -175,7 +108,7 @@ var _default = (store, api, sharedActions) => {
175
108
  user,
176
109
  tenants = [],
177
110
  activeTenant
178
- } = await (0, _helpers.refreshTokenWithRetry)(() => api.auth.refreshTokenV3(tenantId));
111
+ } = await (0, _helpers.refreshTokenWithRetry)(() => api.auth.refreshTokenV3());
179
112
  if ((0, _helpers3.isMfaRequired)(user, store.root.appName)) {
180
113
  const mfaRequiredState = await actions.getMfaRequiredState(user);
181
114
  actions.setAuthState(mfaRequiredState);
@@ -206,13 +139,6 @@ var _default = (store, api, sharedActions) => {
206
139
  });
207
140
  }
208
141
  } catch (e) {
209
- const retry = tenantId => __refreshTokenEmbedded({
210
- tenantId,
211
- isSsoEnforcementRetry: true
212
- });
213
- if (await __handleSsoEnforcementRefreshError(e, isSsoEnforcementRetry, retry)) {
214
- return;
215
- }
216
142
  contextHolder.setAccessToken(null);
217
143
  contextHolder.setUser(null);
218
144
  actions.setAuthState({
@@ -223,9 +149,7 @@ var _default = (store, api, sharedActions) => {
223
149
  };
224
150
 
225
151
  /** @private */
226
- const __refreshTokenWithNativeTokens = async ({
227
- isSsoEnforcementRetry = false
228
- } = {}) => {
152
+ const __refreshTokenWithNativeTokens = async () => {
229
153
  try {
230
154
  const authApi = api.auth;
231
155
  const response = await (0, _helpers.refreshTokenWithRetry)(async () => {
@@ -239,13 +163,6 @@ var _default = (store, api, sharedActions) => {
239
163
  isAuthenticated: true
240
164
  });
241
165
  } catch (e) {
242
- // native tokens can't name a tenant: the retry relies on the tab tenant only
243
- const retry = () => __refreshTokenWithNativeTokens({
244
- isSsoEnforcementRetry: true
245
- });
246
- if (await __handleSsoEnforcementRefreshError(e, isSsoEnforcementRetry, retry)) {
247
- return;
248
- }
249
166
  contextHolder.setAccessToken(null);
250
167
  contextHolder.setUser(null);
251
168
  actions.setAuthState({
@@ -279,9 +196,9 @@ var _default = (store, api, sharedActions) => {
279
196
  * @private
280
197
  */
281
198
  const __handleUnnecessaryEntitlementsUpdate = async updatedUser => {
282
- var _store$auth$user2;
199
+ var _store$auth$user;
283
200
  // @ts-ignore TODO: fix entitlements state type
284
- const oldEntitlements = (_store$auth$user2 = store.auth.user) == null ? void 0 : _store$auth$user2.entitlements;
201
+ const oldEntitlements = (_store$auth$user = store.auth.user) == null ? void 0 : _store$auth$user.entitlements;
285
202
  if ((0, _toolkit.isEntitlementsDeeplyEqual)(oldEntitlements, updatedUser == null ? void 0 : updatedUser.entitlements)) {
286
203
  // set the previous entitlements object
287
204
  return (0, _extends2.default)({}, updatedUser, {
@@ -479,6 +396,29 @@ var _default = (store, api, sharedActions) => {
479
396
  callback == null ? void 0 : callback();
480
397
  }
481
398
  };
399
+
400
+ // v6 parity (takeLeading): a preLogin called while another is in flight is dropped.
401
+ let preLoginInFlight = false;
402
+ let latestPreLoginAttempt = 0;
403
+
404
+ // Read on use: the login box installs onRedirectTo lazily, logout() clears it and the store default is a no-op.
405
+ const redirectToFullPage = url => {
406
+ const onRedirectTo = [store.auth.onRedirectTo, contextHolder.onRedirectTo].find(isUsableRedirect);
407
+ if (onRedirectTo) {
408
+ onRedirectTo(url, {
409
+ refresh: true
410
+ });
411
+ } else {
412
+ window.location.href = url;
413
+ }
414
+ };
415
+ const scheduleSsoRedirect = (ssoRedirectUrl, attempt) => {
416
+ setTimeout(() => {
417
+ if (attempt === latestPreLoginAttempt && store.auth.loginState.step === _interfaces.LoginStep.redirectToSSO) {
418
+ redirectToFullPage(ssoRedirectUrl);
419
+ }
420
+ }, SSO_REDIRECT_DELAY_MS);
421
+ };
482
422
  const preLogin = async payload => {
483
423
  const {
484
424
  email,
@@ -487,11 +427,17 @@ var _default = (store, api, sharedActions) => {
487
427
  invitationToken,
488
428
  callback
489
429
  } = payload;
430
+ if (preLoginInFlight) {
431
+ callback == null ? void 0 : callback();
432
+ return;
433
+ }
434
+ preLoginInFlight = true;
435
+ latestPreLoginAttempt += 1;
436
+ const attempt = latestPreLoginAttempt;
490
437
  setLoginState({
491
438
  loading: true
492
439
  });
493
440
  try {
494
- const onRedirectTo = store.auth.onRedirectTo;
495
441
  const tenantId = (0, _helpers2.getSearchParam)(_helpers2.TENANT_ID_PARAM_KEY);
496
442
  const preLoginResult = await api.auth.preLoginV2((0, _extends2.default)({}, email ? {
497
443
  email
@@ -512,9 +458,7 @@ var _default = (store, api, sharedActions) => {
512
458
  setLoginState({
513
459
  loading: false
514
460
  });
515
- onRedirectTo(address, {
516
- refresh: true
517
- });
461
+ redirectToFullPage(address);
518
462
  return;
519
463
  }
520
464
  let ssoRedirectUrl = address;
@@ -537,11 +481,7 @@ var _default = (store, api, sharedActions) => {
537
481
  loading: false,
538
482
  ssoRedirectUrl
539
483
  });
540
- setTimeout(() => {
541
- onRedirectTo(ssoRedirectUrl, {
542
- refresh: true
543
- });
544
- }, 2000);
484
+ scheduleSsoRedirect(ssoRedirectUrl, attempt);
545
485
  }
546
486
  } else {
547
487
  await ssoPreloginFailed((0, _extends2.default)({}, username ? {
@@ -565,6 +505,8 @@ var _default = (store, api, sharedActions) => {
565
505
  invitationToken,
566
506
  error: e
567
507
  }));
508
+ } finally {
509
+ preLoginInFlight = false;
568
510
  }
569
511
  };
570
512
  const postLogin = async payload => {
@@ -751,7 +693,6 @@ var _default = (store, api, sharedActions) => {
751
693
  if (!keepHostedOAuthState) {
752
694
  (0, _oauthStorage.clearHostedOAuthState)();
753
695
  }
754
- window.sessionStorage.removeItem(_constants.FRONTEGG_PENDING_SWITCH_TENANT);
755
696
  };
756
697
  const logout = async payload => {
757
698
  const hostedLoginBox = store.auth.hostedLoginBox;
@@ -1454,10 +1395,7 @@ var _default = (store, api, sharedActions) => {
1454
1395
  };
1455
1396
 
1456
1397
  /** @private */
1457
- const __refreshTokenHostedSocialLogins = async ({
1458
- tenantId,
1459
- isSsoEnforcementRetry = false
1460
- } = {}) => {
1398
+ const __refreshTokenHostedSocialLogins = async () => {
1461
1399
  const {
1462
1400
  user: currentUser
1463
1401
  } = store.auth;
@@ -1471,7 +1409,7 @@ var _default = (store, api, sharedActions) => {
1471
1409
  return;
1472
1410
  }
1473
1411
  try {
1474
- const tabTenantId = tenantId != null ? tenantId : (0, _restApi.getTabTenantFromSessionStorage)(store.root.appName);
1412
+ const tabTenantId = (0, _restApi.getTabTenantFromSessionStorage)(store.root.appName);
1475
1413
  const body = (0, _extends2.default)({
1476
1414
  grant_type: 'refresh_token',
1477
1415
  refresh_token: currentUser == null ? void 0 : currentUser.refreshToken
@@ -1486,13 +1424,6 @@ var _default = (store, api, sharedActions) => {
1486
1424
  isAuthenticated: true
1487
1425
  });
1488
1426
  } catch (e) {
1489
- const retry = tenantId => __refreshTokenHostedSocialLogins({
1490
- tenantId,
1491
- isSsoEnforcementRetry: true
1492
- });
1493
- if (await __handleSsoEnforcementRefreshError(e, isSsoEnforcementRetry, retry)) {
1494
- return;
1495
- }
1496
1427
  contextHolder.setAccessToken(null);
1497
1428
  contextHolder.setUser(null);
1498
1429
  actions.setAuthState({
@@ -1502,10 +1433,7 @@ var _default = (store, api, sharedActions) => {
1502
1433
  }
1503
1434
  };
1504
1435
  /** @private */
1505
- const __refreshTokenEmbeddedSocialLogins = async ({
1506
- tenantId,
1507
- isSsoEnforcementRetry = false
1508
- } = {}) => {
1436
+ const __refreshTokenEmbeddedSocialLogins = async () => {
1509
1437
  try {
1510
1438
  const {
1511
1439
  routes,
@@ -1516,7 +1444,7 @@ var _default = (store, api, sharedActions) => {
1516
1444
  user,
1517
1445
  tenants = [],
1518
1446
  activeTenant
1519
- } = await (0, _helpers.refreshTokenWithRetry)(() => api.auth.refreshTokenV3(tenantId));
1447
+ } = await (0, _helpers.refreshTokenWithRetry)(() => api.auth.refreshTokenV3());
1520
1448
  if ((0, _helpers3.isMfaRequired)(user, store.root.appName)) {
1521
1449
  const mfaRequiredState = await actions.getMfaRequiredState(user);
1522
1450
  actions.setAuthState(mfaRequiredState);
@@ -1566,13 +1494,6 @@ var _default = (store, api, sharedActions) => {
1566
1494
  }
1567
1495
  }
1568
1496
  } catch (e) {
1569
- const retry = tenantId => __refreshTokenEmbeddedSocialLogins({
1570
- tenantId,
1571
- isSsoEnforcementRetry: true
1572
- });
1573
- if (await __handleSsoEnforcementRefreshError(e, isSsoEnforcementRetry, retry)) {
1574
- return;
1575
- }
1576
1497
  contextHolder.setAccessToken(null);
1577
1498
  contextHolder.setUser(null);
1578
1499
  actions.setAuthState({
@@ -1619,8 +1540,7 @@ var _default = (store, api, sharedActions) => {
1619
1540
  resetBreachedPassword,
1620
1541
  refreshTokenForSocialLogins,
1621
1542
  // protected
1622
- __refreshToken,
1623
- __redirectToSsoForTenant
1543
+ __refreshToken
1624
1544
  }, (0, _hostedLoginAuthorize.default)(store, api, sharedActions), (0, _afterAuthNavigation.default)(store, api, sharedActions), (0, _handleVerifyMFAResponse.default)(store, api, sharedActions), (0, _mfaRequiredState.default)(store, api, sharedActions), (0, _mfaWithAuthenticator.default)(store, api, sharedActions), (0, _mfaWithEmailCode.default)(store, api, sharedActions), (0, _mfaWithSMS.default)(store, api, sharedActions), (0, _mfaWithWebAuthn.default)(store, api, sharedActions));
1625
1545
  };
1626
1546
  exports.default = _default;
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.defaultFronteggRoutes = exports.authStrategyLoginStepMap = void 0;
6
+ exports.defaultOnRedirectTo = exports.defaultFronteggRoutes = exports.authStrategyLoginStepMap = void 0;
7
7
  var _restApi = require("@frontegg/rest-api");
8
8
  var _interfaces = require("./interfaces");
9
9
  /**
@@ -12,6 +12,8 @@ var _interfaces = require("./interfaces");
12
12
  * please don't import from redux or from other files that import from redux
13
13
  */
14
14
 
15
+ const defaultOnRedirectTo = () => void 0;
16
+ exports.defaultOnRedirectTo = defaultOnRedirectTo;
15
17
  const authStrategyLoginStepMap = {
16
18
  [_restApi.AuthStrategyEnum.Code]: _interfaces.LoginStep.loginWithOtc,
17
19
  [_restApi.AuthStrategyEnum.EmailAndPassword]: _interfaces.LoginStep.loginWithPassword,
@@ -7,7 +7,6 @@ exports.default = void 0;
7
7
  var _state = require("./state");
8
8
  var _helpers = require("../../helpers");
9
9
  var _restApi = require("@frontegg/rest-api");
10
- var _constants = require("../../constants");
11
10
  var _default = (store, api, sharedActions) => {
12
11
  const actions = sharedActions;
13
12
  const setTenantsState = state => {
@@ -58,15 +57,6 @@ var _default = (store, api, sharedActions) => {
58
57
  }) : actions.setAuthState({
59
58
  isLoading: false
60
59
  });
61
- const [isEnforceSsoOnTenantAccessEnabled] = _restApi.FeatureFlags.getFeatureFlags([_restApi.ENFORCE_SSO_ON_TENANT_ACCESS_FF], store.root.appName);
62
- // a switch re-run after coming back from SSO doesn't send the user to SSO again
63
- const isPendingSwitchRerun = window.sessionStorage.getItem(_constants.FRONTEGG_PENDING_SWITCH_TENANT) === tenantId;
64
- if (isEnforceSsoOnTenantAccessEnabled && (0, _helpers.isSsoEnforcementError)(e) && e.errorContext.ssoRequired && !isPendingSwitchRerun) {
65
- // saved to finish the switch after the SSO login, which may land in another tenant
66
- window.sessionStorage.setItem(_constants.FRONTEGG_PENDING_SWITCH_TENANT, tenantId);
67
- await actions.__redirectToSsoForTenant(e.errorContext);
68
- return;
69
- }
70
60
  callback == null ? void 0 : callback(false, e);
71
61
  }
72
62
  };
@@ -559,7 +559,7 @@ const createAuthState = _overrideState => {
559
559
  overrideState = (0, _objectWithoutPropertiesLoose2.default)(_ref, _excluded);
560
560
  return (0, _proxy.createProxy)((0, _extends2.default)({
561
561
  routes: (0, _extends2.default)({}, _consts.defaultFronteggRoutes, routes),
562
- onRedirectTo: () => void 0,
562
+ onRedirectTo: _consts.defaultOnRedirectTo,
563
563
  isAuthenticated: false,
564
564
  isLoading: true,
565
565
  isSSOAuth: false,
package/node/constants.js CHANGED
@@ -5,11 +5,10 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  var _exportNames = {
7
7
  FRONTEGG_AFTER_AUTH_REDIRECT_URL: true,
8
- FRONTEGG_PENDING_SWITCH_TENANT: true,
9
8
  HOSTED_LOGIN_VERIFIER_KEY: true,
10
9
  DEFAULT_RETRY_CONFIG: true
11
10
  };
12
- exports.HOSTED_LOGIN_VERIFIER_KEY = exports.FRONTEGG_PENDING_SWITCH_TENANT = exports.FRONTEGG_AFTER_AUTH_REDIRECT_URL = exports.DEFAULT_RETRY_CONFIG = void 0;
11
+ exports.HOSTED_LOGIN_VERIFIER_KEY = exports.FRONTEGG_AFTER_AUTH_REDIRECT_URL = exports.DEFAULT_RETRY_CONFIG = void 0;
13
12
  var _consts = require("./auth/StepUpState/consts");
14
13
  Object.keys(_consts).forEach(function (key) {
15
14
  if (key === "default" || key === "__esModule") return;
@@ -36,8 +35,6 @@ Object.keys(_consts2).forEach(function (key) {
36
35
  });
37
36
  const FRONTEGG_AFTER_AUTH_REDIRECT_URL = 'FRONTEGG_AFTER_AUTH_REDIRECT_URL';
38
37
  exports.FRONTEGG_AFTER_AUTH_REDIRECT_URL = FRONTEGG_AFTER_AUTH_REDIRECT_URL;
39
- const FRONTEGG_PENDING_SWITCH_TENANT = 'FRONTEGG_PENDING_SWITCH_TENANT';
40
- exports.FRONTEGG_PENDING_SWITCH_TENANT = FRONTEGG_PENDING_SWITCH_TENANT;
41
38
  const HOSTED_LOGIN_VERIFIER_KEY = 'HOSTED_LOGIN_VERIFIER_KEY';
42
39
  exports.HOSTED_LOGIN_VERIFIER_KEY = HOSTED_LOGIN_VERIFIER_KEY;
43
40
  const DEFAULT_RETRY_CONFIG = {
@@ -3,14 +3,10 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.isSsoEnforcementError = exports.isFronteggApiError = exports.isError = exports.errorHandler = void 0;
6
+ exports.isFronteggApiError = exports.isError = exports.errorHandler = void 0;
7
7
  var _restApi = require("@frontegg/rest-api");
8
8
  const isFronteggApiError = error => Boolean(error == null ? void 0 : error.statusCode);
9
-
10
- // ER-01079 (disabled user / expired tenant) is not this: it still logs out
11
9
  exports.isFronteggApiError = isFronteggApiError;
12
- const isSsoEnforcementError = error => isFronteggApiError(error) && error.errorCode === _restApi.SSO_REQUIRED_FOR_TENANT_ERROR_CODE;
13
- exports.isSsoEnforcementError = isSsoEnforcementError;
14
10
  const isError = error => {
15
11
  if (error instanceof Error) {
16
12
  return true;
package/node/index.js CHANGED
@@ -1,4 +1,4 @@
1
- /** @license Frontegg v7.129.0-alpha.0
1
+ /** @license Frontegg v7.129.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.129.0-alpha.0",
3
+ "version": "7.129.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.129.0-alpha.0",
10
+ "@frontegg/rest-api": "7.129.0",
11
11
  "fast-deep-equal": "3.1.3",
12
12
  "get-value": "^3.0.1",
13
13
  "proxy-compare": "^3.0.0",