@frontegg/redux-store 7.131.0-alpha.0 → 7.131.0-alpha.2

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, isOnLoginCodeCallbackRoute, 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,14 @@ 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
+ // on the hosted login callback the page finishes the login with its code: fail as with the flag off
31
+ if (isOnLoginCodeCallbackRoute(store)) {
32
+ throw error;
33
+ }
34
+ // the tenant requires SSO: hosted login for that tenant, not the plain one the caller falls back to
35
+ if (await actions.__redirectToSsoIfRequired(error)) {
36
+ return;
37
+ }
29
38
  const tenantId = (_store$auth$user = store.auth.user) == null ? void 0 : _store$auth$user.tenantId;
30
39
  if (!isEnforceSsoOnTenantAccessEnabled(store.root.appName) || !isSsoEnforcementError(error) || ((_error$errorContext = error.errorContext) == null ? void 0 : _error$errorContext.ssoRequired) !== false || !tenantId) {
31
40
  throw error;
@@ -33,7 +33,21 @@ declare const _default: (store: FronteggState, api: RestApi, sharedActions: Shar
33
33
  requestHostedLoginAuthorize: (additionalParams?: Record<string, string>) => Promise<void>;
34
34
  requestHostedLoginAuthorizeV2: (payload?: {
35
35
  additionalParams?: Record<string, string>;
36
- shouldRedirectToLogin?: boolean;
36
+ shouldRedirectToLogin
37
+ /**
38
+ * Handles a hosted (OAuth) token refresh refused because the tenant requires SSO.
39
+ * ssoRequired: true sends the user to SSO, like login. ssoRequired: false renews once more for the tenant of the
40
+ * current token, which keeps the session: OAuth doesn't use up the refresh token on this refusal.
41
+ * @returns true when handled, false when the caller should log out
42
+ * @private
43
+ */
44
+ ? /**
45
+ * Handles a hosted (OAuth) token refresh refused because the tenant requires SSO.
46
+ * ssoRequired: true sends the user to SSO, like login. ssoRequired: false renews once more for the tenant of the
47
+ * current token, which keeps the session: OAuth doesn't use up the refresh token on this refusal.
48
+ * @returns true when handled, false when the caller should log out
49
+ * @private
50
+ */: boolean;
37
51
  firstTime?: boolean;
38
52
  loginDirectAction?: import("../interfaces").LoginDirectAction;
39
53
  }) => Promise<void>;
@@ -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, isOnLoginCodeCallbackRoute, 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);
@@ -99,7 +129,8 @@ export default ((store, api, sharedActions) => {
99
129
  */
100
130
  const __handleSsoEnforcementRefreshError = async (e, isSsoEnforcementRetry, refreshForTenant) => {
101
131
  var _store$auth$user;
102
- if (!isEnforceSsoOnTenantAccessEnabled(store.root.appName) || !isSsoEnforcementError(e)) {
132
+ // on a login callback the page finishes the login with its code: log out as with the flag off, no new SSO trip
133
+ if (!isEnforceSsoOnTenantAccessEnabled(store.root.appName) || !isSsoEnforcementError(e) || isOnLoginCodeCallbackRoute(store)) {
103
134
  return false;
104
135
  }
105
136
  if (await __redirectToSsoIfRequired(e)) {
@@ -215,8 +246,9 @@ export default ((store, api, sharedActions) => {
215
246
  });
216
247
  }
217
248
  } 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)) {
249
+ // a refused identity refresh has already used up the refresh token: no retry, only the SSO redirect, except on
250
+ // a login callback, where the page itself completes the login
251
+ if (isEnforceSsoOnTenantAccessEnabled(store.root.appName) && !isOnLoginCodeCallbackRoute(store) && (await __redirectToSsoIfRequired(e))) {
220
252
  return;
221
253
  }
222
254
  contextHolder.setAccessToken(null);
@@ -263,6 +295,7 @@ export default ((store, api, sharedActions) => {
263
295
 
264
296
  /** @protected */
265
297
  const __refreshToken = async () => {
298
+ restoreTabTenantUnlessSsoCallback(store);
266
299
  if (FronteggNativeModule.isGetTokensAvailable()) {
267
300
  await __refreshTokenWithNativeTokens();
268
301
  return;
@@ -455,7 +488,7 @@ export default ((store, api, sharedActions) => {
455
488
  callback,
456
489
  error
457
490
  } = _ref3,
458
- body = _objectWithoutPropertiesLoose(_ref3, _excluded2);
491
+ body = _objectWithoutPropertiesLoose(_ref3, _excluded);
459
492
  const publicPolicy = store.auth.securityPolicyState.publicPolicy.policy;
460
493
  if (!(publicPolicy != null && publicPolicy.authStrategy)) {
461
494
  actions.setLoginState({
@@ -851,7 +884,7 @@ export default ((store, api, sharedActions) => {
851
884
  const {
852
885
  callback
853
886
  } = _payload,
854
- payload = _objectWithoutPropertiesLoose(_payload, _excluded3);
887
+ payload = _objectWithoutPropertiesLoose(_payload, _excluded2);
855
888
  try {
856
889
  setLoginState({
857
890
  loading: true
@@ -881,7 +914,7 @@ export default ((store, api, sharedActions) => {
881
914
  const {
882
915
  callback
883
916
  } = _payload,
884
- payload = _objectWithoutPropertiesLoose(_payload, _excluded4);
917
+ payload = _objectWithoutPropertiesLoose(_payload, _excluded3);
885
918
  try {
886
919
  setLoginState({
887
920
  loading: true
@@ -906,7 +939,7 @@ export default ((store, api, sharedActions) => {
906
939
  const {
907
940
  callback
908
941
  } = _payload,
909
- payload = _objectWithoutPropertiesLoose(_payload, _excluded5);
942
+ payload = _objectWithoutPropertiesLoose(_payload, _excluded4);
910
943
  try {
911
944
  setLoginState({
912
945
  loading: true
@@ -927,7 +960,7 @@ export default ((store, api, sharedActions) => {
927
960
  const {
928
961
  callback
929
962
  } = _payload,
930
- payload = _objectWithoutPropertiesLoose(_payload, _excluded6);
963
+ payload = _objectWithoutPropertiesLoose(_payload, _excluded5);
931
964
  try {
932
965
  var _username;
933
966
  setLoginState({
@@ -971,7 +1004,7 @@ export default ((store, api, sharedActions) => {
971
1004
  callback,
972
1005
  events
973
1006
  } = _payload,
974
- payload = _objectWithoutPropertiesLoose(_payload, _excluded7);
1007
+ payload = _objectWithoutPropertiesLoose(_payload, _excluded6);
975
1008
  try {
976
1009
  setLoginState({
977
1010
  loading: true
@@ -1102,7 +1135,7 @@ export default ((store, api, sharedActions) => {
1102
1135
  const {
1103
1136
  callback
1104
1137
  } = payload,
1105
- body = _objectWithoutPropertiesLoose(payload, _excluded8);
1138
+ body = _objectWithoutPropertiesLoose(payload, _excluded7);
1106
1139
  try {
1107
1140
  var _options$allowCredent;
1108
1141
  actions.setPasskeysState({
@@ -1142,7 +1175,7 @@ export default ((store, api, sharedActions) => {
1142
1175
  const {
1143
1176
  callback
1144
1177
  } = payload,
1145
- body = _objectWithoutPropertiesLoose(payload, _excluded9);
1178
+ body = _objectWithoutPropertiesLoose(payload, _excluded8);
1146
1179
  try {
1147
1180
  var _publicKey$response$u;
1148
1181
  actions.setPasskeysState({
@@ -1162,7 +1195,7 @@ export default ((store, api, sharedActions) => {
1162
1195
  {
1163
1196
  user
1164
1197
  } = _await$api$auth$webAu,
1165
- rest = _objectWithoutPropertiesLoose(_await$api$auth$webAu, _excluded10);
1198
+ rest = _objectWithoutPropertiesLoose(_await$api$auth$webAu, _excluded9);
1166
1199
  if (isMfaRequired(user, store.root.appName)) {
1167
1200
  const {
1168
1201
  routes,
@@ -1249,7 +1282,7 @@ export default ((store, api, sharedActions) => {
1249
1282
  const {
1250
1283
  callback
1251
1284
  } = payload,
1252
- body = _objectWithoutPropertiesLoose(payload, _excluded11);
1285
+ body = _objectWithoutPropertiesLoose(payload, _excluded10);
1253
1286
  try {
1254
1287
  setLoginState({
1255
1288
  loading: true
@@ -1314,7 +1347,7 @@ export default ((store, api, sharedActions) => {
1314
1347
  const {
1315
1348
  callback
1316
1349
  } = _payload,
1317
- payload = _objectWithoutPropertiesLoose(_payload, _excluded12);
1350
+ payload = _objectWithoutPropertiesLoose(_payload, _excluded11);
1318
1351
  setLoginState({
1319
1352
  loading: true
1320
1353
  });
@@ -1349,7 +1382,7 @@ export default ((store, api, sharedActions) => {
1349
1382
  const {
1350
1383
  callback
1351
1384
  } = _payload,
1352
- payload = _objectWithoutPropertiesLoose(_payload, _excluded13);
1385
+ payload = _objectWithoutPropertiesLoose(_payload, _excluded12);
1353
1386
  setLoginState({
1354
1387
  loading: true
1355
1388
  });
@@ -1364,6 +1397,9 @@ export default ((store, api, sharedActions) => {
1364
1397
  });
1365
1398
  callback == null ? void 0 : callback(true);
1366
1399
  } catch (e) {
1400
+ if (await __redirectToSsoIfRequired(e)) {
1401
+ return;
1402
+ }
1367
1403
  setLoginState({
1368
1404
  loading: false,
1369
1405
  error: errorHandler(e)
@@ -1375,7 +1411,7 @@ export default ((store, api, sharedActions) => {
1375
1411
  const {
1376
1412
  callback
1377
1413
  } = _payload,
1378
- payload = _objectWithoutPropertiesLoose(_payload, _excluded14);
1414
+ payload = _objectWithoutPropertiesLoose(_payload, _excluded13);
1379
1415
  setLoginState({
1380
1416
  loading: true
1381
1417
  });
@@ -1387,6 +1423,9 @@ export default ((store, api, sharedActions) => {
1387
1423
  });
1388
1424
  callback == null ? void 0 : callback(true);
1389
1425
  } catch (e) {
1426
+ if (await __redirectToSsoIfRequired(e)) {
1427
+ return;
1428
+ }
1390
1429
  setLoginState({
1391
1430
  loading: false,
1392
1431
  error: errorHandler(e)
@@ -1398,7 +1437,7 @@ export default ((store, api, sharedActions) => {
1398
1437
  const {
1399
1438
  callback
1400
1439
  } = _payload,
1401
- payload = _objectWithoutPropertiesLoose(_payload, _excluded15);
1440
+ payload = _objectWithoutPropertiesLoose(_payload, _excluded14);
1402
1441
  setLoginState({
1403
1442
  loading: true
1404
1443
  });
@@ -1425,7 +1464,7 @@ export default ((store, api, sharedActions) => {
1425
1464
  const {
1426
1465
  callback
1427
1466
  } = _payload,
1428
- payload = _objectWithoutPropertiesLoose(_payload, _excluded16);
1467
+ payload = _objectWithoutPropertiesLoose(_payload, _excluded15);
1429
1468
  setLoginState({
1430
1469
  loading: true
1431
1470
  });
@@ -1437,6 +1476,9 @@ export default ((store, api, sharedActions) => {
1437
1476
  });
1438
1477
  callback == null ? void 0 : callback(true);
1439
1478
  } catch (e) {
1479
+ if (await __redirectToSsoIfRequired(e)) {
1480
+ return;
1481
+ }
1440
1482
  setLoginState({
1441
1483
  loading: false,
1442
1484
  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)
@@ -94,6 +94,10 @@ export default ((store, api, sharedActions) => {
94
94
  }
95
95
  callback == null ? void 0 : callback(true);
96
96
  } catch (e) {
97
+ // the session's tenant requires SSO: its SSO login steps the user up, instead of logging them out
98
+ if (await actions.__redirectToSsoIfRequired(e)) {
99
+ return;
100
+ }
97
101
  callback == null ? void 0 : callback(false);
98
102
  await __handleStepUpError(e);
99
103
  }
@@ -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,21 @@ 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 or hosted login callback the page still has to finish the login with its code: a refresh refused before
13
+ * that is not a reason to go to SSO again. On the OIDC callback the IdP would send the user straight back here, forever;
14
+ * on the hosted callback a new hosted login would replace the PKCE verifier the code exchange needs.
15
+ */
16
+ export declare const isOnLoginCodeCallbackRoute: (store: FronteggState) => boolean;
17
+ /**
18
+ * After a switch refused for SSO the tab points at the SSO tenant. Its SSO login comes back on a callback route and
19
+ * keeps it; any other page load means the user didn't finish that login, so the tab goes back to its tenant.
20
+ */
21
+ export declare const restoreTabTenantUnlessSsoCallback: (store: FronteggState) => void;
11
22
  export declare const prepareIdentifiersForSignup: (payload: ISignUpUser) => ISignUpUser;
23
+ /**
24
+ * Whether the current route is an auth page (login, tenant chooser, social / magic link / SSO callback), not one to come
25
+ * back to. Unlike isAuthRoute: after-login / sign-up destinations don't count, and endsWith lets a basename prefix it.
26
+ */
27
+ export declare const isOnAuthPageRoute: (urlStrategy: FronteggState["root"]["urlStrategy"], routes?: Partial<AuthPageRoutes>) => boolean;
12
28
  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 } 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,57 @@ 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 or hosted login callback the page still has to finish the login with its code: a refresh refused before
71
+ * that is not a reason to go to SSO again. On the OIDC callback the IdP would send the user straight back here, forever;
72
+ * on the hosted callback a new hosted login would replace the PKCE verifier the code exchange needs.
73
+ */
74
+ export const isOnLoginCodeCallbackRoute = store => {
75
+ var _store$root$urlStrate;
76
+ const {
77
+ oidcRedirectUrl,
78
+ hostedLoginRedirectUrl
79
+ } = _extends({}, defaultFronteggRoutes, store.auth.routes);
80
+ const currentRoutes = getCurrentRoutes((_store$root$urlStrate = store.root.urlStrategy) != null ? _store$root$urlStrate : 'path');
81
+ return [oidcRedirectUrl, hostedLoginRedirectUrl].some(route => !!route && currentRoutes.some(currentRoute => currentRoute.endsWith(route)));
82
+ };
83
+
84
+ /**
85
+ * After a switch refused for SSO the tab points at the SSO tenant. Its SSO login comes back on a callback route and
86
+ * keeps it; any other page load means the user didn't finish that login, so the tab goes back to its tenant.
87
+ */
88
+ export const restoreTabTenantUnlessSsoCallback = store => {
89
+ var _store$root$urlStrate2;
90
+ // Flags load in parallel with the first refresh. Recover an existing marker
91
+ // before choosing that refresh's tenant, even if flags are not available yet.
92
+ if (!ContextHolder.for(store.root.appName).isSessionPerTenantEnabled()) {
93
+ return;
94
+ }
95
+ const {
96
+ samlCallbackUrl,
97
+ oidcRedirectUrl,
98
+ hostedLoginRedirectUrl
99
+ } = _extends({}, defaultFronteggRoutes, store.auth.routes);
100
+ // matched like login matches its callback pages: endsWith, so a basename before the route still counts
101
+ const currentRoutes = getCurrentRoutes((_store$root$urlStrate2 = store.root.urlStrategy) != null ? _store$root$urlStrate2 : 'path');
102
+ const isSsoCallback = [samlCallbackUrl, oidcRedirectUrl, hostedLoginRedirectUrl].some(route => !!route && currentRoutes.some(currentRoute => currentRoute.endsWith(route)));
103
+ if (!isSsoCallback) {
104
+ restoreTabTenantBeforeSsoSwitch();
105
+ }
106
+ };
56
107
  export const prepareIdentifiersForSignup = payload => {
57
108
  const {
58
109
  phoneNumber,
@@ -77,6 +128,18 @@ export const prepareIdentifiersForSignup = payload => {
77
128
  }
78
129
  return finalDto;
79
130
  };
131
+
132
+ // Pages the user lands on after login or sign-up: destinations, not auth pages.
133
+ const AFTER_AUTH_DESTINATION_ROUTES = ['authenticatedUrl', 'signUpSuccessUrl', 'accountSignUpSuccessUrl', 'userSignUpSuccessUrl', 'customLoginAuthenticatedUrl'];
134
+
135
+ /**
136
+ * Whether the current route is an auth page (login, tenant chooser, social / magic link / SSO callback), not one to come
137
+ * back to. Unlike isAuthRoute: after-login / sign-up destinations don't count, and endsWith lets a basename prefix it.
138
+ */
139
+ export const isOnAuthPageRoute = (urlStrategy, routes) => {
140
+ const currentRoutes = getCurrentRoutes(urlStrategy);
141
+ 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)));
142
+ };
80
143
  export const isAuthRoute = (path, routes) => {
81
144
  const _defaultFronteggRoute = _extends({}, defaultFronteggRoutes, routes),
82
145
  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.2
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,14 @@ 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
+ // on the hosted login callback the page finishes the login with its code: fail as with the flag off
38
+ if ((0, _helpers3.isOnLoginCodeCallbackRoute)(store)) {
39
+ throw error;
40
+ }
41
+ // the tenant requires SSO: hosted login for that tenant, not the plain one the caller falls back to
42
+ if (await actions.__redirectToSsoIfRequired(error)) {
43
+ return;
44
+ }
36
45
  const tenantId = (_store$auth$user = store.auth.user) == null ? void 0 : _store$auth$user.tenantId;
37
46
  if (!(0, _restApi.isEnforceSsoOnTenantAccessEnabled)(store.root.appName) || !(0, _helpers.isSsoEnforcementError)(error) || ((_error$errorContext = error.errorContext) == null ? void 0 : _error$errorContext.ssoRequired) !== false || !tenantId) {
38
47
  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);
@@ -106,7 +136,8 @@ var _default = (store, api, sharedActions) => {
106
136
  */
107
137
  const __handleSsoEnforcementRefreshError = async (e, isSsoEnforcementRetry, refreshForTenant) => {
108
138
  var _store$auth$user;
109
- if (!(0, _restApi.isEnforceSsoOnTenantAccessEnabled)(store.root.appName) || !(0, _helpers.isSsoEnforcementError)(e)) {
139
+ // on a login callback the page finishes the login with its code: log out as with the flag off, no new SSO trip
140
+ if (!(0, _restApi.isEnforceSsoOnTenantAccessEnabled)(store.root.appName) || !(0, _helpers.isSsoEnforcementError)(e) || (0, _helpers3.isOnLoginCodeCallbackRoute)(store)) {
110
141
  return false;
111
142
  }
112
143
  if (await __redirectToSsoIfRequired(e)) {
@@ -222,8 +253,9 @@ var _default = (store, api, sharedActions) => {
222
253
  });
223
254
  }
224
255
  } 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)) {
256
+ // a refused identity refresh has already used up the refresh token: no retry, only the SSO redirect, except on
257
+ // a login callback, where the page itself completes the login
258
+ if ((0, _restApi.isEnforceSsoOnTenantAccessEnabled)(store.root.appName) && !(0, _helpers3.isOnLoginCodeCallbackRoute)(store) && (await __redirectToSsoIfRequired(e))) {
227
259
  return;
228
260
  }
229
261
  contextHolder.setAccessToken(null);
@@ -270,6 +302,7 @@ var _default = (store, api, sharedActions) => {
270
302
 
271
303
  /** @protected */
272
304
  const __refreshToken = async () => {
305
+ (0, _helpers3.restoreTabTenantUnlessSsoCallback)(store);
273
306
  if (_toolkit.FronteggNativeModule.isGetTokensAvailable()) {
274
307
  await __refreshTokenWithNativeTokens();
275
308
  return;
@@ -462,7 +495,7 @@ var _default = (store, api, sharedActions) => {
462
495
  callback,
463
496
  error
464
497
  } = _ref3,
465
- body = (0, _objectWithoutPropertiesLoose2.default)(_ref3, _excluded2);
498
+ body = (0, _objectWithoutPropertiesLoose2.default)(_ref3, _excluded);
466
499
  const publicPolicy = store.auth.securityPolicyState.publicPolicy.policy;
467
500
  if (!(publicPolicy != null && publicPolicy.authStrategy)) {
468
501
  actions.setLoginState({
@@ -858,7 +891,7 @@ var _default = (store, api, sharedActions) => {
858
891
  const {
859
892
  callback
860
893
  } = _payload,
861
- payload = (0, _objectWithoutPropertiesLoose2.default)(_payload, _excluded3);
894
+ payload = (0, _objectWithoutPropertiesLoose2.default)(_payload, _excluded2);
862
895
  try {
863
896
  setLoginState({
864
897
  loading: true
@@ -888,7 +921,7 @@ var _default = (store, api, sharedActions) => {
888
921
  const {
889
922
  callback
890
923
  } = _payload,
891
- payload = (0, _objectWithoutPropertiesLoose2.default)(_payload, _excluded4);
924
+ payload = (0, _objectWithoutPropertiesLoose2.default)(_payload, _excluded3);
892
925
  try {
893
926
  setLoginState({
894
927
  loading: true
@@ -913,7 +946,7 @@ var _default = (store, api, sharedActions) => {
913
946
  const {
914
947
  callback
915
948
  } = _payload,
916
- payload = (0, _objectWithoutPropertiesLoose2.default)(_payload, _excluded5);
949
+ payload = (0, _objectWithoutPropertiesLoose2.default)(_payload, _excluded4);
917
950
  try {
918
951
  setLoginState({
919
952
  loading: true
@@ -934,7 +967,7 @@ var _default = (store, api, sharedActions) => {
934
967
  const {
935
968
  callback
936
969
  } = _payload,
937
- payload = (0, _objectWithoutPropertiesLoose2.default)(_payload, _excluded6);
970
+ payload = (0, _objectWithoutPropertiesLoose2.default)(_payload, _excluded5);
938
971
  try {
939
972
  var _username;
940
973
  setLoginState({
@@ -978,7 +1011,7 @@ var _default = (store, api, sharedActions) => {
978
1011
  callback,
979
1012
  events
980
1013
  } = _payload,
981
- payload = (0, _objectWithoutPropertiesLoose2.default)(_payload, _excluded7);
1014
+ payload = (0, _objectWithoutPropertiesLoose2.default)(_payload, _excluded6);
982
1015
  try {
983
1016
  setLoginState({
984
1017
  loading: true
@@ -1109,7 +1142,7 @@ var _default = (store, api, sharedActions) => {
1109
1142
  const {
1110
1143
  callback
1111
1144
  } = payload,
1112
- body = (0, _objectWithoutPropertiesLoose2.default)(payload, _excluded8);
1145
+ body = (0, _objectWithoutPropertiesLoose2.default)(payload, _excluded7);
1113
1146
  try {
1114
1147
  var _options$allowCredent;
1115
1148
  actions.setPasskeysState({
@@ -1149,7 +1182,7 @@ var _default = (store, api, sharedActions) => {
1149
1182
  const {
1150
1183
  callback
1151
1184
  } = payload,
1152
- body = (0, _objectWithoutPropertiesLoose2.default)(payload, _excluded9);
1185
+ body = (0, _objectWithoutPropertiesLoose2.default)(payload, _excluded8);
1153
1186
  try {
1154
1187
  var _publicKey$response$u;
1155
1188
  actions.setPasskeysState({
@@ -1169,7 +1202,7 @@ var _default = (store, api, sharedActions) => {
1169
1202
  {
1170
1203
  user
1171
1204
  } = _await$api$auth$webAu,
1172
- rest = (0, _objectWithoutPropertiesLoose2.default)(_await$api$auth$webAu, _excluded10);
1205
+ rest = (0, _objectWithoutPropertiesLoose2.default)(_await$api$auth$webAu, _excluded9);
1173
1206
  if ((0, _helpers3.isMfaRequired)(user, store.root.appName)) {
1174
1207
  const {
1175
1208
  routes,
@@ -1256,7 +1289,7 @@ var _default = (store, api, sharedActions) => {
1256
1289
  const {
1257
1290
  callback
1258
1291
  } = payload,
1259
- body = (0, _objectWithoutPropertiesLoose2.default)(payload, _excluded11);
1292
+ body = (0, _objectWithoutPropertiesLoose2.default)(payload, _excluded10);
1260
1293
  try {
1261
1294
  setLoginState({
1262
1295
  loading: true
@@ -1321,7 +1354,7 @@ var _default = (store, api, sharedActions) => {
1321
1354
  const {
1322
1355
  callback
1323
1356
  } = _payload,
1324
- payload = (0, _objectWithoutPropertiesLoose2.default)(_payload, _excluded12);
1357
+ payload = (0, _objectWithoutPropertiesLoose2.default)(_payload, _excluded11);
1325
1358
  setLoginState({
1326
1359
  loading: true
1327
1360
  });
@@ -1356,7 +1389,7 @@ var _default = (store, api, sharedActions) => {
1356
1389
  const {
1357
1390
  callback
1358
1391
  } = _payload,
1359
- payload = (0, _objectWithoutPropertiesLoose2.default)(_payload, _excluded13);
1392
+ payload = (0, _objectWithoutPropertiesLoose2.default)(_payload, _excluded12);
1360
1393
  setLoginState({
1361
1394
  loading: true
1362
1395
  });
@@ -1371,6 +1404,9 @@ var _default = (store, api, sharedActions) => {
1371
1404
  });
1372
1405
  callback == null ? void 0 : callback(true);
1373
1406
  } catch (e) {
1407
+ if (await __redirectToSsoIfRequired(e)) {
1408
+ return;
1409
+ }
1374
1410
  setLoginState({
1375
1411
  loading: false,
1376
1412
  error: (0, _helpers.errorHandler)(e)
@@ -1382,7 +1418,7 @@ var _default = (store, api, sharedActions) => {
1382
1418
  const {
1383
1419
  callback
1384
1420
  } = _payload,
1385
- payload = (0, _objectWithoutPropertiesLoose2.default)(_payload, _excluded14);
1421
+ payload = (0, _objectWithoutPropertiesLoose2.default)(_payload, _excluded13);
1386
1422
  setLoginState({
1387
1423
  loading: true
1388
1424
  });
@@ -1394,6 +1430,9 @@ var _default = (store, api, sharedActions) => {
1394
1430
  });
1395
1431
  callback == null ? void 0 : callback(true);
1396
1432
  } catch (e) {
1433
+ if (await __redirectToSsoIfRequired(e)) {
1434
+ return;
1435
+ }
1397
1436
  setLoginState({
1398
1437
  loading: false,
1399
1438
  error: (0, _helpers.errorHandler)(e)
@@ -1405,7 +1444,7 @@ var _default = (store, api, sharedActions) => {
1405
1444
  const {
1406
1445
  callback
1407
1446
  } = _payload,
1408
- payload = (0, _objectWithoutPropertiesLoose2.default)(_payload, _excluded15);
1447
+ payload = (0, _objectWithoutPropertiesLoose2.default)(_payload, _excluded14);
1409
1448
  setLoginState({
1410
1449
  loading: true
1411
1450
  });
@@ -1432,7 +1471,7 @@ var _default = (store, api, sharedActions) => {
1432
1471
  const {
1433
1472
  callback
1434
1473
  } = _payload,
1435
- payload = (0, _objectWithoutPropertiesLoose2.default)(_payload, _excluded16);
1474
+ payload = (0, _objectWithoutPropertiesLoose2.default)(_payload, _excluded15);
1436
1475
  setLoginState({
1437
1476
  loading: true
1438
1477
  });
@@ -1444,6 +1483,9 @@ var _default = (store, api, sharedActions) => {
1444
1483
  });
1445
1484
  callback == null ? void 0 : callback(true);
1446
1485
  } catch (e) {
1486
+ if (await __redirectToSsoIfRequired(e)) {
1487
+ return;
1488
+ }
1447
1489
  setLoginState({
1448
1490
  loading: false,
1449
1491
  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)
@@ -100,6 +100,10 @@ var _default = (store, api, sharedActions) => {
100
100
  }
101
101
  callback == null ? void 0 : callback(true);
102
102
  } catch (e) {
103
+ // the session's tenant requires SSO: its SSO login steps the user up, instead of logging them out
104
+ if (await actions.__redirectToSsoIfRequired(e)) {
105
+ return;
106
+ }
103
107
  callback == null ? void 0 : callback(false);
104
108
  await __handleStepUpError(e);
105
109
  }
@@ -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
+ isOnLoginCodeCallbackRoute: 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.isOnLoginCodeCallbackRoute = 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,60 @@ 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 or hosted login callback the page still has to finish the login with its code: a refresh refused before
126
+ * that is not a reason to go to SSO again. On the OIDC callback the IdP would send the user straight back here, forever;
127
+ * on the hosted callback a new hosted login would replace the PKCE verifier the code exchange needs.
128
+ */
129
+ const isOnLoginCodeCallbackRoute = store => {
130
+ var _store$root$urlStrate;
131
+ const {
132
+ oidcRedirectUrl,
133
+ hostedLoginRedirectUrl
134
+ } = (0, _extends2.default)({}, _consts.defaultFronteggRoutes, store.auth.routes);
135
+ const currentRoutes = getCurrentRoutes((_store$root$urlStrate = store.root.urlStrategy) != null ? _store$root$urlStrate : 'path');
136
+ return [oidcRedirectUrl, hostedLoginRedirectUrl].some(route => !!route && currentRoutes.some(currentRoute => currentRoute.endsWith(route)));
137
+ };
138
+
139
+ /**
140
+ * After a switch refused for SSO the tab points at the SSO tenant. Its SSO login comes back on a callback route and
141
+ * keeps it; any other page load means the user didn't finish that login, so the tab goes back to its tenant.
142
+ */
143
+ exports.isOnLoginCodeCallbackRoute = isOnLoginCodeCallbackRoute;
144
+ const restoreTabTenantUnlessSsoCallback = store => {
145
+ var _store$root$urlStrate2;
146
+ // Flags load in parallel with the first refresh. Recover an existing marker
147
+ // before choosing that refresh's tenant, even if flags are not available yet.
148
+ if (!_restApi.ContextHolder.for(store.root.appName).isSessionPerTenantEnabled()) {
149
+ return;
150
+ }
151
+ const {
152
+ samlCallbackUrl,
153
+ oidcRedirectUrl,
154
+ hostedLoginRedirectUrl
155
+ } = (0, _extends2.default)({}, _consts.defaultFronteggRoutes, store.auth.routes);
156
+ // matched like login matches its callback pages: endsWith, so a basename before the route still counts
157
+ const currentRoutes = getCurrentRoutes((_store$root$urlStrate2 = store.root.urlStrategy) != null ? _store$root$urlStrate2 : 'path');
158
+ const isSsoCallback = [samlCallbackUrl, oidcRedirectUrl, hostedLoginRedirectUrl].some(route => !!route && currentRoutes.some(currentRoute => currentRoute.endsWith(route)));
159
+ if (!isSsoCallback) {
160
+ (0, _restApi.restoreTabTenantBeforeSsoSwitch)();
161
+ }
162
+ };
163
+ exports.restoreTabTenantUnlessSsoCallback = restoreTabTenantUnlessSsoCallback;
108
164
  const prepareIdentifiersForSignup = payload => {
109
165
  const {
110
166
  phoneNumber,
@@ -129,7 +185,20 @@ const prepareIdentifiersForSignup = payload => {
129
185
  }
130
186
  return finalDto;
131
187
  };
188
+
189
+ // Pages the user lands on after login or sign-up: destinations, not auth pages.
132
190
  exports.prepareIdentifiersForSignup = prepareIdentifiersForSignup;
191
+ const AFTER_AUTH_DESTINATION_ROUTES = ['authenticatedUrl', 'signUpSuccessUrl', 'accountSignUpSuccessUrl', 'userSignUpSuccessUrl', 'customLoginAuthenticatedUrl'];
192
+
193
+ /**
194
+ * Whether the current route is an auth page (login, tenant chooser, social / magic link / SSO callback), not one to come
195
+ * back to. Unlike isAuthRoute: after-login / sign-up destinations don't count, and endsWith lets a basename prefix it.
196
+ */
197
+ const isOnAuthPageRoute = (urlStrategy, routes) => {
198
+ const currentRoutes = getCurrentRoutes(urlStrategy);
199
+ 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)));
200
+ };
201
+ exports.isOnAuthPageRoute = isOnAuthPageRoute;
133
202
  const isAuthRoute = (path, routes) => {
134
203
  const _defaultFronteggRoute = (0, _extends2.default)({}, _consts.defaultFronteggRoutes, routes),
135
204
  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.2
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.2",
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.2",
11
11
  "fast-deep-equal": "3.1.3",
12
12
  "get-value": "^3.0.1",
13
13
  "proxy-compare": "^3.0.0",