@frontegg/redux-store 7.128.0 → 7.129.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/auth/LoginState/actions/afterAuthNavigation.actions.js +39 -3
- package/auth/LoginState/actions/hostedLoginAuthorize.actions.js +6 -2
- package/auth/LoginState/actions/index.d.ts +4 -0
- package/auth/LoginState/actions/index.js +123 -16
- package/auth/TenantsState/actions.js +12 -2
- package/constants.d.ts +1 -0
- package/constants.js +1 -0
- package/helpers/handlers.d.ts +4 -1
- package/helpers/handlers.js +4 -1
- package/index.js +1 -1
- package/mocks/auth-mocks/loginActions.mocks.d.ts +4 -0
- package/node/auth/LoginState/actions/afterAuthNavigation.actions.js +38 -2
- package/node/auth/LoginState/actions/hostedLoginAuthorize.actions.js +6 -2
- package/node/auth/LoginState/actions/index.js +119 -12
- package/node/auth/TenantsState/actions.js +10 -0
- package/node/constants.js +4 -1
- package/node/helpers/handlers.js +5 -1
- package/node/index.js +1 -1
- package/package.json +2 -2
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import _extends from "@babel/runtime/helpers/esm/extends";
|
|
2
|
+
import { ENFORCE_SSO_ON_TENANT_ACCESS_FF, FeatureFlags } from '@frontegg/rest-api';
|
|
2
3
|
import { getPathAndSearchParamsFromUrl, getRedirectUrl, isAbsoluteUrl } from '../helpers';
|
|
3
4
|
import { shouldPromptChooseTenant } from '../helpers/tenantSelect';
|
|
4
|
-
import { FRONTEGG_AFTER_AUTH_REDIRECT_URL } from '../../../constants';
|
|
5
|
+
import { FRONTEGG_AFTER_AUTH_REDIRECT_URL, FRONTEGG_PENDING_SWITCH_TENANT } from '../../../constants';
|
|
5
6
|
import { LoginStep } from '../interfaces';
|
|
6
7
|
import { SHOULD_STEP_UP_KEY } from '../../StepUpState/consts';
|
|
7
8
|
import { delay } from '../../../helpers';
|
|
@@ -56,6 +57,33 @@ export default ((store, api, sharedActions) => {
|
|
|
56
57
|
}, additionalUpdate));
|
|
57
58
|
}
|
|
58
59
|
|
|
60
|
+
/**
|
|
61
|
+
* A switch that needed SSO saved its target tenant (in this tab) before sending the user to SSO.
|
|
62
|
+
* Once logged in again, switch to it once if the login landed in another tenant.
|
|
63
|
+
* @returns true when the switch left the user without a session (MFA step for the target tenant, or logout)
|
|
64
|
+
*/
|
|
65
|
+
async function switchToPendingSsoTenant() {
|
|
66
|
+
var _store$auth$user, _store$auth$user2;
|
|
67
|
+
const [isEnforceSsoOnTenantAccessEnabled] = FeatureFlags.getFeatureFlags([ENFORCE_SSO_ON_TENANT_ACCESS_FF], store.root.appName);
|
|
68
|
+
const pendingTenantId = window.sessionStorage.getItem(FRONTEGG_PENDING_SWITCH_TENANT);
|
|
69
|
+
if (!isEnforceSsoOnTenantAccessEnabled || !pendingTenantId || !((_store$auth$user = store.auth.user) != null && _store$auth$user.accessToken)) {
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
if (store.auth.user.tenantId === pendingTenantId) {
|
|
73
|
+
window.sessionStorage.removeItem(FRONTEGG_PENDING_SWITCH_TENANT);
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
// silentReload keeps switchingTenant set, so the switch's own refresh doesn't re-run this navigation
|
|
77
|
+
await actions.switchTenant({
|
|
78
|
+
tenantId: pendingTenantId,
|
|
79
|
+
silentReload: true,
|
|
80
|
+
keepLoginState: true
|
|
81
|
+
});
|
|
82
|
+
// removed only after the switch: a refused re-run must not send the user to SSO again
|
|
83
|
+
window.sessionStorage.removeItem(FRONTEGG_PENDING_SWITCH_TENANT);
|
|
84
|
+
return !((_store$auth$user2 = store.auth.user) != null && _store$auth$user2.accessToken);
|
|
85
|
+
}
|
|
86
|
+
|
|
59
87
|
/**
|
|
60
88
|
* @param customLoginAuthenticatedUrl custom login authenticated url if exists
|
|
61
89
|
* @returns the authenticated url to redirect to after auth navigation
|
|
@@ -159,7 +187,7 @@ export default ((store, api, sharedActions) => {
|
|
|
159
187
|
* until the resolved-guard, the LD flag, and the option all pass.
|
|
160
188
|
*/
|
|
161
189
|
const shouldRouteToChooseTenant = async () => {
|
|
162
|
-
var _store$auth$tenantSel, _store$auth$
|
|
190
|
+
var _store$auth$tenantSel, _store$auth$user3, _store$auth$user3$act, _store$auth$tenantsSt;
|
|
163
191
|
const loginState = store.auth.loginState;
|
|
164
192
|
// Guard first so re-entry after the choice is resolved finalizes without re-reading the flag.
|
|
165
193
|
if (loginState.tenantChoiceResolved) {
|
|
@@ -175,7 +203,7 @@ export default ((store, api, sharedActions) => {
|
|
|
175
203
|
return false; // feature off — never read tenant data
|
|
176
204
|
}
|
|
177
205
|
|
|
178
|
-
const isImpersonation = !!((_store$auth$
|
|
206
|
+
const isImpersonation = !!((_store$auth$user3 = store.auth.user) != null && (_store$auth$user3$act = _store$auth$user3.act) != null && _store$auth$user3$act.sub);
|
|
179
207
|
if (isImpersonation) {
|
|
180
208
|
return false;
|
|
181
209
|
}
|
|
@@ -253,6 +281,14 @@ export default ((store, api, sharedActions) => {
|
|
|
253
281
|
}
|
|
254
282
|
try {
|
|
255
283
|
var _window4, _payload$preventRedir;
|
|
284
|
+
const isSessionLostInPendingSwitch = await switchToPendingSsoTenant();
|
|
285
|
+
if (isSessionLostInPendingSwitch) {
|
|
286
|
+
// MFA for the target tenant or a failed refresh: continue on the login page
|
|
287
|
+
store.auth.onRedirectTo(store.auth.routes.loginUrl, {
|
|
288
|
+
preserveQueryParams: true
|
|
289
|
+
});
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
256
292
|
const {
|
|
257
293
|
customLoginAuthenticatedUrl,
|
|
258
294
|
stepUpUrl
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import _extends from "@babel/runtime/helpers/esm/extends";
|
|
2
2
|
import { createRandomString, generateCodeChallenge, GTMEventAction, refreshTokenWithRetry, reportGTMEvent } from '../../../helpers';
|
|
3
3
|
import { HOSTED_LOGIN_VERIFIER_KEY } from '../../../constants';
|
|
4
|
+
import { ENFORCE_SSO_ON_TENANT_ACCESS_FF, FeatureFlags } from '@frontegg/rest-api';
|
|
4
5
|
import { getBaseNameWithoutSlashSuffix, getSearchParam, isOauthCallbackRoute, TENANT_ID_PARAM_KEY } from '../helpers';
|
|
5
6
|
import { getUri, getUrlDerivedStepUp, isSteppedUp, redirectByStepUpUrl } from '../../helpers';
|
|
6
7
|
import { SHOULD_STEP_UP_KEY } from '../../StepUpState/consts';
|
|
@@ -79,7 +80,10 @@ export default ((store, api, sharedActions) => {
|
|
|
79
80
|
code_challenge_method: 'S256',
|
|
80
81
|
nonce
|
|
81
82
|
}, additionalParams);
|
|
82
|
-
|
|
83
|
+
const [isEnforceSsoOnTenantAccessEnabled] = FeatureFlags.getFeatureFlags([ENFORCE_SSO_ON_TENANT_ACCESS_FF], store.root.appName);
|
|
84
|
+
// the tenant asked for (SSO for a refused switch or refresh) wins over the resolved and the page's tenant
|
|
85
|
+
const isTenantRequested = isEnforceSsoOnTenantAccessEnabled && !!(additionalParams != null && additionalParams.tenantId);
|
|
86
|
+
if (context.tenantResolver && !isTenantRequested) {
|
|
83
87
|
var _context$tenantResolv;
|
|
84
88
|
const resolvedTenantResult = await ((_context$tenantResolv = context.tenantResolver) == null ? void 0 : _context$tenantResolv.call(context));
|
|
85
89
|
if (resolvedTenantResult != null && resolvedTenantResult.tenant) {
|
|
@@ -87,7 +91,7 @@ export default ((store, api, sharedActions) => {
|
|
|
87
91
|
}
|
|
88
92
|
}
|
|
89
93
|
const tenantId = getSearchParam(TENANT_ID_PARAM_KEY);
|
|
90
|
-
if (tenantId) {
|
|
94
|
+
if (tenantId && !isTenantRequested) {
|
|
91
95
|
params['tenantId'] = tenantId;
|
|
92
96
|
}
|
|
93
97
|
if (FronteggNativeModule.isAvailable('closeWindow')) {
|
|
@@ -75,5 +75,9 @@ declare const _default: (store: FronteggState, api: RestApi, sharedActions: Shar
|
|
|
75
75
|
resetBreachedPassword: (payload: IForgotPassword) => Promise<void>;
|
|
76
76
|
refreshTokenForSocialLogins: () => Promise<void>;
|
|
77
77
|
__refreshToken: () => Promise<void>;
|
|
78
|
+
__redirectToSsoForTenant: ({ tenantId, address }: {
|
|
79
|
+
tenantId: string;
|
|
80
|
+
address: string;
|
|
81
|
+
}) => Promise<void>;
|
|
78
82
|
};
|
|
79
83
|
export default _default;
|
|
@@ -23,26 +23,85 @@ import mfaWithEmailCodeActions from './mfaWithEmailCode.actions';
|
|
|
23
23
|
import mfaWithSMSActions from './mfaWithSMS.actions';
|
|
24
24
|
import mfaWithWebAuthnActions from './mfaWithWebAuthn.actions';
|
|
25
25
|
import { LoginFlow, LoginStep } from '../interfaces';
|
|
26
|
-
import { base64urlDecode, deepResetState, delay, errorHandler, errorTraceId, GTMEventAction, publicKeyCredentialToJSON, refreshTokenWithRetry, reportGTMEvent, retryIfNeeded, withRetryConfig } from '../../../helpers';
|
|
26
|
+
import { base64urlDecode, deepResetState, delay, errorHandler, errorTraceId, GTMEventAction, isSsoEnforcementError, publicKeyCredentialToJSON, refreshTokenWithRetry, reportGTMEvent, retryIfNeeded, withRetryConfig } from '../../../helpers';
|
|
27
27
|
import { initialState } from '../state';
|
|
28
28
|
import { getSearchParam, isEmailPayload, isUsernamePayload, shouldShowPasswordRotationPromptFunc, TENANT_ID_PARAM_KEY } from '../helpers';
|
|
29
|
-
import { AuthStrategyEnum, ContextHolder, FeatureFlags, getTabTenantFromSessionStorage, removeTabTenantFromSessionStorage, WebAuthnDeviceType, PreLoginAddressType } from '@frontegg/rest-api';
|
|
29
|
+
import { AuthStrategyEnum, ContextHolder, ENFORCE_SSO_ON_TENANT_ACCESS_FF, FeatureFlags, getTabTenantFromSessionStorage, removeTabTenantFromSessionStorage, setTabTenantInSessionStorage, WebAuthnDeviceType, PreLoginAddressType } from '@frontegg/rest-api';
|
|
30
30
|
import hostedLoginAuthorizeActions from './hostedLoginAuthorize.actions';
|
|
31
31
|
import { clearHostedOAuthState } from '../oauthStorage';
|
|
32
32
|
import { SHOULD_STEP_UP_KEY } from '../../StepUpState/consts';
|
|
33
33
|
import { FronteggNativeModule, isEntitlementsDeeplyEqual } from '../../../toolkit';
|
|
34
34
|
import { REQUEST_NAME, UserVerifiedOriginTypes } from '../../interfaces';
|
|
35
35
|
import { authStrategyLoginStepMap } from '../consts';
|
|
36
|
-
import { isMfaRequired, isResetPasswordRequired } from '../../helpers';
|
|
36
|
+
import { isMfaRequired, isResetPasswordRequired, setAfterAuthRedirectUrlForStepUp } from '../../helpers';
|
|
37
37
|
import { MFAStep } from '../../MfaState/interfaces';
|
|
38
38
|
import { SamlVendors } from '../../SSOState/interfaces';
|
|
39
|
-
import { DEFAULT_RETRY_CONFIG } from '../../../constants';
|
|
39
|
+
import { DEFAULT_RETRY_CONFIG, FRONTEGG_PENDING_SWITCH_TENANT } from '../../../constants';
|
|
40
40
|
export default ((store, api, sharedActions) => {
|
|
41
41
|
const actions = sharedActions;
|
|
42
42
|
const contextHolder = ContextHolder.for(store.root.appName);
|
|
43
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Sends the user to log in through the tenant's SSO and back to the current page afterwards.
|
|
46
|
+
* @protected
|
|
47
|
+
*/
|
|
48
|
+
const __redirectToSsoForTenant = async ({
|
|
49
|
+
tenantId,
|
|
50
|
+
address
|
|
51
|
+
}) => {
|
|
52
|
+
// on the login page (tenant chooser) keep the destination the login already saved
|
|
53
|
+
if (!window.location.pathname.endsWith(store.auth.routes.loginUrl)) {
|
|
54
|
+
setAfterAuthRedirectUrlForStepUp();
|
|
55
|
+
}
|
|
56
|
+
if (store.auth.hostedLoginBox) {
|
|
57
|
+
await actions.requestHostedLoginAuthorize({
|
|
58
|
+
prompt: 'login',
|
|
59
|
+
tenantId
|
|
60
|
+
});
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
store.auth.onRedirectTo(address, {
|
|
64
|
+
refresh: true
|
|
65
|
+
});
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Handles a token refresh refused because the tenant requires SSO.
|
|
69
|
+
* ssoRequired: true sends the user to SSO. ssoRequired: false renews once more for the tenant of the
|
|
70
|
+
* current token, which keeps the session.
|
|
71
|
+
* @returns true when handled, false when the caller should log out
|
|
72
|
+
* @private
|
|
73
|
+
*/
|
|
74
|
+
const __handleSsoEnforcementRefreshError = async (e, isSsoEnforcementRetry, refreshForTenant) => {
|
|
75
|
+
var _store$auth$user;
|
|
76
|
+
const [isEnforceSsoOnTenantAccessEnabled] = FeatureFlags.getFeatureFlags([ENFORCE_SSO_ON_TENANT_ACCESS_FF], store.root.appName);
|
|
77
|
+
if (!isEnforceSsoOnTenantAccessEnabled || !isSsoEnforcementError(e)) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
const {
|
|
81
|
+
errorContext
|
|
82
|
+
} = e;
|
|
83
|
+
// the switch re-run after coming back from SSO keeps the session instead of sending the user to SSO again
|
|
84
|
+
const isPendingSwitchRerun = !!window.sessionStorage.getItem(FRONTEGG_PENDING_SWITCH_TENANT);
|
|
85
|
+
if (errorContext.ssoRequired && !isPendingSwitchRerun) {
|
|
86
|
+
await __redirectToSsoForTenant(errorContext);
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
if (isSsoEnforcementRetry) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
const currentTenantId = (_store$auth$user = store.auth.user) == null ? void 0 : _store$auth$user.tenantId;
|
|
93
|
+
if (currentTenantId && contextHolder.isSessionPerTenantEnabled()) {
|
|
94
|
+
setTabTenantInSessionStorage(currentTenantId);
|
|
95
|
+
}
|
|
96
|
+
await refreshForTenant(currentTenantId);
|
|
97
|
+
return true;
|
|
98
|
+
};
|
|
99
|
+
|
|
44
100
|
/** @private */
|
|
45
|
-
const __refreshTokenHosted = async (
|
|
101
|
+
const __refreshTokenHosted = async ({
|
|
102
|
+
tenantId,
|
|
103
|
+
isSsoEnforcementRetry = false
|
|
104
|
+
} = {}) => {
|
|
46
105
|
const user = store.auth.user;
|
|
47
106
|
if (!(user != null && user.refreshToken)) {
|
|
48
107
|
contextHolder.setAccessToken(null);
|
|
@@ -54,7 +113,7 @@ export default ((store, api, sharedActions) => {
|
|
|
54
113
|
return;
|
|
55
114
|
}
|
|
56
115
|
try {
|
|
57
|
-
const tabTenantId = getTabTenantFromSessionStorage(store.root.appName);
|
|
116
|
+
const tabTenantId = tenantId != null ? tenantId : getTabTenantFromSessionStorage(store.root.appName);
|
|
58
117
|
const body = _extends({
|
|
59
118
|
grant_type: 'refresh_token',
|
|
60
119
|
refresh_token: user == null ? void 0 : user.refreshToken
|
|
@@ -69,6 +128,13 @@ export default ((store, api, sharedActions) => {
|
|
|
69
128
|
isAuthenticated: true
|
|
70
129
|
});
|
|
71
130
|
} catch (e) {
|
|
131
|
+
const retry = tenantId => __refreshTokenHosted({
|
|
132
|
+
tenantId,
|
|
133
|
+
isSsoEnforcementRetry: true
|
|
134
|
+
});
|
|
135
|
+
if (await __handleSsoEnforcementRefreshError(e, isSsoEnforcementRetry, retry)) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
72
138
|
contextHolder.setAccessToken(null);
|
|
73
139
|
contextHolder.setUser(null);
|
|
74
140
|
actions.setAuthState({
|
|
@@ -88,7 +154,10 @@ export default ((store, api, sharedActions) => {
|
|
|
88
154
|
};
|
|
89
155
|
|
|
90
156
|
/** @private */
|
|
91
|
-
const __refreshTokenEmbedded = async (
|
|
157
|
+
const __refreshTokenEmbedded = async ({
|
|
158
|
+
tenantId,
|
|
159
|
+
isSsoEnforcementRetry = false
|
|
160
|
+
} = {}) => {
|
|
92
161
|
try {
|
|
93
162
|
const {
|
|
94
163
|
routes,
|
|
@@ -99,7 +168,7 @@ export default ((store, api, sharedActions) => {
|
|
|
99
168
|
user,
|
|
100
169
|
tenants = [],
|
|
101
170
|
activeTenant
|
|
102
|
-
} = await refreshTokenWithRetry(() => api.auth.refreshTokenV3());
|
|
171
|
+
} = await refreshTokenWithRetry(() => api.auth.refreshTokenV3(tenantId));
|
|
103
172
|
if (isMfaRequired(user, store.root.appName)) {
|
|
104
173
|
const mfaRequiredState = await actions.getMfaRequiredState(user);
|
|
105
174
|
actions.setAuthState(mfaRequiredState);
|
|
@@ -130,6 +199,13 @@ export default ((store, api, sharedActions) => {
|
|
|
130
199
|
});
|
|
131
200
|
}
|
|
132
201
|
} catch (e) {
|
|
202
|
+
const retry = tenantId => __refreshTokenEmbedded({
|
|
203
|
+
tenantId,
|
|
204
|
+
isSsoEnforcementRetry: true
|
|
205
|
+
});
|
|
206
|
+
if (await __handleSsoEnforcementRefreshError(e, isSsoEnforcementRetry, retry)) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
133
209
|
contextHolder.setAccessToken(null);
|
|
134
210
|
contextHolder.setUser(null);
|
|
135
211
|
actions.setAuthState({
|
|
@@ -140,7 +216,9 @@ export default ((store, api, sharedActions) => {
|
|
|
140
216
|
};
|
|
141
217
|
|
|
142
218
|
/** @private */
|
|
143
|
-
const __refreshTokenWithNativeTokens = async (
|
|
219
|
+
const __refreshTokenWithNativeTokens = async ({
|
|
220
|
+
isSsoEnforcementRetry = false
|
|
221
|
+
} = {}) => {
|
|
144
222
|
try {
|
|
145
223
|
const authApi = api.auth;
|
|
146
224
|
const response = await refreshTokenWithRetry(async () => {
|
|
@@ -154,6 +232,13 @@ export default ((store, api, sharedActions) => {
|
|
|
154
232
|
isAuthenticated: true
|
|
155
233
|
});
|
|
156
234
|
} catch (e) {
|
|
235
|
+
// native tokens can't name a tenant: the retry relies on the tab tenant only
|
|
236
|
+
const retry = () => __refreshTokenWithNativeTokens({
|
|
237
|
+
isSsoEnforcementRetry: true
|
|
238
|
+
});
|
|
239
|
+
if (await __handleSsoEnforcementRefreshError(e, isSsoEnforcementRetry, retry)) {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
157
242
|
contextHolder.setAccessToken(null);
|
|
158
243
|
contextHolder.setUser(null);
|
|
159
244
|
actions.setAuthState({
|
|
@@ -187,9 +272,9 @@ export default ((store, api, sharedActions) => {
|
|
|
187
272
|
* @private
|
|
188
273
|
*/
|
|
189
274
|
const __handleUnnecessaryEntitlementsUpdate = async updatedUser => {
|
|
190
|
-
var _store$auth$
|
|
275
|
+
var _store$auth$user2;
|
|
191
276
|
// @ts-ignore TODO: fix entitlements state type
|
|
192
|
-
const oldEntitlements = (_store$auth$
|
|
277
|
+
const oldEntitlements = (_store$auth$user2 = store.auth.user) == null ? void 0 : _store$auth$user2.entitlements;
|
|
193
278
|
if (isEntitlementsDeeplyEqual(oldEntitlements, updatedUser == null ? void 0 : updatedUser.entitlements)) {
|
|
194
279
|
// set the previous entitlements object
|
|
195
280
|
return _extends({}, updatedUser, {
|
|
@@ -659,6 +744,7 @@ export default ((store, api, sharedActions) => {
|
|
|
659
744
|
if (!keepHostedOAuthState) {
|
|
660
745
|
clearHostedOAuthState();
|
|
661
746
|
}
|
|
747
|
+
window.sessionStorage.removeItem(FRONTEGG_PENDING_SWITCH_TENANT);
|
|
662
748
|
};
|
|
663
749
|
const logout = async payload => {
|
|
664
750
|
const hostedLoginBox = store.auth.hostedLoginBox;
|
|
@@ -1361,7 +1447,10 @@ export default ((store, api, sharedActions) => {
|
|
|
1361
1447
|
};
|
|
1362
1448
|
|
|
1363
1449
|
/** @private */
|
|
1364
|
-
const __refreshTokenHostedSocialLogins = async (
|
|
1450
|
+
const __refreshTokenHostedSocialLogins = async ({
|
|
1451
|
+
tenantId,
|
|
1452
|
+
isSsoEnforcementRetry = false
|
|
1453
|
+
} = {}) => {
|
|
1365
1454
|
const {
|
|
1366
1455
|
user: currentUser
|
|
1367
1456
|
} = store.auth;
|
|
@@ -1375,7 +1464,7 @@ export default ((store, api, sharedActions) => {
|
|
|
1375
1464
|
return;
|
|
1376
1465
|
}
|
|
1377
1466
|
try {
|
|
1378
|
-
const tabTenantId = getTabTenantFromSessionStorage(store.root.appName);
|
|
1467
|
+
const tabTenantId = tenantId != null ? tenantId : getTabTenantFromSessionStorage(store.root.appName);
|
|
1379
1468
|
const body = _extends({
|
|
1380
1469
|
grant_type: 'refresh_token',
|
|
1381
1470
|
refresh_token: currentUser == null ? void 0 : currentUser.refreshToken
|
|
@@ -1390,6 +1479,13 @@ export default ((store, api, sharedActions) => {
|
|
|
1390
1479
|
isAuthenticated: true
|
|
1391
1480
|
});
|
|
1392
1481
|
} catch (e) {
|
|
1482
|
+
const retry = tenantId => __refreshTokenHostedSocialLogins({
|
|
1483
|
+
tenantId,
|
|
1484
|
+
isSsoEnforcementRetry: true
|
|
1485
|
+
});
|
|
1486
|
+
if (await __handleSsoEnforcementRefreshError(e, isSsoEnforcementRetry, retry)) {
|
|
1487
|
+
return;
|
|
1488
|
+
}
|
|
1393
1489
|
contextHolder.setAccessToken(null);
|
|
1394
1490
|
contextHolder.setUser(null);
|
|
1395
1491
|
actions.setAuthState({
|
|
@@ -1399,7 +1495,10 @@ export default ((store, api, sharedActions) => {
|
|
|
1399
1495
|
}
|
|
1400
1496
|
};
|
|
1401
1497
|
/** @private */
|
|
1402
|
-
const __refreshTokenEmbeddedSocialLogins = async (
|
|
1498
|
+
const __refreshTokenEmbeddedSocialLogins = async ({
|
|
1499
|
+
tenantId,
|
|
1500
|
+
isSsoEnforcementRetry = false
|
|
1501
|
+
} = {}) => {
|
|
1403
1502
|
try {
|
|
1404
1503
|
const {
|
|
1405
1504
|
routes,
|
|
@@ -1410,7 +1509,7 @@ export default ((store, api, sharedActions) => {
|
|
|
1410
1509
|
user,
|
|
1411
1510
|
tenants = [],
|
|
1412
1511
|
activeTenant
|
|
1413
|
-
} = await refreshTokenWithRetry(() => api.auth.refreshTokenV3());
|
|
1512
|
+
} = await refreshTokenWithRetry(() => api.auth.refreshTokenV3(tenantId));
|
|
1414
1513
|
if (isMfaRequired(user, store.root.appName)) {
|
|
1415
1514
|
const mfaRequiredState = await actions.getMfaRequiredState(user);
|
|
1416
1515
|
actions.setAuthState(mfaRequiredState);
|
|
@@ -1460,6 +1559,13 @@ export default ((store, api, sharedActions) => {
|
|
|
1460
1559
|
}
|
|
1461
1560
|
}
|
|
1462
1561
|
} catch (e) {
|
|
1562
|
+
const retry = tenantId => __refreshTokenEmbeddedSocialLogins({
|
|
1563
|
+
tenantId,
|
|
1564
|
+
isSsoEnforcementRetry: true
|
|
1565
|
+
});
|
|
1566
|
+
if (await __handleSsoEnforcementRefreshError(e, isSsoEnforcementRetry, retry)) {
|
|
1567
|
+
return;
|
|
1568
|
+
}
|
|
1463
1569
|
contextHolder.setAccessToken(null);
|
|
1464
1570
|
contextHolder.setUser(null);
|
|
1465
1571
|
actions.setAuthState({
|
|
@@ -1506,6 +1612,7 @@ export default ((store, api, sharedActions) => {
|
|
|
1506
1612
|
resetBreachedPassword,
|
|
1507
1613
|
refreshTokenForSocialLogins,
|
|
1508
1614
|
// protected
|
|
1509
|
-
__refreshToken
|
|
1615
|
+
__refreshToken,
|
|
1616
|
+
__redirectToSsoForTenant
|
|
1510
1617
|
}, hostedLoginAuthorizeActions(store, api, sharedActions), afterAuthNavigationActions(store, api, sharedActions), handleVerifyMFAResponseActions(store, api, sharedActions), mfaRequiredStateActions(store, api, sharedActions), mfaWithAuthenticatorActions(store, api, sharedActions), mfaWithEmailCodeActions(store, api, sharedActions), mfaWithSMSActions(store, api, sharedActions), mfaWithWebAuthnActions(store, api, sharedActions));
|
|
1511
1618
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { initialState } from './state';
|
|
2
|
-
import { deepResetState } from '../../helpers';
|
|
3
|
-
import { getCurrentUserTenantsFunction } from '@frontegg/rest-api';
|
|
2
|
+
import { deepResetState, isSsoEnforcementError } from '../../helpers';
|
|
3
|
+
import { ENFORCE_SSO_ON_TENANT_ACCESS_FF, FeatureFlags, getCurrentUserTenantsFunction } from '@frontegg/rest-api';
|
|
4
|
+
import { FRONTEGG_PENDING_SWITCH_TENANT } from '../../constants';
|
|
4
5
|
export default ((store, api, sharedActions) => {
|
|
5
6
|
const actions = sharedActions;
|
|
6
7
|
const setTenantsState = state => {
|
|
@@ -51,6 +52,15 @@ export default ((store, api, sharedActions) => {
|
|
|
51
52
|
}) : actions.setAuthState({
|
|
52
53
|
isLoading: false
|
|
53
54
|
});
|
|
55
|
+
const [isEnforceSsoOnTenantAccessEnabled] = FeatureFlags.getFeatureFlags([ENFORCE_SSO_ON_TENANT_ACCESS_FF], store.root.appName);
|
|
56
|
+
// a switch re-run after coming back from SSO doesn't send the user to SSO again
|
|
57
|
+
const isPendingSwitchRerun = window.sessionStorage.getItem(FRONTEGG_PENDING_SWITCH_TENANT) === tenantId;
|
|
58
|
+
if (isEnforceSsoOnTenantAccessEnabled && isSsoEnforcementError(e) && e.errorContext.ssoRequired && !isPendingSwitchRerun) {
|
|
59
|
+
// saved to finish the switch after the SSO login, which may land in another tenant
|
|
60
|
+
window.sessionStorage.setItem(FRONTEGG_PENDING_SWITCH_TENANT, tenantId);
|
|
61
|
+
await actions.__redirectToSsoForTenant(e.errorContext);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
54
64
|
callback == null ? void 0 : callback(false, e);
|
|
55
65
|
}
|
|
56
66
|
};
|
package/constants.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export declare const FRONTEGG_AFTER_AUTH_REDIRECT_URL = "FRONTEGG_AFTER_AUTH_REDIRECT_URL";
|
|
2
|
+
export declare const FRONTEGG_PENDING_SWITCH_TENANT = "FRONTEGG_PENDING_SWITCH_TENANT";
|
|
2
3
|
export declare const HOSTED_LOGIN_VERIFIER_KEY = "HOSTED_LOGIN_VERIFIER_KEY";
|
|
3
4
|
export declare const DEFAULT_RETRY_CONFIG: {
|
|
4
5
|
retryCount: number;
|
package/constants.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export const FRONTEGG_AFTER_AUTH_REDIRECT_URL = 'FRONTEGG_AFTER_AUTH_REDIRECT_URL';
|
|
2
|
+
export const FRONTEGG_PENDING_SWITCH_TENANT = 'FRONTEGG_PENDING_SWITCH_TENANT';
|
|
2
3
|
export const HOSTED_LOGIN_VERIFIER_KEY = 'HOSTED_LOGIN_VERIFIER_KEY';
|
|
3
4
|
export const DEFAULT_RETRY_CONFIG = {
|
|
4
5
|
retryCount: 3
|
package/helpers/handlers.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import { FronteggApiError } from '@frontegg/rest-api';
|
|
1
|
+
import { FronteggApiError, SsoEnforcementErrorContext } from '@frontegg/rest-api';
|
|
2
2
|
export declare const isFronteggApiError: (error: unknown | Error | FronteggApiError) => error is FronteggApiError;
|
|
3
|
+
export declare const isSsoEnforcementError: (error: unknown) => error is FronteggApiError & {
|
|
4
|
+
errorContext: SsoEnforcementErrorContext;
|
|
5
|
+
};
|
|
3
6
|
export declare const isError: (error: any) => error is Error;
|
|
4
7
|
export declare const errorHandler: <T>(error: unknown, fallback?: T) => FronteggApiError | T;
|
package/helpers/handlers.js
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import { FronteggApiError, GENERIC_ERROR_MESSAGE, GENERIC_ERROR_CODE } from '@frontegg/rest-api';
|
|
1
|
+
import { FronteggApiError, GENERIC_ERROR_MESSAGE, GENERIC_ERROR_CODE, SSO_REQUIRED_FOR_TENANT_ERROR_CODE } from '@frontegg/rest-api';
|
|
2
2
|
export const isFronteggApiError = error => Boolean(error == null ? void 0 : error.statusCode);
|
|
3
|
+
|
|
4
|
+
// ER-01079 (disabled user / expired tenant) is not this: it still logs out
|
|
5
|
+
export const isSsoEnforcementError = error => isFronteggApiError(error) && error.errorCode === SSO_REQUIRED_FOR_TENANT_ERROR_CODE;
|
|
3
6
|
export const isError = error => {
|
|
4
7
|
if (error instanceof Error) {
|
|
5
8
|
return true;
|
package/index.js
CHANGED
|
@@ -71,5 +71,9 @@ declare const _default: (store: FronteggState, api: RestApi, sharedActions: Shar
|
|
|
71
71
|
resetBreachedPassword: (payload: import("dist/@frontegg/rest-api").IForgotPassword) => Promise<void>;
|
|
72
72
|
refreshTokenForSocialLogins: () => Promise<void>;
|
|
73
73
|
__refreshToken: () => Promise<void>;
|
|
74
|
+
__redirectToSsoForTenant: ({ tenantId, address }: {
|
|
75
|
+
tenantId: string;
|
|
76
|
+
address: string;
|
|
77
|
+
}) => Promise<void>;
|
|
74
78
|
};
|
|
75
79
|
export default _default;
|
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
});
|
|
7
7
|
exports.default = void 0;
|
|
8
8
|
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
|
|
9
|
+
var _restApi = require("@frontegg/rest-api");
|
|
9
10
|
var _helpers = require("../helpers");
|
|
10
11
|
var _tenantSelect = require("../helpers/tenantSelect");
|
|
11
12
|
var _constants = require("../../../constants");
|
|
@@ -63,6 +64,33 @@ var _default = (store, api, sharedActions) => {
|
|
|
63
64
|
}, additionalUpdate));
|
|
64
65
|
}
|
|
65
66
|
|
|
67
|
+
/**
|
|
68
|
+
* A switch that needed SSO saved its target tenant (in this tab) before sending the user to SSO.
|
|
69
|
+
* Once logged in again, switch to it once if the login landed in another tenant.
|
|
70
|
+
* @returns true when the switch left the user without a session (MFA step for the target tenant, or logout)
|
|
71
|
+
*/
|
|
72
|
+
async function switchToPendingSsoTenant() {
|
|
73
|
+
var _store$auth$user, _store$auth$user2;
|
|
74
|
+
const [isEnforceSsoOnTenantAccessEnabled] = _restApi.FeatureFlags.getFeatureFlags([_restApi.ENFORCE_SSO_ON_TENANT_ACCESS_FF], store.root.appName);
|
|
75
|
+
const pendingTenantId = window.sessionStorage.getItem(_constants.FRONTEGG_PENDING_SWITCH_TENANT);
|
|
76
|
+
if (!isEnforceSsoOnTenantAccessEnabled || !pendingTenantId || !((_store$auth$user = store.auth.user) != null && _store$auth$user.accessToken)) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
if (store.auth.user.tenantId === pendingTenantId) {
|
|
80
|
+
window.sessionStorage.removeItem(_constants.FRONTEGG_PENDING_SWITCH_TENANT);
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
// silentReload keeps switchingTenant set, so the switch's own refresh doesn't re-run this navigation
|
|
84
|
+
await actions.switchTenant({
|
|
85
|
+
tenantId: pendingTenantId,
|
|
86
|
+
silentReload: true,
|
|
87
|
+
keepLoginState: true
|
|
88
|
+
});
|
|
89
|
+
// removed only after the switch: a refused re-run must not send the user to SSO again
|
|
90
|
+
window.sessionStorage.removeItem(_constants.FRONTEGG_PENDING_SWITCH_TENANT);
|
|
91
|
+
return !((_store$auth$user2 = store.auth.user) != null && _store$auth$user2.accessToken);
|
|
92
|
+
}
|
|
93
|
+
|
|
66
94
|
/**
|
|
67
95
|
* @param customLoginAuthenticatedUrl custom login authenticated url if exists
|
|
68
96
|
* @returns the authenticated url to redirect to after auth navigation
|
|
@@ -166,7 +194,7 @@ var _default = (store, api, sharedActions) => {
|
|
|
166
194
|
* until the resolved-guard, the LD flag, and the option all pass.
|
|
167
195
|
*/
|
|
168
196
|
const shouldRouteToChooseTenant = async () => {
|
|
169
|
-
var _store$auth$tenantSel, _store$auth$
|
|
197
|
+
var _store$auth$tenantSel, _store$auth$user3, _store$auth$user3$act, _store$auth$tenantsSt;
|
|
170
198
|
const loginState = store.auth.loginState;
|
|
171
199
|
// Guard first so re-entry after the choice is resolved finalizes without re-reading the flag.
|
|
172
200
|
if (loginState.tenantChoiceResolved) {
|
|
@@ -182,7 +210,7 @@ var _default = (store, api, sharedActions) => {
|
|
|
182
210
|
return false; // feature off — never read tenant data
|
|
183
211
|
}
|
|
184
212
|
|
|
185
|
-
const isImpersonation = !!((_store$auth$
|
|
213
|
+
const isImpersonation = !!((_store$auth$user3 = store.auth.user) != null && (_store$auth$user3$act = _store$auth$user3.act) != null && _store$auth$user3$act.sub);
|
|
186
214
|
if (isImpersonation) {
|
|
187
215
|
return false;
|
|
188
216
|
}
|
|
@@ -260,6 +288,14 @@ var _default = (store, api, sharedActions) => {
|
|
|
260
288
|
}
|
|
261
289
|
try {
|
|
262
290
|
var _window4, _payload$preventRedir;
|
|
291
|
+
const isSessionLostInPendingSwitch = await switchToPendingSsoTenant();
|
|
292
|
+
if (isSessionLostInPendingSwitch) {
|
|
293
|
+
// MFA for the target tenant or a failed refresh: continue on the login page
|
|
294
|
+
store.auth.onRedirectTo(store.auth.routes.loginUrl, {
|
|
295
|
+
preserveQueryParams: true
|
|
296
|
+
});
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
263
299
|
const {
|
|
264
300
|
customLoginAuthenticatedUrl,
|
|
265
301
|
stepUpUrl
|
|
@@ -8,6 +8,7 @@ exports.default = void 0;
|
|
|
8
8
|
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
|
|
9
9
|
var _helpers = require("../../../helpers");
|
|
10
10
|
var _constants = require("../../../constants");
|
|
11
|
+
var _restApi = require("@frontegg/rest-api");
|
|
11
12
|
var _helpers2 = require("../helpers");
|
|
12
13
|
var _helpers3 = require("../../helpers");
|
|
13
14
|
var _consts = require("../../StepUpState/consts");
|
|
@@ -86,7 +87,10 @@ var _default = (store, api, sharedActions) => {
|
|
|
86
87
|
code_challenge_method: 'S256',
|
|
87
88
|
nonce
|
|
88
89
|
}, additionalParams);
|
|
89
|
-
|
|
90
|
+
const [isEnforceSsoOnTenantAccessEnabled] = _restApi.FeatureFlags.getFeatureFlags([_restApi.ENFORCE_SSO_ON_TENANT_ACCESS_FF], store.root.appName);
|
|
91
|
+
// the tenant asked for (SSO for a refused switch or refresh) wins over the resolved and the page's tenant
|
|
92
|
+
const isTenantRequested = isEnforceSsoOnTenantAccessEnabled && !!(additionalParams != null && additionalParams.tenantId);
|
|
93
|
+
if (context.tenantResolver && !isTenantRequested) {
|
|
90
94
|
var _context$tenantResolv;
|
|
91
95
|
const resolvedTenantResult = await ((_context$tenantResolv = context.tenantResolver) == null ? void 0 : _context$tenantResolv.call(context));
|
|
92
96
|
if (resolvedTenantResult != null && resolvedTenantResult.tenant) {
|
|
@@ -94,7 +98,7 @@ var _default = (store, api, sharedActions) => {
|
|
|
94
98
|
}
|
|
95
99
|
}
|
|
96
100
|
const tenantId = (0, _helpers2.getSearchParam)(_helpers2.TENANT_ID_PARAM_KEY);
|
|
97
|
-
if (tenantId) {
|
|
101
|
+
if (tenantId && !isTenantRequested) {
|
|
98
102
|
params['tenantId'] = tenantId;
|
|
99
103
|
}
|
|
100
104
|
if (_toolkit.FronteggNativeModule.isAvailable('closeWindow')) {
|
|
@@ -48,8 +48,67 @@ var _default = (store, api, sharedActions) => {
|
|
|
48
48
|
const actions = sharedActions;
|
|
49
49
|
const contextHolder = _restApi.ContextHolder.for(store.root.appName);
|
|
50
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Sends the user to log in through the tenant's SSO and back to the current page afterwards.
|
|
53
|
+
* @protected
|
|
54
|
+
*/
|
|
55
|
+
const __redirectToSsoForTenant = async ({
|
|
56
|
+
tenantId,
|
|
57
|
+
address
|
|
58
|
+
}) => {
|
|
59
|
+
// on the login page (tenant chooser) keep the destination the login already saved
|
|
60
|
+
if (!window.location.pathname.endsWith(store.auth.routes.loginUrl)) {
|
|
61
|
+
(0, _helpers3.setAfterAuthRedirectUrlForStepUp)();
|
|
62
|
+
}
|
|
63
|
+
if (store.auth.hostedLoginBox) {
|
|
64
|
+
await actions.requestHostedLoginAuthorize({
|
|
65
|
+
prompt: 'login',
|
|
66
|
+
tenantId
|
|
67
|
+
});
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
store.auth.onRedirectTo(address, {
|
|
71
|
+
refresh: true
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Handles a token refresh refused because the tenant requires SSO.
|
|
76
|
+
* ssoRequired: true sends the user to SSO. ssoRequired: false renews once more for the tenant of the
|
|
77
|
+
* current token, which keeps the session.
|
|
78
|
+
* @returns true when handled, false when the caller should log out
|
|
79
|
+
* @private
|
|
80
|
+
*/
|
|
81
|
+
const __handleSsoEnforcementRefreshError = async (e, isSsoEnforcementRetry, refreshForTenant) => {
|
|
82
|
+
var _store$auth$user;
|
|
83
|
+
const [isEnforceSsoOnTenantAccessEnabled] = _restApi.FeatureFlags.getFeatureFlags([_restApi.ENFORCE_SSO_ON_TENANT_ACCESS_FF], store.root.appName);
|
|
84
|
+
if (!isEnforceSsoOnTenantAccessEnabled || !(0, _helpers.isSsoEnforcementError)(e)) {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
const {
|
|
88
|
+
errorContext
|
|
89
|
+
} = e;
|
|
90
|
+
// the switch re-run after coming back from SSO keeps the session instead of sending the user to SSO again
|
|
91
|
+
const isPendingSwitchRerun = !!window.sessionStorage.getItem(_constants.FRONTEGG_PENDING_SWITCH_TENANT);
|
|
92
|
+
if (errorContext.ssoRequired && !isPendingSwitchRerun) {
|
|
93
|
+
await __redirectToSsoForTenant(errorContext);
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
if (isSsoEnforcementRetry) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
const currentTenantId = (_store$auth$user = store.auth.user) == null ? void 0 : _store$auth$user.tenantId;
|
|
100
|
+
if (currentTenantId && contextHolder.isSessionPerTenantEnabled()) {
|
|
101
|
+
(0, _restApi.setTabTenantInSessionStorage)(currentTenantId);
|
|
102
|
+
}
|
|
103
|
+
await refreshForTenant(currentTenantId);
|
|
104
|
+
return true;
|
|
105
|
+
};
|
|
106
|
+
|
|
51
107
|
/** @private */
|
|
52
|
-
const __refreshTokenHosted = async (
|
|
108
|
+
const __refreshTokenHosted = async ({
|
|
109
|
+
tenantId,
|
|
110
|
+
isSsoEnforcementRetry = false
|
|
111
|
+
} = {}) => {
|
|
53
112
|
const user = store.auth.user;
|
|
54
113
|
if (!(user != null && user.refreshToken)) {
|
|
55
114
|
contextHolder.setAccessToken(null);
|
|
@@ -61,7 +120,7 @@ var _default = (store, api, sharedActions) => {
|
|
|
61
120
|
return;
|
|
62
121
|
}
|
|
63
122
|
try {
|
|
64
|
-
const tabTenantId = (0, _restApi.getTabTenantFromSessionStorage)(store.root.appName);
|
|
123
|
+
const tabTenantId = tenantId != null ? tenantId : (0, _restApi.getTabTenantFromSessionStorage)(store.root.appName);
|
|
65
124
|
const body = (0, _extends2.default)({
|
|
66
125
|
grant_type: 'refresh_token',
|
|
67
126
|
refresh_token: user == null ? void 0 : user.refreshToken
|
|
@@ -76,6 +135,13 @@ var _default = (store, api, sharedActions) => {
|
|
|
76
135
|
isAuthenticated: true
|
|
77
136
|
});
|
|
78
137
|
} catch (e) {
|
|
138
|
+
const retry = tenantId => __refreshTokenHosted({
|
|
139
|
+
tenantId,
|
|
140
|
+
isSsoEnforcementRetry: true
|
|
141
|
+
});
|
|
142
|
+
if (await __handleSsoEnforcementRefreshError(e, isSsoEnforcementRetry, retry)) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
79
145
|
contextHolder.setAccessToken(null);
|
|
80
146
|
contextHolder.setUser(null);
|
|
81
147
|
actions.setAuthState({
|
|
@@ -95,7 +161,10 @@ var _default = (store, api, sharedActions) => {
|
|
|
95
161
|
};
|
|
96
162
|
|
|
97
163
|
/** @private */
|
|
98
|
-
const __refreshTokenEmbedded = async (
|
|
164
|
+
const __refreshTokenEmbedded = async ({
|
|
165
|
+
tenantId,
|
|
166
|
+
isSsoEnforcementRetry = false
|
|
167
|
+
} = {}) => {
|
|
99
168
|
try {
|
|
100
169
|
const {
|
|
101
170
|
routes,
|
|
@@ -106,7 +175,7 @@ var _default = (store, api, sharedActions) => {
|
|
|
106
175
|
user,
|
|
107
176
|
tenants = [],
|
|
108
177
|
activeTenant
|
|
109
|
-
} = await (0, _helpers.refreshTokenWithRetry)(() => api.auth.refreshTokenV3());
|
|
178
|
+
} = await (0, _helpers.refreshTokenWithRetry)(() => api.auth.refreshTokenV3(tenantId));
|
|
110
179
|
if ((0, _helpers3.isMfaRequired)(user, store.root.appName)) {
|
|
111
180
|
const mfaRequiredState = await actions.getMfaRequiredState(user);
|
|
112
181
|
actions.setAuthState(mfaRequiredState);
|
|
@@ -137,6 +206,13 @@ var _default = (store, api, sharedActions) => {
|
|
|
137
206
|
});
|
|
138
207
|
}
|
|
139
208
|
} catch (e) {
|
|
209
|
+
const retry = tenantId => __refreshTokenEmbedded({
|
|
210
|
+
tenantId,
|
|
211
|
+
isSsoEnforcementRetry: true
|
|
212
|
+
});
|
|
213
|
+
if (await __handleSsoEnforcementRefreshError(e, isSsoEnforcementRetry, retry)) {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
140
216
|
contextHolder.setAccessToken(null);
|
|
141
217
|
contextHolder.setUser(null);
|
|
142
218
|
actions.setAuthState({
|
|
@@ -147,7 +223,9 @@ var _default = (store, api, sharedActions) => {
|
|
|
147
223
|
};
|
|
148
224
|
|
|
149
225
|
/** @private */
|
|
150
|
-
const __refreshTokenWithNativeTokens = async (
|
|
226
|
+
const __refreshTokenWithNativeTokens = async ({
|
|
227
|
+
isSsoEnforcementRetry = false
|
|
228
|
+
} = {}) => {
|
|
151
229
|
try {
|
|
152
230
|
const authApi = api.auth;
|
|
153
231
|
const response = await (0, _helpers.refreshTokenWithRetry)(async () => {
|
|
@@ -161,6 +239,13 @@ var _default = (store, api, sharedActions) => {
|
|
|
161
239
|
isAuthenticated: true
|
|
162
240
|
});
|
|
163
241
|
} catch (e) {
|
|
242
|
+
// native tokens can't name a tenant: the retry relies on the tab tenant only
|
|
243
|
+
const retry = () => __refreshTokenWithNativeTokens({
|
|
244
|
+
isSsoEnforcementRetry: true
|
|
245
|
+
});
|
|
246
|
+
if (await __handleSsoEnforcementRefreshError(e, isSsoEnforcementRetry, retry)) {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
164
249
|
contextHolder.setAccessToken(null);
|
|
165
250
|
contextHolder.setUser(null);
|
|
166
251
|
actions.setAuthState({
|
|
@@ -194,9 +279,9 @@ var _default = (store, api, sharedActions) => {
|
|
|
194
279
|
* @private
|
|
195
280
|
*/
|
|
196
281
|
const __handleUnnecessaryEntitlementsUpdate = async updatedUser => {
|
|
197
|
-
var _store$auth$
|
|
282
|
+
var _store$auth$user2;
|
|
198
283
|
// @ts-ignore TODO: fix entitlements state type
|
|
199
|
-
const oldEntitlements = (_store$auth$
|
|
284
|
+
const oldEntitlements = (_store$auth$user2 = store.auth.user) == null ? void 0 : _store$auth$user2.entitlements;
|
|
200
285
|
if ((0, _toolkit.isEntitlementsDeeplyEqual)(oldEntitlements, updatedUser == null ? void 0 : updatedUser.entitlements)) {
|
|
201
286
|
// set the previous entitlements object
|
|
202
287
|
return (0, _extends2.default)({}, updatedUser, {
|
|
@@ -666,6 +751,7 @@ var _default = (store, api, sharedActions) => {
|
|
|
666
751
|
if (!keepHostedOAuthState) {
|
|
667
752
|
(0, _oauthStorage.clearHostedOAuthState)();
|
|
668
753
|
}
|
|
754
|
+
window.sessionStorage.removeItem(_constants.FRONTEGG_PENDING_SWITCH_TENANT);
|
|
669
755
|
};
|
|
670
756
|
const logout = async payload => {
|
|
671
757
|
const hostedLoginBox = store.auth.hostedLoginBox;
|
|
@@ -1368,7 +1454,10 @@ var _default = (store, api, sharedActions) => {
|
|
|
1368
1454
|
};
|
|
1369
1455
|
|
|
1370
1456
|
/** @private */
|
|
1371
|
-
const __refreshTokenHostedSocialLogins = async (
|
|
1457
|
+
const __refreshTokenHostedSocialLogins = async ({
|
|
1458
|
+
tenantId,
|
|
1459
|
+
isSsoEnforcementRetry = false
|
|
1460
|
+
} = {}) => {
|
|
1372
1461
|
const {
|
|
1373
1462
|
user: currentUser
|
|
1374
1463
|
} = store.auth;
|
|
@@ -1382,7 +1471,7 @@ var _default = (store, api, sharedActions) => {
|
|
|
1382
1471
|
return;
|
|
1383
1472
|
}
|
|
1384
1473
|
try {
|
|
1385
|
-
const tabTenantId = (0, _restApi.getTabTenantFromSessionStorage)(store.root.appName);
|
|
1474
|
+
const tabTenantId = tenantId != null ? tenantId : (0, _restApi.getTabTenantFromSessionStorage)(store.root.appName);
|
|
1386
1475
|
const body = (0, _extends2.default)({
|
|
1387
1476
|
grant_type: 'refresh_token',
|
|
1388
1477
|
refresh_token: currentUser == null ? void 0 : currentUser.refreshToken
|
|
@@ -1397,6 +1486,13 @@ var _default = (store, api, sharedActions) => {
|
|
|
1397
1486
|
isAuthenticated: true
|
|
1398
1487
|
});
|
|
1399
1488
|
} catch (e) {
|
|
1489
|
+
const retry = tenantId => __refreshTokenHostedSocialLogins({
|
|
1490
|
+
tenantId,
|
|
1491
|
+
isSsoEnforcementRetry: true
|
|
1492
|
+
});
|
|
1493
|
+
if (await __handleSsoEnforcementRefreshError(e, isSsoEnforcementRetry, retry)) {
|
|
1494
|
+
return;
|
|
1495
|
+
}
|
|
1400
1496
|
contextHolder.setAccessToken(null);
|
|
1401
1497
|
contextHolder.setUser(null);
|
|
1402
1498
|
actions.setAuthState({
|
|
@@ -1406,7 +1502,10 @@ var _default = (store, api, sharedActions) => {
|
|
|
1406
1502
|
}
|
|
1407
1503
|
};
|
|
1408
1504
|
/** @private */
|
|
1409
|
-
const __refreshTokenEmbeddedSocialLogins = async (
|
|
1505
|
+
const __refreshTokenEmbeddedSocialLogins = async ({
|
|
1506
|
+
tenantId,
|
|
1507
|
+
isSsoEnforcementRetry = false
|
|
1508
|
+
} = {}) => {
|
|
1410
1509
|
try {
|
|
1411
1510
|
const {
|
|
1412
1511
|
routes,
|
|
@@ -1417,7 +1516,7 @@ var _default = (store, api, sharedActions) => {
|
|
|
1417
1516
|
user,
|
|
1418
1517
|
tenants = [],
|
|
1419
1518
|
activeTenant
|
|
1420
|
-
} = await (0, _helpers.refreshTokenWithRetry)(() => api.auth.refreshTokenV3());
|
|
1519
|
+
} = await (0, _helpers.refreshTokenWithRetry)(() => api.auth.refreshTokenV3(tenantId));
|
|
1421
1520
|
if ((0, _helpers3.isMfaRequired)(user, store.root.appName)) {
|
|
1422
1521
|
const mfaRequiredState = await actions.getMfaRequiredState(user);
|
|
1423
1522
|
actions.setAuthState(mfaRequiredState);
|
|
@@ -1467,6 +1566,13 @@ var _default = (store, api, sharedActions) => {
|
|
|
1467
1566
|
}
|
|
1468
1567
|
}
|
|
1469
1568
|
} catch (e) {
|
|
1569
|
+
const retry = tenantId => __refreshTokenEmbeddedSocialLogins({
|
|
1570
|
+
tenantId,
|
|
1571
|
+
isSsoEnforcementRetry: true
|
|
1572
|
+
});
|
|
1573
|
+
if (await __handleSsoEnforcementRefreshError(e, isSsoEnforcementRetry, retry)) {
|
|
1574
|
+
return;
|
|
1575
|
+
}
|
|
1470
1576
|
contextHolder.setAccessToken(null);
|
|
1471
1577
|
contextHolder.setUser(null);
|
|
1472
1578
|
actions.setAuthState({
|
|
@@ -1513,7 +1619,8 @@ var _default = (store, api, sharedActions) => {
|
|
|
1513
1619
|
resetBreachedPassword,
|
|
1514
1620
|
refreshTokenForSocialLogins,
|
|
1515
1621
|
// protected
|
|
1516
|
-
__refreshToken
|
|
1622
|
+
__refreshToken,
|
|
1623
|
+
__redirectToSsoForTenant
|
|
1517
1624
|
}, (0, _hostedLoginAuthorize.default)(store, api, sharedActions), (0, _afterAuthNavigation.default)(store, api, sharedActions), (0, _handleVerifyMFAResponse.default)(store, api, sharedActions), (0, _mfaRequiredState.default)(store, api, sharedActions), (0, _mfaWithAuthenticator.default)(store, api, sharedActions), (0, _mfaWithEmailCode.default)(store, api, sharedActions), (0, _mfaWithSMS.default)(store, api, sharedActions), (0, _mfaWithWebAuthn.default)(store, api, sharedActions));
|
|
1518
1625
|
};
|
|
1519
1626
|
exports.default = _default;
|
|
@@ -7,6 +7,7 @@ exports.default = void 0;
|
|
|
7
7
|
var _state = require("./state");
|
|
8
8
|
var _helpers = require("../../helpers");
|
|
9
9
|
var _restApi = require("@frontegg/rest-api");
|
|
10
|
+
var _constants = require("../../constants");
|
|
10
11
|
var _default = (store, api, sharedActions) => {
|
|
11
12
|
const actions = sharedActions;
|
|
12
13
|
const setTenantsState = state => {
|
|
@@ -57,6 +58,15 @@ var _default = (store, api, sharedActions) => {
|
|
|
57
58
|
}) : actions.setAuthState({
|
|
58
59
|
isLoading: false
|
|
59
60
|
});
|
|
61
|
+
const [isEnforceSsoOnTenantAccessEnabled] = _restApi.FeatureFlags.getFeatureFlags([_restApi.ENFORCE_SSO_ON_TENANT_ACCESS_FF], store.root.appName);
|
|
62
|
+
// a switch re-run after coming back from SSO doesn't send the user to SSO again
|
|
63
|
+
const isPendingSwitchRerun = window.sessionStorage.getItem(_constants.FRONTEGG_PENDING_SWITCH_TENANT) === tenantId;
|
|
64
|
+
if (isEnforceSsoOnTenantAccessEnabled && (0, _helpers.isSsoEnforcementError)(e) && e.errorContext.ssoRequired && !isPendingSwitchRerun) {
|
|
65
|
+
// saved to finish the switch after the SSO login, which may land in another tenant
|
|
66
|
+
window.sessionStorage.setItem(_constants.FRONTEGG_PENDING_SWITCH_TENANT, tenantId);
|
|
67
|
+
await actions.__redirectToSsoForTenant(e.errorContext);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
60
70
|
callback == null ? void 0 : callback(false, e);
|
|
61
71
|
}
|
|
62
72
|
};
|
package/node/constants.js
CHANGED
|
@@ -5,10 +5,11 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
var _exportNames = {
|
|
7
7
|
FRONTEGG_AFTER_AUTH_REDIRECT_URL: true,
|
|
8
|
+
FRONTEGG_PENDING_SWITCH_TENANT: true,
|
|
8
9
|
HOSTED_LOGIN_VERIFIER_KEY: true,
|
|
9
10
|
DEFAULT_RETRY_CONFIG: true
|
|
10
11
|
};
|
|
11
|
-
exports.HOSTED_LOGIN_VERIFIER_KEY = exports.FRONTEGG_AFTER_AUTH_REDIRECT_URL = exports.DEFAULT_RETRY_CONFIG = void 0;
|
|
12
|
+
exports.HOSTED_LOGIN_VERIFIER_KEY = exports.FRONTEGG_PENDING_SWITCH_TENANT = exports.FRONTEGG_AFTER_AUTH_REDIRECT_URL = exports.DEFAULT_RETRY_CONFIG = void 0;
|
|
12
13
|
var _consts = require("./auth/StepUpState/consts");
|
|
13
14
|
Object.keys(_consts).forEach(function (key) {
|
|
14
15
|
if (key === "default" || key === "__esModule") return;
|
|
@@ -35,6 +36,8 @@ Object.keys(_consts2).forEach(function (key) {
|
|
|
35
36
|
});
|
|
36
37
|
const FRONTEGG_AFTER_AUTH_REDIRECT_URL = 'FRONTEGG_AFTER_AUTH_REDIRECT_URL';
|
|
37
38
|
exports.FRONTEGG_AFTER_AUTH_REDIRECT_URL = FRONTEGG_AFTER_AUTH_REDIRECT_URL;
|
|
39
|
+
const FRONTEGG_PENDING_SWITCH_TENANT = 'FRONTEGG_PENDING_SWITCH_TENANT';
|
|
40
|
+
exports.FRONTEGG_PENDING_SWITCH_TENANT = FRONTEGG_PENDING_SWITCH_TENANT;
|
|
38
41
|
const HOSTED_LOGIN_VERIFIER_KEY = 'HOSTED_LOGIN_VERIFIER_KEY';
|
|
39
42
|
exports.HOSTED_LOGIN_VERIFIER_KEY = HOSTED_LOGIN_VERIFIER_KEY;
|
|
40
43
|
const DEFAULT_RETRY_CONFIG = {
|
package/node/helpers/handlers.js
CHANGED
|
@@ -3,10 +3,14 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.isFronteggApiError = exports.isError = exports.errorHandler = void 0;
|
|
6
|
+
exports.isSsoEnforcementError = exports.isFronteggApiError = exports.isError = exports.errorHandler = void 0;
|
|
7
7
|
var _restApi = require("@frontegg/rest-api");
|
|
8
8
|
const isFronteggApiError = error => Boolean(error == null ? void 0 : error.statusCode);
|
|
9
|
+
|
|
10
|
+
// ER-01079 (disabled user / expired tenant) is not this: it still logs out
|
|
9
11
|
exports.isFronteggApiError = isFronteggApiError;
|
|
12
|
+
const isSsoEnforcementError = error => isFronteggApiError(error) && error.errorCode === _restApi.SSO_REQUIRED_FOR_TENANT_ERROR_CODE;
|
|
13
|
+
exports.isSsoEnforcementError = isSsoEnforcementError;
|
|
10
14
|
const isError = error => {
|
|
11
15
|
if (error instanceof Error) {
|
|
12
16
|
return true;
|
package/node/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frontegg/redux-store",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.129.0-alpha.0",
|
|
4
4
|
"main": "./node/index.js",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Frontegg LTD",
|
|
7
7
|
"dependencies": {
|
|
8
8
|
"@babel/runtime": "^7.18.6",
|
|
9
9
|
"@frontegg/entitlements-javascript-commons": "1.1.2",
|
|
10
|
-
"@frontegg/rest-api": "7.
|
|
10
|
+
"@frontegg/rest-api": "7.129.0-alpha.0",
|
|
11
11
|
"fast-deep-equal": "3.1.3",
|
|
12
12
|
"get-value": "^3.0.1",
|
|
13
13
|
"proxy-compare": "^3.0.0",
|