@frontegg/redux-store 7.131.0-alpha.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.
@@ -151,6 +151,9 @@ export default ((store, api, sharedActions) => {
151
151
  });
152
152
  callback == null ? void 0 : callback(true);
153
153
  } catch (e) {
154
+ if (await actions.__redirectToSsoIfRequired(e)) {
155
+ return;
156
+ }
154
157
  setActivateState({
155
158
  loading: false,
156
159
  error: errorHandler(e)
@@ -188,6 +191,9 @@ export default ((store, api, sharedActions) => {
188
191
  });
189
192
  callback == null ? void 0 : callback(true);
190
193
  } catch (e) {
194
+ if (await actions.__redirectToSsoIfRequired(e)) {
195
+ return;
196
+ }
191
197
  setActivateState({
192
198
  submitting: false,
193
199
  loading: false,
@@ -3,7 +3,7 @@ import { createRandomString, generateCodeChallenge, GTMEventAction, isSsoEnforce
3
3
  import { HOSTED_LOGIN_VERIFIER_KEY } from '../../../constants';
4
4
  import { isEnforceSsoOnTenantAccessEnabled } from '@frontegg/rest-api';
5
5
  import { getBaseNameWithoutSlashSuffix, getSearchParam, isOauthCallbackRoute, TENANT_ID_PARAM_KEY } from '../helpers';
6
- import { getUri, getUrlDerivedStepUp, isSteppedUp, redirectByStepUpUrl } from '../../helpers';
6
+ import { getUri, getUrlDerivedStepUp, isSteppedUp, redirectByStepUpUrl, restoreTabTenantUnlessSsoCallback } from '../../helpers';
7
7
  import { SHOULD_STEP_UP_KEY } from '../../StepUpState/consts';
8
8
  import { FronteggNativeModule } from '../../../toolkit';
9
9
  export default ((store, api, sharedActions) => {
@@ -15,6 +15,7 @@ export default ((store, api, sharedActions) => {
15
15
  const __requestHostedLoginSilentAuthorize = async () => {
16
16
  var _response;
17
17
  let response;
18
+ restoreTabTenantUnlessSsoCallback(store);
18
19
  if (FronteggNativeModule.isGetTokensAvailable()) {
19
20
  const authApi = api.auth;
20
21
  response = await refreshTokenWithRetry(async () => {
@@ -26,6 +27,10 @@ export default ((store, api, sharedActions) => {
26
27
  response = await api.auth.silentOAuthRefreshTokenV2();
27
28
  } catch (error) {
28
29
  var _store$auth$user, _error$errorContext;
30
+ // the tenant requires SSO: hosted login for that tenant, not the plain one the caller falls back to
31
+ if (await actions.__redirectToSsoIfRequired(error)) {
32
+ return;
33
+ }
29
34
  const tenantId = (_store$auth$user = store.auth.user) == null ? void 0 : _store$auth$user.tenantId;
30
35
  if (!isEnforceSsoOnTenantAccessEnabled(store.root.appName) || !isSsoEnforcementError(error) || ((_error$errorContext = error.errorContext) == null ? void 0 : _error$errorContext.ssoRequired) !== false || !tenantId) {
31
36
  throw error;
@@ -1,21 +1,20 @@
1
- import _extends from "@babel/runtime/helpers/esm/extends";
2
1
  import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
3
- const _excluded = ["authenticatedUrl"],
4
- _excluded2 = ["callback", "error"],
2
+ import _extends from "@babel/runtime/helpers/esm/extends";
3
+ const _excluded = ["callback", "error"],
4
+ _excluded2 = ["callback"],
5
5
  _excluded3 = ["callback"],
6
6
  _excluded4 = ["callback"],
7
7
  _excluded5 = ["callback"],
8
- _excluded6 = ["callback"],
9
- _excluded7 = ["callback", "events"],
8
+ _excluded6 = ["callback", "events"],
9
+ _excluded7 = ["callback"],
10
10
  _excluded8 = ["callback"],
11
- _excluded9 = ["callback"],
12
- _excluded10 = ["user"],
11
+ _excluded9 = ["user"],
12
+ _excluded10 = ["callback"],
13
13
  _excluded11 = ["callback"],
14
14
  _excluded12 = ["callback"],
15
15
  _excluded13 = ["callback"],
16
16
  _excluded14 = ["callback"],
17
- _excluded15 = ["callback"],
18
- _excluded16 = ["callback"];
17
+ _excluded15 = ["callback"];
19
18
  import afterAuthNavigationActions from './afterAuthNavigation.actions';
20
19
  import handleVerifyMFAResponseActions from './handleVerifyMFAResponse.actions';
21
20
  import mfaRequiredStateActions from './mfaRequiredState.actions';
@@ -33,8 +32,8 @@ import { clearHostedOAuthState } from '../oauthStorage';
33
32
  import { SHOULD_STEP_UP_KEY } from '../../StepUpState/consts';
34
33
  import { FronteggNativeModule, isEntitlementsDeeplyEqual } from '../../../toolkit';
35
34
  import { REQUEST_NAME, UserVerifiedOriginTypes } from '../../interfaces';
36
- import { authStrategyLoginStepMap, defaultFronteggRoutes, defaultOnRedirectTo } from '../consts';
37
- import { isMfaRequired, isResetPasswordRequired, setAfterAuthRedirectUrlForStepUp } from '../../helpers';
35
+ import { authStrategyLoginStepMap, defaultOnRedirectTo } from '../consts';
36
+ import { isMfaRequired, isOnAuthPageRoute, isOnOidcCallbackRoute, isResetPasswordRequired, restoreTabTenantUnlessSsoCallback, setAfterAuthRedirectUrlForStepUp } from '../../helpers';
38
37
  import { MFAStep } from '../../MfaState/interfaces';
39
38
  import { SamlVendors } from '../../SSOState/interfaces';
40
39
  import { DEFAULT_RETRY_CONFIG } from '../../../constants';
@@ -48,17 +47,44 @@ export default ((store, api, sharedActions) => {
48
47
  * Sends the user to log in through the tenant's SSO and back to the current page afterwards.
49
48
  * @protected
50
49
  */
50
+ // as preLogin (takeLeading): an SSO redirect started while another is in flight is dropped; the first one leaves the
51
+ // page, and a second hosted one would replace the PKCE verifier the first one's callback needs
52
+ let ssoRedirectInFlight = false;
51
53
  const __redirectToSsoForTenant = async ({
52
54
  tenantId,
53
55
  address
54
56
  }) => {
57
+ if (ssoRedirectInFlight) {
58
+ return;
59
+ }
60
+ ssoRedirectInFlight = true;
61
+ try {
62
+ await __redirectToSsoForTenantNow({
63
+ tenantId,
64
+ address
65
+ });
66
+ } finally {
67
+ ssoRedirectInFlight = false;
68
+ }
69
+ };
70
+ const __redirectToSsoForTenantNow = async ({
71
+ tenantId,
72
+ address
73
+ }) => {
74
+ var _store$root$urlStrate;
75
+ // the login box shows login's "redirecting to SSO" step instead of the screen that was refused (its spinner and
76
+ // a previous error)
77
+ setLoginState({
78
+ step: LoginStep.redirectToSSO,
79
+ loading: false,
80
+ error: undefined
81
+ });
55
82
  // on an auth page (login, tenant chooser, social / magic link / SSO callback) keep the destination the login
56
83
  // already saved: the page itself is not one to come back to
57
- const _defaultFronteggRoute = _extends({}, defaultFronteggRoutes, store.auth.routes),
58
- authRoutes = _objectWithoutPropertiesLoose(_defaultFronteggRoute, _excluded);
59
- const isOnAuthPage = Object.values(authRoutes).some(route => typeof route === 'string' && !!route && window.location.pathname.endsWith(route));
84
+ const urlStrategy = (_store$root$urlStrate = store.root.urlStrategy) != null ? _store$root$urlStrate : 'path';
85
+ const isOnAuthPage = isOnAuthPageRoute(urlStrategy, store.auth.routes);
60
86
  if (!isOnAuthPage) {
61
- setAfterAuthRedirectUrlForStepUp();
87
+ setAfterAuthRedirectUrlForStepUp(urlStrategy);
62
88
  }
63
89
  if (store.auth.hostedLoginBox) {
64
90
  var _window$localStorage;
@@ -72,9 +98,12 @@ export default ((store, api, sharedActions) => {
72
98
  });
73
99
  return;
74
100
  }
75
- store.auth.onRedirectTo(address, {
76
- refresh: true
77
- });
101
+ // as login does for an OIDC SSO page without redirect_uri; the refusal has no idpType, but an OIDC page asks for a code
102
+ let ssoRedirectUrl = address;
103
+ if (/[?&]response_type=code(&|$)/.test(address) && !address.includes('redirect_uri')) {
104
+ ssoRedirectUrl += `&redirect_uri=${window.location.origin}${store.auth.routes.oidcRedirectUrl}`;
105
+ }
106
+ redirectToFullPage(ssoRedirectUrl);
78
107
  };
79
108
 
80
109
  /**
@@ -84,7 +113,8 @@ export default ((store, api, sharedActions) => {
84
113
  * @protected
85
114
  */
86
115
  const __redirectToSsoIfRequired = async e => {
87
- if (!isEnforceSsoOnTenantAccessEnabled(store.root.appName) || !isSsoEnforcementError(e) || !e.errorContext.ssoRequired) {
116
+ var _e$errorContext;
117
+ if (!isEnforceSsoOnTenantAccessEnabled(store.root.appName) || !isSsoEnforcementError(e) || !((_e$errorContext = e.errorContext) != null && _e$errorContext.ssoRequired)) {
88
118
  return false;
89
119
  }
90
120
  await __redirectToSsoForTenant(e.errorContext);
@@ -215,8 +245,9 @@ export default ((store, api, sharedActions) => {
215
245
  });
216
246
  }
217
247
  } catch (e) {
218
- // a refused identity refresh has already used up the refresh token: no retry, only the SSO redirect
219
- if (await __redirectToSsoIfRequired(e)) {
248
+ // a refused identity refresh has already used up the refresh token: no retry, only the SSO redirect, except on
249
+ // the OIDC callback, where the page itself completes the OIDC login
250
+ if (isEnforceSsoOnTenantAccessEnabled(store.root.appName) && !isOnOidcCallbackRoute(store) && (await __redirectToSsoIfRequired(e))) {
220
251
  return;
221
252
  }
222
253
  contextHolder.setAccessToken(null);
@@ -263,6 +294,7 @@ export default ((store, api, sharedActions) => {
263
294
 
264
295
  /** @protected */
265
296
  const __refreshToken = async () => {
297
+ restoreTabTenantUnlessSsoCallback(store);
266
298
  if (FronteggNativeModule.isGetTokensAvailable()) {
267
299
  await __refreshTokenWithNativeTokens();
268
300
  return;
@@ -455,7 +487,7 @@ export default ((store, api, sharedActions) => {
455
487
  callback,
456
488
  error
457
489
  } = _ref3,
458
- body = _objectWithoutPropertiesLoose(_ref3, _excluded2);
490
+ body = _objectWithoutPropertiesLoose(_ref3, _excluded);
459
491
  const publicPolicy = store.auth.securityPolicyState.publicPolicy.policy;
460
492
  if (!(publicPolicy != null && publicPolicy.authStrategy)) {
461
493
  actions.setLoginState({
@@ -851,7 +883,7 @@ export default ((store, api, sharedActions) => {
851
883
  const {
852
884
  callback
853
885
  } = _payload,
854
- payload = _objectWithoutPropertiesLoose(_payload, _excluded3);
886
+ payload = _objectWithoutPropertiesLoose(_payload, _excluded2);
855
887
  try {
856
888
  setLoginState({
857
889
  loading: true
@@ -881,7 +913,7 @@ export default ((store, api, sharedActions) => {
881
913
  const {
882
914
  callback
883
915
  } = _payload,
884
- payload = _objectWithoutPropertiesLoose(_payload, _excluded4);
916
+ payload = _objectWithoutPropertiesLoose(_payload, _excluded3);
885
917
  try {
886
918
  setLoginState({
887
919
  loading: true
@@ -906,7 +938,7 @@ export default ((store, api, sharedActions) => {
906
938
  const {
907
939
  callback
908
940
  } = _payload,
909
- payload = _objectWithoutPropertiesLoose(_payload, _excluded5);
941
+ payload = _objectWithoutPropertiesLoose(_payload, _excluded4);
910
942
  try {
911
943
  setLoginState({
912
944
  loading: true
@@ -927,7 +959,7 @@ export default ((store, api, sharedActions) => {
927
959
  const {
928
960
  callback
929
961
  } = _payload,
930
- payload = _objectWithoutPropertiesLoose(_payload, _excluded6);
962
+ payload = _objectWithoutPropertiesLoose(_payload, _excluded5);
931
963
  try {
932
964
  var _username;
933
965
  setLoginState({
@@ -971,7 +1003,7 @@ export default ((store, api, sharedActions) => {
971
1003
  callback,
972
1004
  events
973
1005
  } = _payload,
974
- payload = _objectWithoutPropertiesLoose(_payload, _excluded7);
1006
+ payload = _objectWithoutPropertiesLoose(_payload, _excluded6);
975
1007
  try {
976
1008
  setLoginState({
977
1009
  loading: true
@@ -1102,7 +1134,7 @@ export default ((store, api, sharedActions) => {
1102
1134
  const {
1103
1135
  callback
1104
1136
  } = payload,
1105
- body = _objectWithoutPropertiesLoose(payload, _excluded8);
1137
+ body = _objectWithoutPropertiesLoose(payload, _excluded7);
1106
1138
  try {
1107
1139
  var _options$allowCredent;
1108
1140
  actions.setPasskeysState({
@@ -1142,7 +1174,7 @@ export default ((store, api, sharedActions) => {
1142
1174
  const {
1143
1175
  callback
1144
1176
  } = payload,
1145
- body = _objectWithoutPropertiesLoose(payload, _excluded9);
1177
+ body = _objectWithoutPropertiesLoose(payload, _excluded8);
1146
1178
  try {
1147
1179
  var _publicKey$response$u;
1148
1180
  actions.setPasskeysState({
@@ -1162,7 +1194,7 @@ export default ((store, api, sharedActions) => {
1162
1194
  {
1163
1195
  user
1164
1196
  } = _await$api$auth$webAu,
1165
- rest = _objectWithoutPropertiesLoose(_await$api$auth$webAu, _excluded10);
1197
+ rest = _objectWithoutPropertiesLoose(_await$api$auth$webAu, _excluded9);
1166
1198
  if (isMfaRequired(user, store.root.appName)) {
1167
1199
  const {
1168
1200
  routes,
@@ -1249,7 +1281,7 @@ export default ((store, api, sharedActions) => {
1249
1281
  const {
1250
1282
  callback
1251
1283
  } = payload,
1252
- body = _objectWithoutPropertiesLoose(payload, _excluded11);
1284
+ body = _objectWithoutPropertiesLoose(payload, _excluded10);
1253
1285
  try {
1254
1286
  setLoginState({
1255
1287
  loading: true
@@ -1314,7 +1346,7 @@ export default ((store, api, sharedActions) => {
1314
1346
  const {
1315
1347
  callback
1316
1348
  } = _payload,
1317
- payload = _objectWithoutPropertiesLoose(_payload, _excluded12);
1349
+ payload = _objectWithoutPropertiesLoose(_payload, _excluded11);
1318
1350
  setLoginState({
1319
1351
  loading: true
1320
1352
  });
@@ -1349,7 +1381,7 @@ export default ((store, api, sharedActions) => {
1349
1381
  const {
1350
1382
  callback
1351
1383
  } = _payload,
1352
- payload = _objectWithoutPropertiesLoose(_payload, _excluded13);
1384
+ payload = _objectWithoutPropertiesLoose(_payload, _excluded12);
1353
1385
  setLoginState({
1354
1386
  loading: true
1355
1387
  });
@@ -1364,6 +1396,9 @@ export default ((store, api, sharedActions) => {
1364
1396
  });
1365
1397
  callback == null ? void 0 : callback(true);
1366
1398
  } catch (e) {
1399
+ if (await __redirectToSsoIfRequired(e)) {
1400
+ return;
1401
+ }
1367
1402
  setLoginState({
1368
1403
  loading: false,
1369
1404
  error: errorHandler(e)
@@ -1375,7 +1410,7 @@ export default ((store, api, sharedActions) => {
1375
1410
  const {
1376
1411
  callback
1377
1412
  } = _payload,
1378
- payload = _objectWithoutPropertiesLoose(_payload, _excluded14);
1413
+ payload = _objectWithoutPropertiesLoose(_payload, _excluded13);
1379
1414
  setLoginState({
1380
1415
  loading: true
1381
1416
  });
@@ -1387,6 +1422,9 @@ export default ((store, api, sharedActions) => {
1387
1422
  });
1388
1423
  callback == null ? void 0 : callback(true);
1389
1424
  } catch (e) {
1425
+ if (await __redirectToSsoIfRequired(e)) {
1426
+ return;
1427
+ }
1390
1428
  setLoginState({
1391
1429
  loading: false,
1392
1430
  error: errorHandler(e)
@@ -1398,7 +1436,7 @@ export default ((store, api, sharedActions) => {
1398
1436
  const {
1399
1437
  callback
1400
1438
  } = _payload,
1401
- payload = _objectWithoutPropertiesLoose(_payload, _excluded15);
1439
+ payload = _objectWithoutPropertiesLoose(_payload, _excluded14);
1402
1440
  setLoginState({
1403
1441
  loading: true
1404
1442
  });
@@ -1425,7 +1463,7 @@ export default ((store, api, sharedActions) => {
1425
1463
  const {
1426
1464
  callback
1427
1465
  } = _payload,
1428
- payload = _objectWithoutPropertiesLoose(_payload, _excluded16);
1466
+ payload = _objectWithoutPropertiesLoose(_payload, _excluded15);
1429
1467
  setLoginState({
1430
1468
  loading: true
1431
1469
  });
@@ -1437,6 +1475,9 @@ export default ((store, api, sharedActions) => {
1437
1475
  });
1438
1476
  callback == null ? void 0 : callback(true);
1439
1477
  } catch (e) {
1478
+ if (await __redirectToSsoIfRequired(e)) {
1479
+ return;
1480
+ }
1440
1481
  setLoginState({
1441
1482
  loading: false,
1442
1483
  error: errorHandler(e)
@@ -169,6 +169,9 @@ export default ((store, api, sharedActions) => {
169
169
  }
170
170
  callback == null ? void 0 : callback(mfaState.recoveryCode);
171
171
  } catch (e) {
172
+ if (await actions.__redirectToSsoIfRequired(e)) {
173
+ return;
174
+ }
172
175
  setMfaState({
173
176
  loading: false,
174
177
  error: errorHandler(e)
@@ -95,6 +95,11 @@ export default ((store, api, sharedActions) => {
95
95
  callback == null ? void 0 : callback(true);
96
96
  } catch (e) {
97
97
  callback == null ? void 0 : callback(false);
98
+
99
+ // the session's tenant requires SSO: its SSO login steps the user up, instead of logging them out
100
+ if (await actions.__redirectToSsoIfRequired(e)) {
101
+ return;
102
+ }
98
103
  await __handleStepUpError(e);
99
104
  }
100
105
  };
@@ -22,8 +22,9 @@ export interface UrlDerivedStepUp {
22
22
  export declare const getUrlDerivedStepUp: () => UrlDerivedStepUp;
23
23
  /**
24
24
  * Set the url and query params in the local storage FRONTEGG_AFTER_AUTH_REDIRECT_URL value
25
+ * @param urlStrategy 'hash' saves the route in the hash, which is the page the app shows
25
26
  */
26
- export declare function setAfterAuthRedirectUrlForStepUp(): void;
27
+ export declare function setAfterAuthRedirectUrlForStepUp(urlStrategy?: 'path' | 'hash'): void;
27
28
  /**
28
29
  * Redirects to the step up url with the max age param and set the redirect url in the local storage
29
30
  * The redirect url will be used after the step up flow is done
@@ -51,9 +51,10 @@ export const getUrlDerivedStepUp = () => {
51
51
 
52
52
  /**
53
53
  * Set the url and query params in the local storage FRONTEGG_AFTER_AUTH_REDIRECT_URL value
54
+ * @param urlStrategy 'hash' saves the route in the hash, which is the page the app shows
54
55
  */
55
- export function setAfterAuthRedirectUrlForStepUp() {
56
- const encodedRedirectUrl = window.location.pathname + window.location.search;
56
+ export function setAfterAuthRedirectUrlForStepUp(urlStrategy = 'path') {
57
+ const encodedRedirectUrl = urlStrategy === 'hash' ? window.location.hash.substring(1) || '/' : window.location.pathname + window.location.search;
57
58
  window.localStorage.setItem(FRONTEGG_AFTER_AUTH_REDIRECT_URL, encodedRedirectUrl);
58
59
  }
59
60
 
@@ -1,6 +1,6 @@
1
1
  import { initialState } from './state';
2
2
  import { deepResetState } from '../../helpers';
3
- import { ContextHolder, getCurrentUserTenantsFunction, setTabTenantInSessionStorage } from '@frontegg/rest-api';
3
+ import { ContextHolder, getCurrentUserTenantsFunction, setTabTenantForSsoSwitch } from '@frontegg/rest-api';
4
4
  export default ((store, api, sharedActions) => {
5
5
  const actions = sharedActions;
6
6
  const setTenantsState = state => {
@@ -54,7 +54,7 @@ export default ((store, api, sharedActions) => {
54
54
  if (await actions.__redirectToSsoIfRequired(e)) {
55
55
  // the tab comes back from SSO in the target tenant, not the one the failed switch restored
56
56
  if (ContextHolder.for(store.root.appName).isSessionPerTenantEnabled()) {
57
- setTabTenantInSessionStorage(tenantId);
57
+ setTabTenantForSsoSwitch(tenantId);
58
58
  }
59
59
  return;
60
60
  }
package/auth/helpers.d.ts CHANGED
@@ -8,5 +8,20 @@ export declare const isMfaRequired: (user: ILoginResponse, appName: string) => b
8
8
  export declare const isResetPasswordRequired: (user: ILoginResponse, appName: string) => boolean;
9
9
  export declare const mapMetaDataObjectToActions: (obj: any, path?: string[]) => CommitChangeDto[];
10
10
  export declare const getUri: (urlStrategy: FronteggState["root"]["urlStrategy"]) => string;
11
+ /**
12
+ * On the OIDC callback the page still has to finish the OIDC login with its code: a refresh refused before that is not
13
+ * a reason to go back to the IdP, which would send the user straight back here, forever.
14
+ */
15
+ export declare const isOnOidcCallbackRoute: (store: FronteggState) => boolean;
16
+ /**
17
+ * After a switch refused for SSO the tab points at the SSO tenant. Its SSO login comes back on a callback route and
18
+ * keeps it; any other page load means the user didn't finish that login, so the tab goes back to its tenant.
19
+ */
20
+ export declare const restoreTabTenantUnlessSsoCallback: (store: FronteggState) => void;
11
21
  export declare const prepareIdentifiersForSignup: (payload: ISignUpUser) => ISignUpUser;
22
+ /**
23
+ * Whether the current route is an auth page (login, tenant chooser, social / magic link / SSO callback), not one to come
24
+ * back to. Unlike isAuthRoute: after-login / sign-up destinations don't count, and endsWith lets a basename prefix it.
25
+ */
26
+ export declare const isOnAuthPageRoute: (urlStrategy: FronteggState["root"]["urlStrategy"], routes?: Partial<AuthPageRoutes>) => boolean;
12
27
  export declare const isAuthRoute: (path: string, routes?: Partial<AuthPageRoutes>) => boolean;
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);
package/index.js CHANGED
@@ -1,4 +1,4 @@
1
- /** @license Frontegg v7.131.0-alpha.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.
@@ -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,
@@ -22,6 +22,7 @@ var _default = (store, api, sharedActions) => {
22
22
  const __requestHostedLoginSilentAuthorize = async () => {
23
23
  var _response;
24
24
  let response;
25
+ (0, _helpers3.restoreTabTenantUnlessSsoCallback)(store);
25
26
  if (_toolkit.FronteggNativeModule.isGetTokensAvailable()) {
26
27
  const authApi = api.auth;
27
28
  response = await (0, _helpers.refreshTokenWithRetry)(async () => {
@@ -33,6 +34,10 @@ var _default = (store, api, sharedActions) => {
33
34
  response = await api.auth.silentOAuthRefreshTokenV2();
34
35
  } catch (error) {
35
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
+ }
36
41
  const tenantId = (_store$auth$user = store.auth.user) == null ? void 0 : _store$auth$user.tenantId;
37
42
  if (!(0, _restApi.isEnforceSsoOnTenantAccessEnabled)(store.root.appName) || !(0, _helpers.isSsoEnforcementError)(error) || ((_error$errorContext = error.errorContext) == null ? void 0 : _error$errorContext.ssoRequired) !== false || !tenantId) {
38
43
  throw error;
@@ -5,8 +5,8 @@ Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
7
  exports.default = void 0;
8
- var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
9
8
  var _objectWithoutPropertiesLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutPropertiesLoose"));
9
+ var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
10
10
  var _afterAuthNavigation = _interopRequireDefault(require("./afterAuthNavigation.actions"));
11
11
  var _handleVerifyMFAResponse = _interopRequireDefault(require("./handleVerifyMFAResponse.actions"));
12
12
  var _mfaRequiredState = _interopRequireDefault(require("./mfaRequiredState.actions"));
@@ -29,22 +29,21 @@ var _helpers3 = require("../../helpers");
29
29
  var _interfaces3 = require("../../MfaState/interfaces");
30
30
  var _interfaces4 = require("../../SSOState/interfaces");
31
31
  var _constants = require("../../../constants");
32
- const _excluded = ["authenticatedUrl"],
33
- _excluded2 = ["callback", "error"],
32
+ const _excluded = ["callback", "error"],
33
+ _excluded2 = ["callback"],
34
34
  _excluded3 = ["callback"],
35
35
  _excluded4 = ["callback"],
36
36
  _excluded5 = ["callback"],
37
- _excluded6 = ["callback"],
38
- _excluded7 = ["callback", "events"],
37
+ _excluded6 = ["callback", "events"],
38
+ _excluded7 = ["callback"],
39
39
  _excluded8 = ["callback"],
40
- _excluded9 = ["callback"],
41
- _excluded10 = ["user"],
40
+ _excluded9 = ["user"],
41
+ _excluded10 = ["callback"],
42
42
  _excluded11 = ["callback"],
43
43
  _excluded12 = ["callback"],
44
44
  _excluded13 = ["callback"],
45
45
  _excluded14 = ["callback"],
46
- _excluded15 = ["callback"],
47
- _excluded16 = ["callback"];
46
+ _excluded15 = ["callback"];
48
47
  const SSO_REDIRECT_DELAY_MS = 2000;
49
48
  const isUsableRedirect = onRedirectTo => typeof onRedirectTo === 'function' && onRedirectTo !== _consts2.defaultOnRedirectTo;
50
49
  var _default = (store, api, sharedActions) => {
@@ -55,17 +54,44 @@ var _default = (store, api, sharedActions) => {
55
54
  * Sends the user to log in through the tenant's SSO and back to the current page afterwards.
56
55
  * @protected
57
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;
58
60
  const __redirectToSsoForTenant = async ({
59
61
  tenantId,
60
62
  address
61
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
+ });
62
89
  // on an auth page (login, tenant chooser, social / magic link / SSO callback) keep the destination the login
63
90
  // already saved: the page itself is not one to come back to
64
- const _defaultFronteggRoute = (0, _extends2.default)({}, _consts2.defaultFronteggRoutes, store.auth.routes),
65
- authRoutes = (0, _objectWithoutPropertiesLoose2.default)(_defaultFronteggRoute, _excluded);
66
- const isOnAuthPage = Object.values(authRoutes).some(route => typeof route === 'string' && !!route && window.location.pathname.endsWith(route));
91
+ const urlStrategy = (_store$root$urlStrate = store.root.urlStrategy) != null ? _store$root$urlStrate : 'path';
92
+ const isOnAuthPage = (0, _helpers3.isOnAuthPageRoute)(urlStrategy, store.auth.routes);
67
93
  if (!isOnAuthPage) {
68
- (0, _helpers3.setAfterAuthRedirectUrlForStepUp)();
94
+ (0, _helpers3.setAfterAuthRedirectUrlForStepUp)(urlStrategy);
69
95
  }
70
96
  if (store.auth.hostedLoginBox) {
71
97
  var _window$localStorage;
@@ -79,9 +105,12 @@ var _default = (store, api, sharedActions) => {
79
105
  });
80
106
  return;
81
107
  }
82
- store.auth.onRedirectTo(address, {
83
- refresh: true
84
- });
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);
85
114
  };
86
115
 
87
116
  /**
@@ -91,7 +120,8 @@ var _default = (store, api, sharedActions) => {
91
120
  * @protected
92
121
  */
93
122
  const __redirectToSsoIfRequired = async e => {
94
- if (!(0, _restApi.isEnforceSsoOnTenantAccessEnabled)(store.root.appName) || !(0, _helpers.isSsoEnforcementError)(e) || !e.errorContext.ssoRequired) {
123
+ var _e$errorContext;
124
+ if (!(0, _restApi.isEnforceSsoOnTenantAccessEnabled)(store.root.appName) || !(0, _helpers.isSsoEnforcementError)(e) || !((_e$errorContext = e.errorContext) != null && _e$errorContext.ssoRequired)) {
95
125
  return false;
96
126
  }
97
127
  await __redirectToSsoForTenant(e.errorContext);
@@ -222,8 +252,9 @@ var _default = (store, api, sharedActions) => {
222
252
  });
223
253
  }
224
254
  } catch (e) {
225
- // a refused identity refresh has already used up the refresh token: no retry, only the SSO redirect
226
- if (await __redirectToSsoIfRequired(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))) {
227
258
  return;
228
259
  }
229
260
  contextHolder.setAccessToken(null);
@@ -270,6 +301,7 @@ var _default = (store, api, sharedActions) => {
270
301
 
271
302
  /** @protected */
272
303
  const __refreshToken = async () => {
304
+ (0, _helpers3.restoreTabTenantUnlessSsoCallback)(store);
273
305
  if (_toolkit.FronteggNativeModule.isGetTokensAvailable()) {
274
306
  await __refreshTokenWithNativeTokens();
275
307
  return;
@@ -462,7 +494,7 @@ var _default = (store, api, sharedActions) => {
462
494
  callback,
463
495
  error
464
496
  } = _ref3,
465
- body = (0, _objectWithoutPropertiesLoose2.default)(_ref3, _excluded2);
497
+ body = (0, _objectWithoutPropertiesLoose2.default)(_ref3, _excluded);
466
498
  const publicPolicy = store.auth.securityPolicyState.publicPolicy.policy;
467
499
  if (!(publicPolicy != null && publicPolicy.authStrategy)) {
468
500
  actions.setLoginState({
@@ -858,7 +890,7 @@ var _default = (store, api, sharedActions) => {
858
890
  const {
859
891
  callback
860
892
  } = _payload,
861
- payload = (0, _objectWithoutPropertiesLoose2.default)(_payload, _excluded3);
893
+ payload = (0, _objectWithoutPropertiesLoose2.default)(_payload, _excluded2);
862
894
  try {
863
895
  setLoginState({
864
896
  loading: true
@@ -888,7 +920,7 @@ var _default = (store, api, sharedActions) => {
888
920
  const {
889
921
  callback
890
922
  } = _payload,
891
- payload = (0, _objectWithoutPropertiesLoose2.default)(_payload, _excluded4);
923
+ payload = (0, _objectWithoutPropertiesLoose2.default)(_payload, _excluded3);
892
924
  try {
893
925
  setLoginState({
894
926
  loading: true
@@ -913,7 +945,7 @@ var _default = (store, api, sharedActions) => {
913
945
  const {
914
946
  callback
915
947
  } = _payload,
916
- payload = (0, _objectWithoutPropertiesLoose2.default)(_payload, _excluded5);
948
+ payload = (0, _objectWithoutPropertiesLoose2.default)(_payload, _excluded4);
917
949
  try {
918
950
  setLoginState({
919
951
  loading: true
@@ -934,7 +966,7 @@ var _default = (store, api, sharedActions) => {
934
966
  const {
935
967
  callback
936
968
  } = _payload,
937
- payload = (0, _objectWithoutPropertiesLoose2.default)(_payload, _excluded6);
969
+ payload = (0, _objectWithoutPropertiesLoose2.default)(_payload, _excluded5);
938
970
  try {
939
971
  var _username;
940
972
  setLoginState({
@@ -978,7 +1010,7 @@ var _default = (store, api, sharedActions) => {
978
1010
  callback,
979
1011
  events
980
1012
  } = _payload,
981
- payload = (0, _objectWithoutPropertiesLoose2.default)(_payload, _excluded7);
1013
+ payload = (0, _objectWithoutPropertiesLoose2.default)(_payload, _excluded6);
982
1014
  try {
983
1015
  setLoginState({
984
1016
  loading: true
@@ -1109,7 +1141,7 @@ var _default = (store, api, sharedActions) => {
1109
1141
  const {
1110
1142
  callback
1111
1143
  } = payload,
1112
- body = (0, _objectWithoutPropertiesLoose2.default)(payload, _excluded8);
1144
+ body = (0, _objectWithoutPropertiesLoose2.default)(payload, _excluded7);
1113
1145
  try {
1114
1146
  var _options$allowCredent;
1115
1147
  actions.setPasskeysState({
@@ -1149,7 +1181,7 @@ var _default = (store, api, sharedActions) => {
1149
1181
  const {
1150
1182
  callback
1151
1183
  } = payload,
1152
- body = (0, _objectWithoutPropertiesLoose2.default)(payload, _excluded9);
1184
+ body = (0, _objectWithoutPropertiesLoose2.default)(payload, _excluded8);
1153
1185
  try {
1154
1186
  var _publicKey$response$u;
1155
1187
  actions.setPasskeysState({
@@ -1169,7 +1201,7 @@ var _default = (store, api, sharedActions) => {
1169
1201
  {
1170
1202
  user
1171
1203
  } = _await$api$auth$webAu,
1172
- rest = (0, _objectWithoutPropertiesLoose2.default)(_await$api$auth$webAu, _excluded10);
1204
+ rest = (0, _objectWithoutPropertiesLoose2.default)(_await$api$auth$webAu, _excluded9);
1173
1205
  if ((0, _helpers3.isMfaRequired)(user, store.root.appName)) {
1174
1206
  const {
1175
1207
  routes,
@@ -1256,7 +1288,7 @@ var _default = (store, api, sharedActions) => {
1256
1288
  const {
1257
1289
  callback
1258
1290
  } = payload,
1259
- body = (0, _objectWithoutPropertiesLoose2.default)(payload, _excluded11);
1291
+ body = (0, _objectWithoutPropertiesLoose2.default)(payload, _excluded10);
1260
1292
  try {
1261
1293
  setLoginState({
1262
1294
  loading: true
@@ -1321,7 +1353,7 @@ var _default = (store, api, sharedActions) => {
1321
1353
  const {
1322
1354
  callback
1323
1355
  } = _payload,
1324
- payload = (0, _objectWithoutPropertiesLoose2.default)(_payload, _excluded12);
1356
+ payload = (0, _objectWithoutPropertiesLoose2.default)(_payload, _excluded11);
1325
1357
  setLoginState({
1326
1358
  loading: true
1327
1359
  });
@@ -1356,7 +1388,7 @@ var _default = (store, api, sharedActions) => {
1356
1388
  const {
1357
1389
  callback
1358
1390
  } = _payload,
1359
- payload = (0, _objectWithoutPropertiesLoose2.default)(_payload, _excluded13);
1391
+ payload = (0, _objectWithoutPropertiesLoose2.default)(_payload, _excluded12);
1360
1392
  setLoginState({
1361
1393
  loading: true
1362
1394
  });
@@ -1371,6 +1403,9 @@ var _default = (store, api, sharedActions) => {
1371
1403
  });
1372
1404
  callback == null ? void 0 : callback(true);
1373
1405
  } catch (e) {
1406
+ if (await __redirectToSsoIfRequired(e)) {
1407
+ return;
1408
+ }
1374
1409
  setLoginState({
1375
1410
  loading: false,
1376
1411
  error: (0, _helpers.errorHandler)(e)
@@ -1382,7 +1417,7 @@ var _default = (store, api, sharedActions) => {
1382
1417
  const {
1383
1418
  callback
1384
1419
  } = _payload,
1385
- payload = (0, _objectWithoutPropertiesLoose2.default)(_payload, _excluded14);
1420
+ payload = (0, _objectWithoutPropertiesLoose2.default)(_payload, _excluded13);
1386
1421
  setLoginState({
1387
1422
  loading: true
1388
1423
  });
@@ -1394,6 +1429,9 @@ var _default = (store, api, sharedActions) => {
1394
1429
  });
1395
1430
  callback == null ? void 0 : callback(true);
1396
1431
  } catch (e) {
1432
+ if (await __redirectToSsoIfRequired(e)) {
1433
+ return;
1434
+ }
1397
1435
  setLoginState({
1398
1436
  loading: false,
1399
1437
  error: (0, _helpers.errorHandler)(e)
@@ -1405,7 +1443,7 @@ var _default = (store, api, sharedActions) => {
1405
1443
  const {
1406
1444
  callback
1407
1445
  } = _payload,
1408
- payload = (0, _objectWithoutPropertiesLoose2.default)(_payload, _excluded15);
1446
+ payload = (0, _objectWithoutPropertiesLoose2.default)(_payload, _excluded14);
1409
1447
  setLoginState({
1410
1448
  loading: true
1411
1449
  });
@@ -1432,7 +1470,7 @@ var _default = (store, api, sharedActions) => {
1432
1470
  const {
1433
1471
  callback
1434
1472
  } = _payload,
1435
- payload = (0, _objectWithoutPropertiesLoose2.default)(_payload, _excluded16);
1473
+ payload = (0, _objectWithoutPropertiesLoose2.default)(_payload, _excluded15);
1436
1474
  setLoginState({
1437
1475
  loading: true
1438
1476
  });
@@ -1444,6 +1482,9 @@ var _default = (store, api, sharedActions) => {
1444
1482
  });
1445
1483
  callback == null ? void 0 : callback(true);
1446
1484
  } catch (e) {
1485
+ if (await __redirectToSsoIfRequired(e)) {
1486
+ return;
1487
+ }
1447
1488
  setLoginState({
1448
1489
  loading: false,
1449
1490
  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)
@@ -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
 
@@ -60,7 +60,7 @@ var _default = (store, api, sharedActions) => {
60
60
  if (await actions.__redirectToSsoIfRequired(e)) {
61
61
  // the tab comes back from SSO in the target tenant, not the one the failed switch restored
62
62
  if (_restApi.ContextHolder.for(store.root.appName).isSessionPerTenantEnabled()) {
63
- (0, _restApi.setTabTenantInSessionStorage)(tenantId);
63
+ (0, _restApi.setTabTenantForSsoSwitch)(tenantId);
64
64
  }
65
65
  return;
66
66
  }
@@ -9,12 +9,15 @@ var _exportNames = {
9
9
  isResetPasswordRequired: true,
10
10
  mapMetaDataObjectToActions: true,
11
11
  getUri: true,
12
+ isOnOidcCallbackRoute: true,
13
+ restoreTabTenantUnlessSsoCallback: true,
12
14
  prepareIdentifiersForSignup: true,
15
+ isOnAuthPageRoute: true,
13
16
  isAuthRoute: true
14
17
  };
15
- exports.prepareIdentifiersForSignup = exports.mapMetaDataObjectToActions = exports.isResetPasswordRequired = exports.isMfaRequired = exports.isAuthRoute = exports.getUri = void 0;
16
- var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
18
+ exports.restoreTabTenantUnlessSsoCallback = exports.prepareIdentifiersForSignup = exports.mapMetaDataObjectToActions = exports.isResetPasswordRequired = exports.isOnOidcCallbackRoute = exports.isOnAuthPageRoute = exports.isMfaRequired = exports.isAuthRoute = exports.getUri = void 0;
17
19
  var _objectWithoutPropertiesLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutPropertiesLoose"));
20
+ var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
18
21
  var _restApi = require("@frontegg/rest-api");
19
22
  var _consts = require("./LoginState/consts");
20
23
  var _helpers = require("./Entitlements/helpers");
@@ -104,7 +107,55 @@ const getUri = urlStrategy => {
104
107
  }
105
108
  }
106
109
  };
110
+
111
+ /**
112
+ * The route the app shows and the page's path. With hash routing they differ: the app's pages live in the hash, but
113
+ * SAML, OIDC, social and magic link callbacks come back on the path, which is where login matches them.
114
+ */
107
115
  exports.getUri = getUri;
116
+ const getCurrentRoutes = urlStrategy => {
117
+ const hashOrPathRoute = getUri(urlStrategy);
118
+ const {
119
+ pathname
120
+ } = window.location;
121
+ return hashOrPathRoute === pathname ? [pathname] : [hashOrPathRoute, pathname];
122
+ };
123
+
124
+ /**
125
+ * On the OIDC callback the page still has to finish the OIDC login with its code: a refresh refused before that is not
126
+ * a reason to go back to the IdP, which would send the user straight back here, forever.
127
+ */
128
+ const isOnOidcCallbackRoute = store => {
129
+ var _store$root$urlStrate;
130
+ const {
131
+ oidcRedirectUrl
132
+ } = (0, _extends2.default)({}, _consts.defaultFronteggRoutes, store.auth.routes);
133
+ return !!oidcRedirectUrl && getCurrentRoutes((_store$root$urlStrate = store.root.urlStrategy) != null ? _store$root$urlStrate : 'path').some(currentRoute => currentRoute.endsWith(oidcRedirectUrl));
134
+ };
135
+
136
+ /**
137
+ * After a switch refused for SSO the tab points at the SSO tenant. Its SSO login comes back on a callback route and
138
+ * keeps it; any other page load means the user didn't finish that login, so the tab goes back to its tenant.
139
+ */
140
+ exports.isOnOidcCallbackRoute = isOnOidcCallbackRoute;
141
+ const restoreTabTenantUnlessSsoCallback = store => {
142
+ var _store$root$urlStrate2;
143
+ if (!(0, _restApi.isEnforceSsoOnTenantAccessEnabled)(store.root.appName) || !_restApi.ContextHolder.for(store.root.appName).isSessionPerTenantEnabled()) {
144
+ return;
145
+ }
146
+ const {
147
+ samlCallbackUrl,
148
+ oidcRedirectUrl,
149
+ hostedLoginRedirectUrl
150
+ } = (0, _extends2.default)({}, _consts.defaultFronteggRoutes, store.auth.routes);
151
+ // matched like login matches its callback pages: endsWith, so a basename before the route still counts
152
+ const currentRoutes = getCurrentRoutes((_store$root$urlStrate2 = store.root.urlStrategy) != null ? _store$root$urlStrate2 : 'path');
153
+ const isSsoCallback = [samlCallbackUrl, oidcRedirectUrl, hostedLoginRedirectUrl].some(route => !!route && currentRoutes.some(currentRoute => currentRoute.endsWith(route)));
154
+ if (!isSsoCallback) {
155
+ (0, _restApi.restoreTabTenantBeforeSsoSwitch)();
156
+ }
157
+ };
158
+ exports.restoreTabTenantUnlessSsoCallback = restoreTabTenantUnlessSsoCallback;
108
159
  const prepareIdentifiersForSignup = payload => {
109
160
  const {
110
161
  phoneNumber,
@@ -129,7 +180,20 @@ const prepareIdentifiersForSignup = payload => {
129
180
  }
130
181
  return finalDto;
131
182
  };
183
+
184
+ // Pages the user lands on after login or sign-up: destinations, not auth pages.
132
185
  exports.prepareIdentifiersForSignup = prepareIdentifiersForSignup;
186
+ const AFTER_AUTH_DESTINATION_ROUTES = ['authenticatedUrl', 'signUpSuccessUrl', 'accountSignUpSuccessUrl', 'userSignUpSuccessUrl', 'customLoginAuthenticatedUrl'];
187
+
188
+ /**
189
+ * Whether the current route is an auth page (login, tenant chooser, social / magic link / SSO callback), not one to come
190
+ * back to. Unlike isAuthRoute: after-login / sign-up destinations don't count, and endsWith lets a basename prefix it.
191
+ */
192
+ const isOnAuthPageRoute = (urlStrategy, routes) => {
193
+ const currentRoutes = getCurrentRoutes(urlStrategy);
194
+ return Object.entries((0, _extends2.default)({}, _consts.defaultFronteggRoutes, routes)).some(([name, route]) => !AFTER_AUTH_DESTINATION_ROUTES.includes(name) && typeof route === 'string' && !!route && currentRoutes.some(currentRoute => currentRoute.endsWith(route)));
195
+ };
196
+ exports.isOnAuthPageRoute = isOnAuthPageRoute;
133
197
  const isAuthRoute = (path, routes) => {
134
198
  const _defaultFronteggRoute = (0, _extends2.default)({}, _consts.defaultFronteggRoutes, routes),
135
199
  authRoutes = (0, _objectWithoutPropertiesLoose2.default)(_defaultFronteggRoute, _excluded2);
package/node/index.js CHANGED
@@ -1,4 +1,4 @@
1
- /** @license Frontegg v7.131.0-alpha.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.
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@frontegg/redux-store",
3
- "version": "7.131.0-alpha.0",
3
+ "version": "7.131.0-alpha.1",
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.131.0-alpha.0",
10
+ "@frontegg/rest-api": "7.131.0-alpha.1",
11
11
  "fast-deep-equal": "3.1.3",
12
12
  "get-value": "^3.0.1",
13
13
  "proxy-compare": "^3.0.0",