@frontegg/redux-store 7.130.0 → 7.131.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/auth/ActivateAccountState/actions.js +6 -0
- package/auth/LoginState/actions/hostedLoginAuthorize.actions.d.ts +3 -1
- package/auth/LoginState/actions/hostedLoginAuthorize.actions.js +29 -6
- package/auth/LoginState/actions/index.d.ts +8 -1
- package/auth/LoginState/actions/index.js +172 -11
- package/auth/LoginState/actions/mfaWithAuthenticator.actions.js +3 -0
- package/auth/LoginState/actions/mfaWithEmailCode.actions.js +3 -0
- package/auth/LoginState/actions/mfaWithSMS.actions.js +3 -0
- package/auth/LoginState/actions/mfaWithWebAuthn.actions.js +3 -0
- package/auth/MfaState/actions.js +3 -0
- package/auth/SocialLoginState/actions.js +3 -0
- package/auth/StepUpState/actions/generateStepUpSession.actions.js +5 -0
- package/auth/StepUpState/helpers.d.ts +2 -1
- package/auth/StepUpState/helpers.js +3 -2
- package/auth/TenantsState/actions.js +8 -1
- package/auth/helpers.d.ts +15 -0
- package/auth/helpers.js +60 -2
- 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 +8 -1
- package/node/auth/ActivateAccountState/actions.js +6 -0
- package/node/auth/LoginState/actions/hostedLoginAuthorize.actions.js +27 -4
- package/node/auth/LoginState/actions/index.js +169 -8
- package/node/auth/LoginState/actions/mfaWithAuthenticator.actions.js +3 -0
- package/node/auth/LoginState/actions/mfaWithEmailCode.actions.js +3 -0
- package/node/auth/LoginState/actions/mfaWithSMS.actions.js +3 -0
- package/node/auth/LoginState/actions/mfaWithWebAuthn.actions.js +3 -0
- package/node/auth/MfaState/actions.js +3 -0
- package/node/auth/SocialLoginState/actions.js +3 -0
- package/node/auth/StepUpState/actions/generateStepUpSession.actions.js +5 -0
- package/node/auth/StepUpState/helpers.js +3 -2
- package/node/auth/TenantsState/actions.js +7 -0
- package/node/auth/helpers.js +66 -2
- package/node/helpers/handlers.js +5 -1
- package/node/index.js +1 -1
- package/package.json +2 -2
|
@@ -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,
|
|
@@ -8,7 +8,9 @@ declare const _default: (store: FronteggState, api: RestApi, sharedActions: Shar
|
|
|
8
8
|
firstTime?: boolean;
|
|
9
9
|
loginDirectAction?: LoginDirectAction;
|
|
10
10
|
}) => Promise<void>;
|
|
11
|
-
__requestHostedLoginAuthorize: (additionalParams?: Record<string, string
|
|
11
|
+
__requestHostedLoginAuthorize: (additionalParams?: Record<string, string>, { forceTenantId }?: {
|
|
12
|
+
forceTenantId?: boolean;
|
|
13
|
+
}) => Promise<void>;
|
|
12
14
|
__requestHostedLoginSilentAuthorize: () => Promise<void>;
|
|
13
15
|
handleHostedLoginCallback: (payload: HostedLoginCallback) => Promise<void>;
|
|
14
16
|
};
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import _extends from "@babel/runtime/helpers/esm/extends";
|
|
2
|
-
import { createRandomString, generateCodeChallenge, GTMEventAction, refreshTokenWithRetry, reportGTMEvent } from '../../../helpers';
|
|
2
|
+
import { createRandomString, generateCodeChallenge, GTMEventAction, isSsoEnforcementError, refreshTokenWithRetry, reportGTMEvent } from '../../../helpers';
|
|
3
3
|
import { HOSTED_LOGIN_VERIFIER_KEY } from '../../../constants';
|
|
4
|
+
import { isEnforceSsoOnTenantAccessEnabled } from '@frontegg/rest-api';
|
|
4
5
|
import { getBaseNameWithoutSlashSuffix, getSearchParam, isOauthCallbackRoute, TENANT_ID_PARAM_KEY } from '../helpers';
|
|
5
|
-
import { getUri, getUrlDerivedStepUp, isSteppedUp, redirectByStepUpUrl } from '../../helpers';
|
|
6
|
+
import { getUri, getUrlDerivedStepUp, isSteppedUp, redirectByStepUpUrl, restoreTabTenantUnlessSsoCallback } from '../../helpers';
|
|
6
7
|
import { SHOULD_STEP_UP_KEY } from '../../StepUpState/consts';
|
|
7
8
|
import { FronteggNativeModule } from '../../../toolkit';
|
|
8
9
|
export default ((store, api, sharedActions) => {
|
|
@@ -14,6 +15,7 @@ export default ((store, api, sharedActions) => {
|
|
|
14
15
|
const __requestHostedLoginSilentAuthorize = async () => {
|
|
15
16
|
var _response;
|
|
16
17
|
let response;
|
|
18
|
+
restoreTabTenantUnlessSsoCallback(store);
|
|
17
19
|
if (FronteggNativeModule.isGetTokensAvailable()) {
|
|
18
20
|
const authApi = api.auth;
|
|
19
21
|
response = await refreshTokenWithRetry(async () => {
|
|
@@ -21,7 +23,21 @@ export default ((store, api, sharedActions) => {
|
|
|
21
23
|
return await authApi.silentOAuthRefreshTokenV3(tokens);
|
|
22
24
|
});
|
|
23
25
|
} else {
|
|
24
|
-
|
|
26
|
+
try {
|
|
27
|
+
response = await api.auth.silentOAuthRefreshTokenV2();
|
|
28
|
+
} catch (error) {
|
|
29
|
+
var _store$auth$user, _error$errorContext;
|
|
30
|
+
// the tenant requires SSO: hosted login for that tenant, not the plain one the caller falls back to
|
|
31
|
+
if (await actions.__redirectToSsoIfRequired(error)) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const tenantId = (_store$auth$user = store.auth.user) == null ? void 0 : _store$auth$user.tenantId;
|
|
35
|
+
if (!isEnforceSsoOnTenantAccessEnabled(store.root.appName) || !isSsoEnforcementError(error) || ((_error$errorContext = error.errorContext) == null ? void 0 : _error$errorContext.ssoRequired) !== false || !tenantId) {
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
// one retry for the tenant of the current token (a cold reload has no user yet: no retry)
|
|
39
|
+
response = await api.auth.silentOAuthRefreshTokenV2(tenantId);
|
|
40
|
+
}
|
|
25
41
|
}
|
|
26
42
|
if ((_response = response) != null && _response.user) {
|
|
27
43
|
await actions.afterAuthenticationStateUpdate(response, {
|
|
@@ -32,7 +48,14 @@ export default ((store, api, sharedActions) => {
|
|
|
32
48
|
throw new Error(`couldn't refresh user token with oauth service`);
|
|
33
49
|
}
|
|
34
50
|
};
|
|
35
|
-
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @param options.forceTenantId the tenantId in additionalParams wins over the resolved and the page's tenant
|
|
54
|
+
* (sending the user to that tenant's SSO); other callers keep their custom login tenant
|
|
55
|
+
*/
|
|
56
|
+
const requestHostedLoginAuthorize = async (additionalParams, {
|
|
57
|
+
forceTenantId = false
|
|
58
|
+
} = {}) => {
|
|
36
59
|
var _store$auth$customLog;
|
|
37
60
|
const {
|
|
38
61
|
routes,
|
|
@@ -79,7 +102,7 @@ export default ((store, api, sharedActions) => {
|
|
|
79
102
|
code_challenge_method: 'S256',
|
|
80
103
|
nonce
|
|
81
104
|
}, additionalParams);
|
|
82
|
-
if (context.tenantResolver) {
|
|
105
|
+
if (context.tenantResolver && !forceTenantId) {
|
|
83
106
|
var _context$tenantResolv;
|
|
84
107
|
const resolvedTenantResult = await ((_context$tenantResolv = context.tenantResolver) == null ? void 0 : _context$tenantResolv.call(context));
|
|
85
108
|
if (resolvedTenantResult != null && resolvedTenantResult.tenant) {
|
|
@@ -87,7 +110,7 @@ export default ((store, api, sharedActions) => {
|
|
|
87
110
|
}
|
|
88
111
|
}
|
|
89
112
|
const tenantId = getSearchParam(TENANT_ID_PARAM_KEY);
|
|
90
|
-
if (tenantId) {
|
|
113
|
+
if (tenantId && !forceTenantId) {
|
|
91
114
|
params['tenantId'] = tenantId;
|
|
92
115
|
}
|
|
93
116
|
if (FronteggNativeModule.isAvailable('closeWindow')) {
|
|
@@ -37,7 +37,9 @@ declare const _default: (store: FronteggState, api: RestApi, sharedActions: Shar
|
|
|
37
37
|
firstTime?: boolean;
|
|
38
38
|
loginDirectAction?: import("../interfaces").LoginDirectAction;
|
|
39
39
|
}) => Promise<void>;
|
|
40
|
-
__requestHostedLoginAuthorize: (additionalParams?: Record<string, string
|
|
40
|
+
__requestHostedLoginAuthorize: (additionalParams?: Record<string, string>, { forceTenantId }?: {
|
|
41
|
+
forceTenantId?: boolean;
|
|
42
|
+
}) => Promise<void>;
|
|
41
43
|
__requestHostedLoginSilentAuthorize: () => Promise<void>;
|
|
42
44
|
handleHostedLoginCallback: (payload: import("../interfaces").HostedLoginCallback) => Promise<void>;
|
|
43
45
|
setLoginState: (state: Partial<LoginState>) => void;
|
|
@@ -75,5 +77,10 @@ declare const _default: (store: FronteggState, api: RestApi, sharedActions: Shar
|
|
|
75
77
|
resetBreachedPassword: (payload: IForgotPassword) => Promise<void>;
|
|
76
78
|
refreshTokenForSocialLogins: () => Promise<void>;
|
|
77
79
|
__refreshToken: () => Promise<void>;
|
|
80
|
+
__redirectToSsoForTenant: ({ tenantId, address }: {
|
|
81
|
+
tenantId: string;
|
|
82
|
+
address: string;
|
|
83
|
+
}) => Promise<void>;
|
|
84
|
+
__redirectToSsoIfRequired: (e: unknown) => Promise<boolean>;
|
|
78
85
|
};
|
|
79
86
|
export default _default;
|
|
@@ -23,17 +23,17 @@ 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, isEnforceSsoOnTenantAccessEnabled, 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, defaultOnRedirectTo } from '../consts';
|
|
36
|
-
import { isMfaRequired, isResetPasswordRequired } from '../../helpers';
|
|
36
|
+
import { isMfaRequired, isOnAuthPageRoute, isOnOidcCallbackRoute, isResetPasswordRequired, restoreTabTenantUnlessSsoCallback, setAfterAuthRedirectUrlForStepUp } from '../../helpers';
|
|
37
37
|
import { MFAStep } from '../../MfaState/interfaces';
|
|
38
38
|
import { SamlVendors } from '../../SSOState/interfaces';
|
|
39
39
|
import { DEFAULT_RETRY_CONFIG } from '../../../constants';
|
|
@@ -43,8 +43,114 @@ export default ((store, api, sharedActions) => {
|
|
|
43
43
|
const actions = sharedActions;
|
|
44
44
|
const contextHolder = ContextHolder.for(store.root.appName);
|
|
45
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Sends the user to log in through the tenant's SSO and back to the current page afterwards.
|
|
48
|
+
* @protected
|
|
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;
|
|
53
|
+
const __redirectToSsoForTenant = async ({
|
|
54
|
+
tenantId,
|
|
55
|
+
address
|
|
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
|
+
});
|
|
82
|
+
// on an auth page (login, tenant chooser, social / magic link / SSO callback) keep the destination the login
|
|
83
|
+
// already saved: the page itself is not one to come back to
|
|
84
|
+
const urlStrategy = (_store$root$urlStrate = store.root.urlStrategy) != null ? _store$root$urlStrate : 'path';
|
|
85
|
+
const isOnAuthPage = isOnAuthPageRoute(urlStrategy, store.auth.routes);
|
|
86
|
+
if (!isOnAuthPage) {
|
|
87
|
+
setAfterAuthRedirectUrlForStepUp(urlStrategy);
|
|
88
|
+
}
|
|
89
|
+
if (store.auth.hostedLoginBox) {
|
|
90
|
+
var _window$localStorage;
|
|
91
|
+
// as loginWithRedirect does, but the SSO tenant wins over the custom login tenant
|
|
92
|
+
(_window$localStorage = window.localStorage) == null ? void 0 : _window$localStorage.removeItem(SHOULD_STEP_UP_KEY);
|
|
93
|
+
await actions.__requestHostedLoginAuthorize({
|
|
94
|
+
prompt: 'login',
|
|
95
|
+
tenantId
|
|
96
|
+
}, {
|
|
97
|
+
forceTenantId: true
|
|
98
|
+
});
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
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);
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Sends the user to the tenant's SSO when a login or tenant switch is refused because that tenant requires it
|
|
111
|
+
* (e.g. an invitation into a tenant whose SSO covers the user's email). The SSO login lands in that tenant.
|
|
112
|
+
* @returns true when redirected, so the caller doesn't show the error
|
|
113
|
+
* @protected
|
|
114
|
+
*/
|
|
115
|
+
const __redirectToSsoIfRequired = async e => {
|
|
116
|
+
var _e$errorContext;
|
|
117
|
+
if (!isEnforceSsoOnTenantAccessEnabled(store.root.appName) || !isSsoEnforcementError(e) || !((_e$errorContext = e.errorContext) != null && _e$errorContext.ssoRequired)) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
await __redirectToSsoForTenant(e.errorContext);
|
|
121
|
+
return true;
|
|
122
|
+
};
|
|
123
|
+
/**
|
|
124
|
+
* Handles a hosted (OAuth) token refresh refused because the tenant requires SSO.
|
|
125
|
+
* ssoRequired: true sends the user to SSO, like login. ssoRequired: false renews once more for the tenant of the
|
|
126
|
+
* current token, which keeps the session: OAuth doesn't use up the refresh token on this refusal.
|
|
127
|
+
* @returns true when handled, false when the caller should log out
|
|
128
|
+
* @private
|
|
129
|
+
*/
|
|
130
|
+
const __handleSsoEnforcementRefreshError = async (e, isSsoEnforcementRetry, refreshForTenant) => {
|
|
131
|
+
var _store$auth$user;
|
|
132
|
+
if (!isEnforceSsoOnTenantAccessEnabled(store.root.appName) || !isSsoEnforcementError(e)) {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
if (await __redirectToSsoIfRequired(e)) {
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
const currentTenantId = (_store$auth$user = store.auth.user) == null ? void 0 : _store$auth$user.tenantId;
|
|
139
|
+
if (isSsoEnforcementRetry || !currentTenantId) {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
if (contextHolder.isSessionPerTenantEnabled()) {
|
|
143
|
+
setTabTenantInSessionStorage(currentTenantId);
|
|
144
|
+
}
|
|
145
|
+
await refreshForTenant(currentTenantId);
|
|
146
|
+
return true;
|
|
147
|
+
};
|
|
148
|
+
|
|
46
149
|
/** @private */
|
|
47
|
-
const __refreshTokenHosted = async (
|
|
150
|
+
const __refreshTokenHosted = async ({
|
|
151
|
+
tenantId,
|
|
152
|
+
isSsoEnforcementRetry = false
|
|
153
|
+
} = {}) => {
|
|
48
154
|
const user = store.auth.user;
|
|
49
155
|
if (!(user != null && user.refreshToken)) {
|
|
50
156
|
contextHolder.setAccessToken(null);
|
|
@@ -56,7 +162,7 @@ export default ((store, api, sharedActions) => {
|
|
|
56
162
|
return;
|
|
57
163
|
}
|
|
58
164
|
try {
|
|
59
|
-
const tabTenantId = getTabTenantFromSessionStorage(store.root.appName);
|
|
165
|
+
const tabTenantId = tenantId != null ? tenantId : getTabTenantFromSessionStorage(store.root.appName);
|
|
60
166
|
const body = _extends({
|
|
61
167
|
grant_type: 'refresh_token',
|
|
62
168
|
refresh_token: user == null ? void 0 : user.refreshToken
|
|
@@ -71,6 +177,13 @@ export default ((store, api, sharedActions) => {
|
|
|
71
177
|
isAuthenticated: true
|
|
72
178
|
});
|
|
73
179
|
} catch (e) {
|
|
180
|
+
const retry = tenantId => __refreshTokenHosted({
|
|
181
|
+
tenantId,
|
|
182
|
+
isSsoEnforcementRetry: true
|
|
183
|
+
});
|
|
184
|
+
if (await __handleSsoEnforcementRefreshError(e, isSsoEnforcementRetry, retry)) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
74
187
|
contextHolder.setAccessToken(null);
|
|
75
188
|
contextHolder.setUser(null);
|
|
76
189
|
actions.setAuthState({
|
|
@@ -132,6 +245,11 @@ export default ((store, api, sharedActions) => {
|
|
|
132
245
|
});
|
|
133
246
|
}
|
|
134
247
|
} catch (e) {
|
|
248
|
+
// a refused identity refresh has already used up the refresh token: no retry, only the SSO redirect, except on
|
|
249
|
+
// the OIDC callback, where the page itself completes the OIDC login
|
|
250
|
+
if (isEnforceSsoOnTenantAccessEnabled(store.root.appName) && !isOnOidcCallbackRoute(store) && (await __redirectToSsoIfRequired(e))) {
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
135
253
|
contextHolder.setAccessToken(null);
|
|
136
254
|
contextHolder.setUser(null);
|
|
137
255
|
actions.setAuthState({
|
|
@@ -142,7 +260,9 @@ export default ((store, api, sharedActions) => {
|
|
|
142
260
|
};
|
|
143
261
|
|
|
144
262
|
/** @private */
|
|
145
|
-
const __refreshTokenWithNativeTokens = async (
|
|
263
|
+
const __refreshTokenWithNativeTokens = async ({
|
|
264
|
+
isSsoEnforcementRetry = false
|
|
265
|
+
} = {}) => {
|
|
146
266
|
try {
|
|
147
267
|
const authApi = api.auth;
|
|
148
268
|
const response = await refreshTokenWithRetry(async () => {
|
|
@@ -156,6 +276,13 @@ export default ((store, api, sharedActions) => {
|
|
|
156
276
|
isAuthenticated: true
|
|
157
277
|
});
|
|
158
278
|
} catch (e) {
|
|
279
|
+
// native tokens can't name a tenant: the retry relies on the tab tenant only
|
|
280
|
+
const retry = () => __refreshTokenWithNativeTokens({
|
|
281
|
+
isSsoEnforcementRetry: true
|
|
282
|
+
});
|
|
283
|
+
if (await __handleSsoEnforcementRefreshError(e, isSsoEnforcementRetry, retry)) {
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
159
286
|
contextHolder.setAccessToken(null);
|
|
160
287
|
contextHolder.setUser(null);
|
|
161
288
|
actions.setAuthState({
|
|
@@ -167,6 +294,7 @@ export default ((store, api, sharedActions) => {
|
|
|
167
294
|
|
|
168
295
|
/** @protected */
|
|
169
296
|
const __refreshToken = async () => {
|
|
297
|
+
restoreTabTenantUnlessSsoCallback(store);
|
|
170
298
|
if (FronteggNativeModule.isGetTokensAvailable()) {
|
|
171
299
|
await __refreshTokenWithNativeTokens();
|
|
172
300
|
return;
|
|
@@ -189,9 +317,9 @@ export default ((store, api, sharedActions) => {
|
|
|
189
317
|
* @private
|
|
190
318
|
*/
|
|
191
319
|
const __handleUnnecessaryEntitlementsUpdate = async updatedUser => {
|
|
192
|
-
var _store$auth$
|
|
320
|
+
var _store$auth$user2;
|
|
193
321
|
// @ts-ignore TODO: fix entitlements state type
|
|
194
|
-
const oldEntitlements = (_store$auth$
|
|
322
|
+
const oldEntitlements = (_store$auth$user2 = store.auth.user) == null ? void 0 : _store$auth$user2.entitlements;
|
|
195
323
|
if (isEntitlementsDeeplyEqual(oldEntitlements, updatedUser == null ? void 0 : updatedUser.entitlements)) {
|
|
196
324
|
// set the previous entitlements object
|
|
197
325
|
return _extends({}, updatedUser, {
|
|
@@ -668,6 +796,9 @@ export default ((store, api, sharedActions) => {
|
|
|
668
796
|
} catch (e) {
|
|
669
797
|
contextHolder.setAccessToken(null);
|
|
670
798
|
contextHolder.setUser(null);
|
|
799
|
+
if (await __redirectToSsoIfRequired(e)) {
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
671
802
|
callback == null ? void 0 : callback(false, e);
|
|
672
803
|
setLoginState({
|
|
673
804
|
email,
|
|
@@ -963,6 +1094,9 @@ export default ((store, api, sharedActions) => {
|
|
|
963
1094
|
callback == null ? void 0 : callback(true);
|
|
964
1095
|
}
|
|
965
1096
|
} catch (e) {
|
|
1097
|
+
if (await __redirectToSsoIfRequired(e)) {
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
966
1100
|
setLoginState({
|
|
967
1101
|
error: errorHandler(e, 'Failed to authenticate')
|
|
968
1102
|
});
|
|
@@ -1099,6 +1233,9 @@ export default ((store, api, sharedActions) => {
|
|
|
1099
1233
|
}
|
|
1100
1234
|
// callback?.(true);
|
|
1101
1235
|
} catch (e) {
|
|
1236
|
+
if (await __redirectToSsoIfRequired(e)) {
|
|
1237
|
+
return;
|
|
1238
|
+
}
|
|
1102
1239
|
setLoginState({
|
|
1103
1240
|
error: errorHandler(e)
|
|
1104
1241
|
});
|
|
@@ -1259,6 +1396,9 @@ export default ((store, api, sharedActions) => {
|
|
|
1259
1396
|
});
|
|
1260
1397
|
callback == null ? void 0 : callback(true);
|
|
1261
1398
|
} catch (e) {
|
|
1399
|
+
if (await __redirectToSsoIfRequired(e)) {
|
|
1400
|
+
return;
|
|
1401
|
+
}
|
|
1262
1402
|
setLoginState({
|
|
1263
1403
|
loading: false,
|
|
1264
1404
|
error: errorHandler(e)
|
|
@@ -1282,6 +1422,9 @@ export default ((store, api, sharedActions) => {
|
|
|
1282
1422
|
});
|
|
1283
1423
|
callback == null ? void 0 : callback(true);
|
|
1284
1424
|
} catch (e) {
|
|
1425
|
+
if (await __redirectToSsoIfRequired(e)) {
|
|
1426
|
+
return;
|
|
1427
|
+
}
|
|
1285
1428
|
setLoginState({
|
|
1286
1429
|
loading: false,
|
|
1287
1430
|
error: errorHandler(e)
|
|
@@ -1332,6 +1475,9 @@ export default ((store, api, sharedActions) => {
|
|
|
1332
1475
|
});
|
|
1333
1476
|
callback == null ? void 0 : callback(true);
|
|
1334
1477
|
} catch (e) {
|
|
1478
|
+
if (await __redirectToSsoIfRequired(e)) {
|
|
1479
|
+
return;
|
|
1480
|
+
}
|
|
1335
1481
|
setLoginState({
|
|
1336
1482
|
loading: false,
|
|
1337
1483
|
error: errorHandler(e)
|
|
@@ -1388,7 +1534,10 @@ export default ((store, api, sharedActions) => {
|
|
|
1388
1534
|
};
|
|
1389
1535
|
|
|
1390
1536
|
/** @private */
|
|
1391
|
-
const __refreshTokenHostedSocialLogins = async (
|
|
1537
|
+
const __refreshTokenHostedSocialLogins = async ({
|
|
1538
|
+
tenantId,
|
|
1539
|
+
isSsoEnforcementRetry = false
|
|
1540
|
+
} = {}) => {
|
|
1392
1541
|
const {
|
|
1393
1542
|
user: currentUser
|
|
1394
1543
|
} = store.auth;
|
|
@@ -1402,7 +1551,7 @@ export default ((store, api, sharedActions) => {
|
|
|
1402
1551
|
return;
|
|
1403
1552
|
}
|
|
1404
1553
|
try {
|
|
1405
|
-
const tabTenantId = getTabTenantFromSessionStorage(store.root.appName);
|
|
1554
|
+
const tabTenantId = tenantId != null ? tenantId : getTabTenantFromSessionStorage(store.root.appName);
|
|
1406
1555
|
const body = _extends({
|
|
1407
1556
|
grant_type: 'refresh_token',
|
|
1408
1557
|
refresh_token: currentUser == null ? void 0 : currentUser.refreshToken
|
|
@@ -1417,6 +1566,13 @@ export default ((store, api, sharedActions) => {
|
|
|
1417
1566
|
isAuthenticated: true
|
|
1418
1567
|
});
|
|
1419
1568
|
} catch (e) {
|
|
1569
|
+
const retry = tenantId => __refreshTokenHostedSocialLogins({
|
|
1570
|
+
tenantId,
|
|
1571
|
+
isSsoEnforcementRetry: true
|
|
1572
|
+
});
|
|
1573
|
+
if (await __handleSsoEnforcementRefreshError(e, isSsoEnforcementRetry, retry)) {
|
|
1574
|
+
return;
|
|
1575
|
+
}
|
|
1420
1576
|
contextHolder.setAccessToken(null);
|
|
1421
1577
|
contextHolder.setUser(null);
|
|
1422
1578
|
actions.setAuthState({
|
|
@@ -1487,6 +1643,9 @@ export default ((store, api, sharedActions) => {
|
|
|
1487
1643
|
}
|
|
1488
1644
|
}
|
|
1489
1645
|
} catch (e) {
|
|
1646
|
+
if (await __redirectToSsoIfRequired(e)) {
|
|
1647
|
+
return;
|
|
1648
|
+
}
|
|
1490
1649
|
contextHolder.setAccessToken(null);
|
|
1491
1650
|
contextHolder.setUser(null);
|
|
1492
1651
|
actions.setAuthState({
|
|
@@ -1533,6 +1692,8 @@ export default ((store, api, sharedActions) => {
|
|
|
1533
1692
|
resetBreachedPassword,
|
|
1534
1693
|
refreshTokenForSocialLogins,
|
|
1535
1694
|
// protected
|
|
1536
|
-
__refreshToken
|
|
1695
|
+
__refreshToken,
|
|
1696
|
+
__redirectToSsoForTenant,
|
|
1697
|
+
__redirectToSsoIfRequired
|
|
1537
1698
|
}, 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));
|
|
1538
1699
|
});
|
|
@@ -145,6 +145,9 @@ export default ((store, api, sharedActions) => {
|
|
|
145
145
|
}
|
|
146
146
|
return await __postLoginMfaAuthenticator(isAuthenticated, user, callback);
|
|
147
147
|
} catch (e) {
|
|
148
|
+
if (await actions.__redirectToSsoIfRequired(e)) {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
148
151
|
setLoadingAction({
|
|
149
152
|
loading: false,
|
|
150
153
|
error: errorHandler(e)
|
package/auth/MfaState/actions.js
CHANGED
|
@@ -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)
|
|
@@ -157,6 +157,9 @@ export default ((store, api, sharedActions) => {
|
|
|
157
157
|
loading: false
|
|
158
158
|
});
|
|
159
159
|
} catch (e) {
|
|
160
|
+
if (await actions.__redirectToSsoIfRequired(e)) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
160
163
|
setSocialLoginsState({
|
|
161
164
|
loading: false,
|
|
162
165
|
error: errorHandler(e, 'Failed to authenticate'),
|
|
@@ -95,6 +95,11 @@ export default ((store, api, sharedActions) => {
|
|
|
95
95
|
callback == null ? void 0 : callback(true);
|
|
96
96
|
} catch (e) {
|
|
97
97
|
callback == null ? void 0 : callback(false);
|
|
98
|
+
|
|
99
|
+
// the session's tenant requires SSO: its SSO login steps the user up, instead of logging them out
|
|
100
|
+
if (await actions.__redirectToSsoIfRequired(e)) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
98
103
|
await __handleStepUpError(e);
|
|
99
104
|
}
|
|
100
105
|
};
|
|
@@ -22,8 +22,9 @@ export interface UrlDerivedStepUp {
|
|
|
22
22
|
export declare const getUrlDerivedStepUp: () => UrlDerivedStepUp;
|
|
23
23
|
/**
|
|
24
24
|
* Set the url and query params in the local storage FRONTEGG_AFTER_AUTH_REDIRECT_URL value
|
|
25
|
+
* @param urlStrategy 'hash' saves the route in the hash, which is the page the app shows
|
|
25
26
|
*/
|
|
26
|
-
export declare function setAfterAuthRedirectUrlForStepUp(): void;
|
|
27
|
+
export declare function setAfterAuthRedirectUrlForStepUp(urlStrategy?: 'path' | 'hash'): void;
|
|
27
28
|
/**
|
|
28
29
|
* Redirects to the step up url with the max age param and set the redirect url in the local storage
|
|
29
30
|
* The redirect url will be used after the step up flow is done
|
|
@@ -51,9 +51,10 @@ export const getUrlDerivedStepUp = () => {
|
|
|
51
51
|
|
|
52
52
|
/**
|
|
53
53
|
* Set the url and query params in the local storage FRONTEGG_AFTER_AUTH_REDIRECT_URL value
|
|
54
|
+
* @param urlStrategy 'hash' saves the route in the hash, which is the page the app shows
|
|
54
55
|
*/
|
|
55
|
-
export function setAfterAuthRedirectUrlForStepUp() {
|
|
56
|
-
const encodedRedirectUrl = window.location.pathname + window.location.search;
|
|
56
|
+
export function setAfterAuthRedirectUrlForStepUp(urlStrategy = 'path') {
|
|
57
|
+
const encodedRedirectUrl = urlStrategy === 'hash' ? window.location.hash.substring(1) || '/' : window.location.pathname + window.location.search;
|
|
57
58
|
window.localStorage.setItem(FRONTEGG_AFTER_AUTH_REDIRECT_URL, encodedRedirectUrl);
|
|
58
59
|
}
|
|
59
60
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { initialState } from './state';
|
|
2
2
|
import { deepResetState } from '../../helpers';
|
|
3
|
-
import { getCurrentUserTenantsFunction } 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 => {
|
|
@@ -51,6 +51,13 @@ export default ((store, api, sharedActions) => {
|
|
|
51
51
|
}) : actions.setAuthState({
|
|
52
52
|
isLoading: false
|
|
53
53
|
});
|
|
54
|
+
if (await actions.__redirectToSsoIfRequired(e)) {
|
|
55
|
+
// the tab comes back from SSO in the target tenant, not the one the failed switch restored
|
|
56
|
+
if (ContextHolder.for(store.root.appName).isSessionPerTenantEnabled()) {
|
|
57
|
+
setTabTenantForSsoSwitch(tenantId);
|
|
58
|
+
}
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
54
61
|
callback == null ? void 0 : callback(false, e);
|
|
55
62
|
}
|
|
56
63
|
};
|
package/auth/helpers.d.ts
CHANGED
|
@@ -8,5 +8,20 @@ export declare const isMfaRequired: (user: ILoginResponse, appName: string) => b
|
|
|
8
8
|
export declare const isResetPasswordRequired: (user: ILoginResponse, appName: string) => boolean;
|
|
9
9
|
export declare const mapMetaDataObjectToActions: (obj: any, path?: string[]) => CommitChangeDto[];
|
|
10
10
|
export declare const getUri: (urlStrategy: FronteggState["root"]["urlStrategy"]) => string;
|
|
11
|
+
/**
|
|
12
|
+
* On the OIDC callback the page still has to finish the OIDC login with its code: a refresh refused before that is not
|
|
13
|
+
* a reason to go back to the IdP, which would send the user straight back here, forever.
|
|
14
|
+
*/
|
|
15
|
+
export declare const isOnOidcCallbackRoute: (store: FronteggState) => boolean;
|
|
16
|
+
/**
|
|
17
|
+
* After a switch refused for SSO the tab points at the SSO tenant. Its SSO login comes back on a callback route and
|
|
18
|
+
* keeps it; any other page load means the user didn't finish that login, so the tab goes back to its tenant.
|
|
19
|
+
*/
|
|
20
|
+
export declare const restoreTabTenantUnlessSsoCallback: (store: FronteggState) => void;
|
|
11
21
|
export declare const prepareIdentifiersForSignup: (payload: ISignUpUser) => ISignUpUser;
|
|
22
|
+
/**
|
|
23
|
+
* Whether the current route is an auth page (login, tenant chooser, social / magic link / SSO callback), not one to come
|
|
24
|
+
* back to. Unlike isAuthRoute: after-login / sign-up destinations don't count, and endsWith lets a basename prefix it.
|
|
25
|
+
*/
|
|
26
|
+
export declare const isOnAuthPageRoute: (urlStrategy: FronteggState["root"]["urlStrategy"], routes?: Partial<AuthPageRoutes>) => boolean;
|
|
12
27
|
export declare const isAuthRoute: (path: string, routes?: Partial<AuthPageRoutes>) => boolean;
|