@frontegg/redux-store 7.130.0 → 7.131.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/auth/ActivateAccountState/actions.js +6 -0
  2. package/auth/LoginState/actions/hostedLoginAuthorize.actions.d.ts +3 -1
  3. package/auth/LoginState/actions/hostedLoginAuthorize.actions.js +29 -6
  4. package/auth/LoginState/actions/index.d.ts +8 -1
  5. package/auth/LoginState/actions/index.js +172 -11
  6. package/auth/LoginState/actions/mfaWithAuthenticator.actions.js +3 -0
  7. package/auth/LoginState/actions/mfaWithEmailCode.actions.js +3 -0
  8. package/auth/LoginState/actions/mfaWithSMS.actions.js +3 -0
  9. package/auth/LoginState/actions/mfaWithWebAuthn.actions.js +3 -0
  10. package/auth/MfaState/actions.js +3 -0
  11. package/auth/SocialLoginState/actions.js +3 -0
  12. package/auth/StepUpState/actions/generateStepUpSession.actions.js +5 -0
  13. package/auth/StepUpState/helpers.d.ts +2 -1
  14. package/auth/StepUpState/helpers.js +3 -2
  15. package/auth/TenantsState/actions.js +8 -1
  16. package/auth/helpers.d.ts +15 -0
  17. package/auth/helpers.js +60 -2
  18. package/helpers/handlers.d.ts +4 -1
  19. package/helpers/handlers.js +4 -1
  20. package/index.js +1 -1
  21. package/mocks/auth-mocks/loginActions.mocks.d.ts +8 -1
  22. package/node/auth/ActivateAccountState/actions.js +6 -0
  23. package/node/auth/LoginState/actions/hostedLoginAuthorize.actions.js +27 -4
  24. package/node/auth/LoginState/actions/index.js +169 -8
  25. package/node/auth/LoginState/actions/mfaWithAuthenticator.actions.js +3 -0
  26. package/node/auth/LoginState/actions/mfaWithEmailCode.actions.js +3 -0
  27. package/node/auth/LoginState/actions/mfaWithSMS.actions.js +3 -0
  28. package/node/auth/LoginState/actions/mfaWithWebAuthn.actions.js +3 -0
  29. package/node/auth/MfaState/actions.js +3 -0
  30. package/node/auth/SocialLoginState/actions.js +3 -0
  31. package/node/auth/StepUpState/actions/generateStepUpSession.actions.js +5 -0
  32. package/node/auth/StepUpState/helpers.js +3 -2
  33. package/node/auth/TenantsState/actions.js +7 -0
  34. package/node/auth/helpers.js +66 -2
  35. package/node/helpers/handlers.js +5 -1
  36. package/node/index.js +1 -1
  37. package/package.json +2 -2
package/auth/helpers.js CHANGED
@@ -1,8 +1,8 @@
1
- import _extends from "@babel/runtime/helpers/esm/extends";
2
1
  import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
2
+ import _extends from "@babel/runtime/helpers/esm/extends";
3
3
  const _excluded = ["phoneNumber", "email", "username"],
4
4
  _excluded2 = ["authenticatedUrl"];
5
- import { ContextHolder } from '@frontegg/rest-api';
5
+ import { ContextHolder, restoreTabTenantBeforeSsoSwitch, isEnforceSsoOnTenantAccessEnabled } from '@frontegg/rest-api';
6
6
  import { defaultFronteggRoutes } from './LoginState/consts';
7
7
  export * from './Entitlements/helpers';
8
8
  export * from './LoginState/helpers';
@@ -53,6 +53,52 @@ export const getUri = urlStrategy => {
53
53
  }
54
54
  }
55
55
  };
56
+
57
+ /**
58
+ * The route the app shows and the page's path. With hash routing they differ: the app's pages live in the hash, but
59
+ * SAML, OIDC, social and magic link callbacks come back on the path, which is where login matches them.
60
+ */
61
+ const getCurrentRoutes = urlStrategy => {
62
+ const hashOrPathRoute = getUri(urlStrategy);
63
+ const {
64
+ pathname
65
+ } = window.location;
66
+ return hashOrPathRoute === pathname ? [pathname] : [hashOrPathRoute, pathname];
67
+ };
68
+
69
+ /**
70
+ * On the OIDC callback the page still has to finish the OIDC login with its code: a refresh refused before that is not
71
+ * a reason to go back to the IdP, which would send the user straight back here, forever.
72
+ */
73
+ export const isOnOidcCallbackRoute = store => {
74
+ var _store$root$urlStrate;
75
+ const {
76
+ oidcRedirectUrl
77
+ } = _extends({}, defaultFronteggRoutes, store.auth.routes);
78
+ return !!oidcRedirectUrl && getCurrentRoutes((_store$root$urlStrate = store.root.urlStrategy) != null ? _store$root$urlStrate : 'path').some(currentRoute => currentRoute.endsWith(oidcRedirectUrl));
79
+ };
80
+
81
+ /**
82
+ * After a switch refused for SSO the tab points at the SSO tenant. Its SSO login comes back on a callback route and
83
+ * keeps it; any other page load means the user didn't finish that login, so the tab goes back to its tenant.
84
+ */
85
+ export const restoreTabTenantUnlessSsoCallback = store => {
86
+ var _store$root$urlStrate2;
87
+ if (!isEnforceSsoOnTenantAccessEnabled(store.root.appName) || !ContextHolder.for(store.root.appName).isSessionPerTenantEnabled()) {
88
+ return;
89
+ }
90
+ const {
91
+ samlCallbackUrl,
92
+ oidcRedirectUrl,
93
+ hostedLoginRedirectUrl
94
+ } = _extends({}, defaultFronteggRoutes, store.auth.routes);
95
+ // matched like login matches its callback pages: endsWith, so a basename before the route still counts
96
+ const currentRoutes = getCurrentRoutes((_store$root$urlStrate2 = store.root.urlStrategy) != null ? _store$root$urlStrate2 : 'path');
97
+ const isSsoCallback = [samlCallbackUrl, oidcRedirectUrl, hostedLoginRedirectUrl].some(route => !!route && currentRoutes.some(currentRoute => currentRoute.endsWith(route)));
98
+ if (!isSsoCallback) {
99
+ restoreTabTenantBeforeSsoSwitch();
100
+ }
101
+ };
56
102
  export const prepareIdentifiersForSignup = payload => {
57
103
  const {
58
104
  phoneNumber,
@@ -77,6 +123,18 @@ export const prepareIdentifiersForSignup = payload => {
77
123
  }
78
124
  return finalDto;
79
125
  };
126
+
127
+ // Pages the user lands on after login or sign-up: destinations, not auth pages.
128
+ const AFTER_AUTH_DESTINATION_ROUTES = ['authenticatedUrl', 'signUpSuccessUrl', 'accountSignUpSuccessUrl', 'userSignUpSuccessUrl', 'customLoginAuthenticatedUrl'];
129
+
130
+ /**
131
+ * Whether the current route is an auth page (login, tenant chooser, social / magic link / SSO callback), not one to come
132
+ * back to. Unlike isAuthRoute: after-login / sign-up destinations don't count, and endsWith lets a basename prefix it.
133
+ */
134
+ export const isOnAuthPageRoute = (urlStrategy, routes) => {
135
+ const currentRoutes = getCurrentRoutes(urlStrategy);
136
+ return Object.entries(_extends({}, defaultFronteggRoutes, routes)).some(([name, route]) => !AFTER_AUTH_DESTINATION_ROUTES.includes(name) && typeof route === 'string' && !!route && currentRoutes.some(currentRoute => currentRoute.endsWith(route)));
137
+ };
80
138
  export const isAuthRoute = (path, routes) => {
81
139
  const _defaultFronteggRoute = _extends({}, defaultFronteggRoutes, routes),
82
140
  authRoutes = _objectWithoutPropertiesLoose(_defaultFronteggRoute, _excluded2);
@@ -1,4 +1,7 @@
1
- import { FronteggApiError } from '@frontegg/rest-api';
1
+ import { FronteggApiError, SsoEnforcementErrorContext } 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
+ };
3
6
  export declare const isError: (error: any) => error is Error;
4
7
  export declare const errorHandler: <T>(error: unknown, fallback?: T) => FronteggApiError | T;
@@ -1,5 +1,8 @@
1
- import { FronteggApiError, GENERIC_ERROR_MESSAGE, GENERIC_ERROR_CODE } from '@frontegg/rest-api';
1
+ import { FronteggApiError, GENERIC_ERROR_MESSAGE, GENERIC_ERROR_CODE, SSO_REQUIRED_FOR_TENANT_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;
3
6
  export const isError = error => {
4
7
  if (error instanceof Error) {
5
8
  return true;
package/index.js CHANGED
@@ -1,4 +1,4 @@
1
- /** @license Frontegg v7.130.0
1
+ /** @license Frontegg v7.131.0-alpha.1
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.
@@ -33,7 +33,9 @@ declare const _default: (store: FronteggState, api: RestApi, sharedActions: Shar
33
33
  firstTime?: boolean;
34
34
  loginDirectAction?: import("../..").LoginDirectAction;
35
35
  }) => Promise<void>;
36
- __requestHostedLoginAuthorize: (additionalParams?: Record<string, string>) => Promise<void>;
36
+ __requestHostedLoginAuthorize: (additionalParams?: Record<string, string>, { forceTenantId }?: {
37
+ forceTenantId?: boolean;
38
+ }) => Promise<void>;
37
39
  __requestHostedLoginSilentAuthorize: () => Promise<void>;
38
40
  handleHostedLoginCallback: (payload: import("../..").HostedLoginCallback) => Promise<void>;
39
41
  setLoginState: (state: Partial<import("../..").LoginState>) => void;
@@ -71,5 +73,10 @@ declare const _default: (store: FronteggState, api: RestApi, sharedActions: Shar
71
73
  resetBreachedPassword: (payload: import("dist/@frontegg/rest-api").IForgotPassword) => Promise<void>;
72
74
  refreshTokenForSocialLogins: () => Promise<void>;
73
75
  __refreshToken: () => Promise<void>;
76
+ __redirectToSsoForTenant: ({ tenantId, address }: {
77
+ tenantId: string;
78
+ address: string;
79
+ }) => Promise<void>;
80
+ __redirectToSsoIfRequired: (e: unknown) => Promise<boolean>;
74
81
  };
75
82
  export default _default;
@@ -158,6 +158,9 @@ var _default = (store, api, sharedActions) => {
158
158
  });
159
159
  callback == null ? void 0 : callback(true);
160
160
  } catch (e) {
161
+ if (await actions.__redirectToSsoIfRequired(e)) {
162
+ return;
163
+ }
161
164
  setActivateState({
162
165
  loading: false,
163
166
  error: (0, _helpers.errorHandler)(e)
@@ -195,6 +198,9 @@ var _default = (store, api, sharedActions) => {
195
198
  });
196
199
  callback == null ? void 0 : callback(true);
197
200
  } catch (e) {
201
+ if (await actions.__redirectToSsoIfRequired(e)) {
202
+ return;
203
+ }
198
204
  setActivateState({
199
205
  submitting: false,
200
206
  loading: false,
@@ -8,6 +8,7 @@ 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");
11
12
  var _helpers2 = require("../helpers");
12
13
  var _helpers3 = require("../../helpers");
13
14
  var _consts = require("../../StepUpState/consts");
@@ -21,6 +22,7 @@ var _default = (store, api, sharedActions) => {
21
22
  const __requestHostedLoginSilentAuthorize = async () => {
22
23
  var _response;
23
24
  let response;
25
+ (0, _helpers3.restoreTabTenantUnlessSsoCallback)(store);
24
26
  if (_toolkit.FronteggNativeModule.isGetTokensAvailable()) {
25
27
  const authApi = api.auth;
26
28
  response = await (0, _helpers.refreshTokenWithRetry)(async () => {
@@ -28,7 +30,21 @@ var _default = (store, api, sharedActions) => {
28
30
  return await authApi.silentOAuthRefreshTokenV3(tokens);
29
31
  });
30
32
  } else {
31
- response = await api.auth.silentOAuthRefreshTokenV2();
33
+ try {
34
+ response = await api.auth.silentOAuthRefreshTokenV2();
35
+ } catch (error) {
36
+ var _store$auth$user, _error$errorContext;
37
+ // the tenant requires SSO: hosted login for that tenant, not the plain one the caller falls back to
38
+ if (await actions.__redirectToSsoIfRequired(error)) {
39
+ return;
40
+ }
41
+ const tenantId = (_store$auth$user = store.auth.user) == null ? void 0 : _store$auth$user.tenantId;
42
+ if (!(0, _restApi.isEnforceSsoOnTenantAccessEnabled)(store.root.appName) || !(0, _helpers.isSsoEnforcementError)(error) || ((_error$errorContext = error.errorContext) == null ? void 0 : _error$errorContext.ssoRequired) !== false || !tenantId) {
43
+ throw error;
44
+ }
45
+ // one retry for the tenant of the current token (a cold reload has no user yet: no retry)
46
+ response = await api.auth.silentOAuthRefreshTokenV2(tenantId);
47
+ }
32
48
  }
33
49
  if ((_response = response) != null && _response.user) {
34
50
  await actions.afterAuthenticationStateUpdate(response, {
@@ -39,7 +55,14 @@ var _default = (store, api, sharedActions) => {
39
55
  throw new Error(`couldn't refresh user token with oauth service`);
40
56
  }
41
57
  };
42
- const requestHostedLoginAuthorize = async additionalParams => {
58
+
59
+ /**
60
+ * @param options.forceTenantId the tenantId in additionalParams wins over the resolved and the page's tenant
61
+ * (sending the user to that tenant's SSO); other callers keep their custom login tenant
62
+ */
63
+ const requestHostedLoginAuthorize = async (additionalParams, {
64
+ forceTenantId = false
65
+ } = {}) => {
43
66
  var _store$auth$customLog;
44
67
  const {
45
68
  routes,
@@ -86,7 +109,7 @@ var _default = (store, api, sharedActions) => {
86
109
  code_challenge_method: 'S256',
87
110
  nonce
88
111
  }, additionalParams);
89
- if (context.tenantResolver) {
112
+ if (context.tenantResolver && !forceTenantId) {
90
113
  var _context$tenantResolv;
91
114
  const resolvedTenantResult = await ((_context$tenantResolv = context.tenantResolver) == null ? void 0 : _context$tenantResolv.call(context));
92
115
  if (resolvedTenantResult != null && resolvedTenantResult.tenant) {
@@ -94,7 +117,7 @@ var _default = (store, api, sharedActions) => {
94
117
  }
95
118
  }
96
119
  const tenantId = (0, _helpers2.getSearchParam)(_helpers2.TENANT_ID_PARAM_KEY);
97
- if (tenantId) {
120
+ if (tenantId && !forceTenantId) {
98
121
  params['tenantId'] = tenantId;
99
122
  }
100
123
  if (_toolkit.FronteggNativeModule.isAvailable('closeWindow')) {
@@ -50,8 +50,114 @@ var _default = (store, api, sharedActions) => {
50
50
  const actions = sharedActions;
51
51
  const contextHolder = _restApi.ContextHolder.for(store.root.appName);
52
52
 
53
+ /**
54
+ * Sends the user to log in through the tenant's SSO and back to the current page afterwards.
55
+ * @protected
56
+ */
57
+ // as preLogin (takeLeading): an SSO redirect started while another is in flight is dropped; the first one leaves the
58
+ // page, and a second hosted one would replace the PKCE verifier the first one's callback needs
59
+ let ssoRedirectInFlight = false;
60
+ const __redirectToSsoForTenant = async ({
61
+ tenantId,
62
+ address
63
+ }) => {
64
+ if (ssoRedirectInFlight) {
65
+ return;
66
+ }
67
+ ssoRedirectInFlight = true;
68
+ try {
69
+ await __redirectToSsoForTenantNow({
70
+ tenantId,
71
+ address
72
+ });
73
+ } finally {
74
+ ssoRedirectInFlight = false;
75
+ }
76
+ };
77
+ const __redirectToSsoForTenantNow = async ({
78
+ tenantId,
79
+ address
80
+ }) => {
81
+ var _store$root$urlStrate;
82
+ // the login box shows login's "redirecting to SSO" step instead of the screen that was refused (its spinner and
83
+ // a previous error)
84
+ setLoginState({
85
+ step: _interfaces.LoginStep.redirectToSSO,
86
+ loading: false,
87
+ error: undefined
88
+ });
89
+ // on an auth page (login, tenant chooser, social / magic link / SSO callback) keep the destination the login
90
+ // already saved: the page itself is not one to come back to
91
+ const urlStrategy = (_store$root$urlStrate = store.root.urlStrategy) != null ? _store$root$urlStrate : 'path';
92
+ const isOnAuthPage = (0, _helpers3.isOnAuthPageRoute)(urlStrategy, store.auth.routes);
93
+ if (!isOnAuthPage) {
94
+ (0, _helpers3.setAfterAuthRedirectUrlForStepUp)(urlStrategy);
95
+ }
96
+ if (store.auth.hostedLoginBox) {
97
+ var _window$localStorage;
98
+ // as loginWithRedirect does, but the SSO tenant wins over the custom login tenant
99
+ (_window$localStorage = window.localStorage) == null ? void 0 : _window$localStorage.removeItem(_consts.SHOULD_STEP_UP_KEY);
100
+ await actions.__requestHostedLoginAuthorize({
101
+ prompt: 'login',
102
+ tenantId
103
+ }, {
104
+ forceTenantId: true
105
+ });
106
+ return;
107
+ }
108
+ // as login does for an OIDC SSO page without redirect_uri; the refusal has no idpType, but an OIDC page asks for a code
109
+ let ssoRedirectUrl = address;
110
+ if (/[?&]response_type=code(&|$)/.test(address) && !address.includes('redirect_uri')) {
111
+ ssoRedirectUrl += `&redirect_uri=${window.location.origin}${store.auth.routes.oidcRedirectUrl}`;
112
+ }
113
+ redirectToFullPage(ssoRedirectUrl);
114
+ };
115
+
116
+ /**
117
+ * Sends the user to the tenant's SSO when a login or tenant switch is refused because that tenant requires it
118
+ * (e.g. an invitation into a tenant whose SSO covers the user's email). The SSO login lands in that tenant.
119
+ * @returns true when redirected, so the caller doesn't show the error
120
+ * @protected
121
+ */
122
+ const __redirectToSsoIfRequired = async e => {
123
+ var _e$errorContext;
124
+ if (!(0, _restApi.isEnforceSsoOnTenantAccessEnabled)(store.root.appName) || !(0, _helpers.isSsoEnforcementError)(e) || !((_e$errorContext = e.errorContext) != null && _e$errorContext.ssoRequired)) {
125
+ return false;
126
+ }
127
+ await __redirectToSsoForTenant(e.errorContext);
128
+ return true;
129
+ };
130
+ /**
131
+ * Handles a hosted (OAuth) token refresh refused because the tenant requires SSO.
132
+ * ssoRequired: true sends the user to SSO, like login. ssoRequired: false renews once more for the tenant of the
133
+ * current token, which keeps the session: OAuth doesn't use up the refresh token on this refusal.
134
+ * @returns true when handled, false when the caller should log out
135
+ * @private
136
+ */
137
+ const __handleSsoEnforcementRefreshError = async (e, isSsoEnforcementRetry, refreshForTenant) => {
138
+ var _store$auth$user;
139
+ if (!(0, _restApi.isEnforceSsoOnTenantAccessEnabled)(store.root.appName) || !(0, _helpers.isSsoEnforcementError)(e)) {
140
+ return false;
141
+ }
142
+ if (await __redirectToSsoIfRequired(e)) {
143
+ return true;
144
+ }
145
+ const currentTenantId = (_store$auth$user = store.auth.user) == null ? void 0 : _store$auth$user.tenantId;
146
+ if (isSsoEnforcementRetry || !currentTenantId) {
147
+ return false;
148
+ }
149
+ if (contextHolder.isSessionPerTenantEnabled()) {
150
+ (0, _restApi.setTabTenantInSessionStorage)(currentTenantId);
151
+ }
152
+ await refreshForTenant(currentTenantId);
153
+ return true;
154
+ };
155
+
53
156
  /** @private */
54
- const __refreshTokenHosted = async () => {
157
+ const __refreshTokenHosted = async ({
158
+ tenantId,
159
+ isSsoEnforcementRetry = false
160
+ } = {}) => {
55
161
  const user = store.auth.user;
56
162
  if (!(user != null && user.refreshToken)) {
57
163
  contextHolder.setAccessToken(null);
@@ -63,7 +169,7 @@ var _default = (store, api, sharedActions) => {
63
169
  return;
64
170
  }
65
171
  try {
66
- const tabTenantId = (0, _restApi.getTabTenantFromSessionStorage)(store.root.appName);
172
+ const tabTenantId = tenantId != null ? tenantId : (0, _restApi.getTabTenantFromSessionStorage)(store.root.appName);
67
173
  const body = (0, _extends2.default)({
68
174
  grant_type: 'refresh_token',
69
175
  refresh_token: user == null ? void 0 : user.refreshToken
@@ -78,6 +184,13 @@ var _default = (store, api, sharedActions) => {
78
184
  isAuthenticated: true
79
185
  });
80
186
  } catch (e) {
187
+ const retry = tenantId => __refreshTokenHosted({
188
+ tenantId,
189
+ isSsoEnforcementRetry: true
190
+ });
191
+ if (await __handleSsoEnforcementRefreshError(e, isSsoEnforcementRetry, retry)) {
192
+ return;
193
+ }
81
194
  contextHolder.setAccessToken(null);
82
195
  contextHolder.setUser(null);
83
196
  actions.setAuthState({
@@ -139,6 +252,11 @@ var _default = (store, api, sharedActions) => {
139
252
  });
140
253
  }
141
254
  } catch (e) {
255
+ // a refused identity refresh has already used up the refresh token: no retry, only the SSO redirect, except on
256
+ // the OIDC callback, where the page itself completes the OIDC login
257
+ if ((0, _restApi.isEnforceSsoOnTenantAccessEnabled)(store.root.appName) && !(0, _helpers3.isOnOidcCallbackRoute)(store) && (await __redirectToSsoIfRequired(e))) {
258
+ return;
259
+ }
142
260
  contextHolder.setAccessToken(null);
143
261
  contextHolder.setUser(null);
144
262
  actions.setAuthState({
@@ -149,7 +267,9 @@ var _default = (store, api, sharedActions) => {
149
267
  };
150
268
 
151
269
  /** @private */
152
- const __refreshTokenWithNativeTokens = async () => {
270
+ const __refreshTokenWithNativeTokens = async ({
271
+ isSsoEnforcementRetry = false
272
+ } = {}) => {
153
273
  try {
154
274
  const authApi = api.auth;
155
275
  const response = await (0, _helpers.refreshTokenWithRetry)(async () => {
@@ -163,6 +283,13 @@ var _default = (store, api, sharedActions) => {
163
283
  isAuthenticated: true
164
284
  });
165
285
  } catch (e) {
286
+ // native tokens can't name a tenant: the retry relies on the tab tenant only
287
+ const retry = () => __refreshTokenWithNativeTokens({
288
+ isSsoEnforcementRetry: true
289
+ });
290
+ if (await __handleSsoEnforcementRefreshError(e, isSsoEnforcementRetry, retry)) {
291
+ return;
292
+ }
166
293
  contextHolder.setAccessToken(null);
167
294
  contextHolder.setUser(null);
168
295
  actions.setAuthState({
@@ -174,6 +301,7 @@ var _default = (store, api, sharedActions) => {
174
301
 
175
302
  /** @protected */
176
303
  const __refreshToken = async () => {
304
+ (0, _helpers3.restoreTabTenantUnlessSsoCallback)(store);
177
305
  if (_toolkit.FronteggNativeModule.isGetTokensAvailable()) {
178
306
  await __refreshTokenWithNativeTokens();
179
307
  return;
@@ -196,9 +324,9 @@ var _default = (store, api, sharedActions) => {
196
324
  * @private
197
325
  */
198
326
  const __handleUnnecessaryEntitlementsUpdate = async updatedUser => {
199
- var _store$auth$user;
327
+ var _store$auth$user2;
200
328
  // @ts-ignore TODO: fix entitlements state type
201
- const oldEntitlements = (_store$auth$user = store.auth.user) == null ? void 0 : _store$auth$user.entitlements;
329
+ const oldEntitlements = (_store$auth$user2 = store.auth.user) == null ? void 0 : _store$auth$user2.entitlements;
202
330
  if ((0, _toolkit.isEntitlementsDeeplyEqual)(oldEntitlements, updatedUser == null ? void 0 : updatedUser.entitlements)) {
203
331
  // set the previous entitlements object
204
332
  return (0, _extends2.default)({}, updatedUser, {
@@ -675,6 +803,9 @@ var _default = (store, api, sharedActions) => {
675
803
  } catch (e) {
676
804
  contextHolder.setAccessToken(null);
677
805
  contextHolder.setUser(null);
806
+ if (await __redirectToSsoIfRequired(e)) {
807
+ return;
808
+ }
678
809
  callback == null ? void 0 : callback(false, e);
679
810
  setLoginState({
680
811
  email,
@@ -970,6 +1101,9 @@ var _default = (store, api, sharedActions) => {
970
1101
  callback == null ? void 0 : callback(true);
971
1102
  }
972
1103
  } catch (e) {
1104
+ if (await __redirectToSsoIfRequired(e)) {
1105
+ return;
1106
+ }
973
1107
  setLoginState({
974
1108
  error: (0, _helpers.errorHandler)(e, 'Failed to authenticate')
975
1109
  });
@@ -1106,6 +1240,9 @@ var _default = (store, api, sharedActions) => {
1106
1240
  }
1107
1241
  // callback?.(true);
1108
1242
  } catch (e) {
1243
+ if (await __redirectToSsoIfRequired(e)) {
1244
+ return;
1245
+ }
1109
1246
  setLoginState({
1110
1247
  error: (0, _helpers.errorHandler)(e)
1111
1248
  });
@@ -1266,6 +1403,9 @@ var _default = (store, api, sharedActions) => {
1266
1403
  });
1267
1404
  callback == null ? void 0 : callback(true);
1268
1405
  } catch (e) {
1406
+ if (await __redirectToSsoIfRequired(e)) {
1407
+ return;
1408
+ }
1269
1409
  setLoginState({
1270
1410
  loading: false,
1271
1411
  error: (0, _helpers.errorHandler)(e)
@@ -1289,6 +1429,9 @@ var _default = (store, api, sharedActions) => {
1289
1429
  });
1290
1430
  callback == null ? void 0 : callback(true);
1291
1431
  } catch (e) {
1432
+ if (await __redirectToSsoIfRequired(e)) {
1433
+ return;
1434
+ }
1292
1435
  setLoginState({
1293
1436
  loading: false,
1294
1437
  error: (0, _helpers.errorHandler)(e)
@@ -1339,6 +1482,9 @@ var _default = (store, api, sharedActions) => {
1339
1482
  });
1340
1483
  callback == null ? void 0 : callback(true);
1341
1484
  } catch (e) {
1485
+ if (await __redirectToSsoIfRequired(e)) {
1486
+ return;
1487
+ }
1342
1488
  setLoginState({
1343
1489
  loading: false,
1344
1490
  error: (0, _helpers.errorHandler)(e)
@@ -1395,7 +1541,10 @@ var _default = (store, api, sharedActions) => {
1395
1541
  };
1396
1542
 
1397
1543
  /** @private */
1398
- const __refreshTokenHostedSocialLogins = async () => {
1544
+ const __refreshTokenHostedSocialLogins = async ({
1545
+ tenantId,
1546
+ isSsoEnforcementRetry = false
1547
+ } = {}) => {
1399
1548
  const {
1400
1549
  user: currentUser
1401
1550
  } = store.auth;
@@ -1409,7 +1558,7 @@ var _default = (store, api, sharedActions) => {
1409
1558
  return;
1410
1559
  }
1411
1560
  try {
1412
- const tabTenantId = (0, _restApi.getTabTenantFromSessionStorage)(store.root.appName);
1561
+ const tabTenantId = tenantId != null ? tenantId : (0, _restApi.getTabTenantFromSessionStorage)(store.root.appName);
1413
1562
  const body = (0, _extends2.default)({
1414
1563
  grant_type: 'refresh_token',
1415
1564
  refresh_token: currentUser == null ? void 0 : currentUser.refreshToken
@@ -1424,6 +1573,13 @@ var _default = (store, api, sharedActions) => {
1424
1573
  isAuthenticated: true
1425
1574
  });
1426
1575
  } catch (e) {
1576
+ const retry = tenantId => __refreshTokenHostedSocialLogins({
1577
+ tenantId,
1578
+ isSsoEnforcementRetry: true
1579
+ });
1580
+ if (await __handleSsoEnforcementRefreshError(e, isSsoEnforcementRetry, retry)) {
1581
+ return;
1582
+ }
1427
1583
  contextHolder.setAccessToken(null);
1428
1584
  contextHolder.setUser(null);
1429
1585
  actions.setAuthState({
@@ -1494,6 +1650,9 @@ var _default = (store, api, sharedActions) => {
1494
1650
  }
1495
1651
  }
1496
1652
  } catch (e) {
1653
+ if (await __redirectToSsoIfRequired(e)) {
1654
+ return;
1655
+ }
1497
1656
  contextHolder.setAccessToken(null);
1498
1657
  contextHolder.setUser(null);
1499
1658
  actions.setAuthState({
@@ -1540,7 +1699,9 @@ var _default = (store, api, sharedActions) => {
1540
1699
  resetBreachedPassword,
1541
1700
  refreshTokenForSocialLogins,
1542
1701
  // protected
1543
- __refreshToken
1702
+ __refreshToken,
1703
+ __redirectToSsoForTenant,
1704
+ __redirectToSsoIfRequired
1544
1705
  }, (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));
1545
1706
  };
1546
1707
  exports.default = _default;
@@ -152,6 +152,9 @@ var _default = (store, api, sharedActions) => {
152
152
  }
153
153
  return await __postLoginMfaAuthenticator(isAuthenticated, user, callback);
154
154
  } catch (e) {
155
+ if (await actions.__redirectToSsoIfRequired(e)) {
156
+ return;
157
+ }
155
158
  setLoadingAction({
156
159
  loading: false,
157
160
  error: (0, _helpers.errorHandler)(e)
@@ -75,6 +75,9 @@ var _default = (store, api, sharedActions) => {
75
75
  });
76
76
  callback == null ? void 0 : callback(true);
77
77
  } catch (e) {
78
+ if (await actions.__redirectToSsoIfRequired(e)) {
79
+ return;
80
+ }
78
81
  setLoadingAction({
79
82
  loading: false,
80
83
  error: (0, _helpers.errorHandler)(e)
@@ -80,6 +80,9 @@ var _default = (store, api, sharedActions) => {
80
80
  });
81
81
  callback == null ? void 0 : callback(true);
82
82
  } catch (e) {
83
+ if (await actions.__redirectToSsoIfRequired(e)) {
84
+ return;
85
+ }
83
86
  setLoadingAction({
84
87
  loading: false,
85
88
  error: (0, _helpers.errorHandler)(e)
@@ -94,6 +94,9 @@ var _default = (store, api, sharedActions) => {
94
94
  });
95
95
  callback == null ? void 0 : callback(true);
96
96
  } catch (e) {
97
+ if (await actions.__redirectToSsoIfRequired(e)) {
98
+ return;
99
+ }
97
100
  setLoadingAction({
98
101
  loading: false,
99
102
  error: (0, _helpers.errorHandler)(e)
@@ -176,6 +176,9 @@ var _default = (store, api, sharedActions) => {
176
176
  }
177
177
  callback == null ? void 0 : callback(mfaState.recoveryCode);
178
178
  } catch (e) {
179
+ if (await actions.__redirectToSsoIfRequired(e)) {
180
+ return;
181
+ }
179
182
  setMfaState({
180
183
  loading: false,
181
184
  error: (0, _helpers.errorHandler)(e)
@@ -164,6 +164,9 @@ var _default = (store, api, sharedActions) => {
164
164
  loading: false
165
165
  });
166
166
  } catch (e) {
167
+ if (await actions.__redirectToSsoIfRequired(e)) {
168
+ return;
169
+ }
167
170
  setSocialLoginsState({
168
171
  loading: false,
169
172
  error: (0, _helpers.errorHandler)(e, 'Failed to authenticate'),
@@ -101,6 +101,11 @@ var _default = (store, api, sharedActions) => {
101
101
  callback == null ? void 0 : callback(true);
102
102
  } catch (e) {
103
103
  callback == null ? void 0 : callback(false);
104
+
105
+ // the session's tenant requires SSO: its SSO login steps the user up, instead of logging them out
106
+ if (await actions.__redirectToSsoIfRequired(e)) {
107
+ return;
108
+ }
104
109
  await __handleStepUpError(e);
105
110
  }
106
111
  };
@@ -59,10 +59,11 @@ const getUrlDerivedStepUp = () => {
59
59
 
60
60
  /**
61
61
  * Set the url and query params in the local storage FRONTEGG_AFTER_AUTH_REDIRECT_URL value
62
+ * @param urlStrategy 'hash' saves the route in the hash, which is the page the app shows
62
63
  */
63
64
  exports.getUrlDerivedStepUp = getUrlDerivedStepUp;
64
- function setAfterAuthRedirectUrlForStepUp() {
65
- const encodedRedirectUrl = window.location.pathname + window.location.search;
65
+ function setAfterAuthRedirectUrlForStepUp(urlStrategy = 'path') {
66
+ const encodedRedirectUrl = urlStrategy === 'hash' ? window.location.hash.substring(1) || '/' : window.location.pathname + window.location.search;
66
67
  window.localStorage.setItem(_constants.FRONTEGG_AFTER_AUTH_REDIRECT_URL, encodedRedirectUrl);
67
68
  }
68
69
 
@@ -57,6 +57,13 @@ var _default = (store, api, sharedActions) => {
57
57
  }) : actions.setAuthState({
58
58
  isLoading: false
59
59
  });
60
+ if (await actions.__redirectToSsoIfRequired(e)) {
61
+ // the tab comes back from SSO in the target tenant, not the one the failed switch restored
62
+ if (_restApi.ContextHolder.for(store.root.appName).isSessionPerTenantEnabled()) {
63
+ (0, _restApi.setTabTenantForSsoSwitch)(tenantId);
64
+ }
65
+ return;
66
+ }
60
67
  callback == null ? void 0 : callback(false, e);
61
68
  }
62
69
  };