mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2026-01-09 05:56:17 +00:00
minor: add isApplicationLoading state in ApplicationStore (#16242)
* minor: add isApplicationLoading state and ApplicationStore * minor: remove setIsUserAuthenticated(true) from the oidcAuthenticator Callback onSuccess * chore: add comments * chore: update isSigningIn to isSigningUp * update comment * chore: add switch in app router * chore: improve setIsAuthenticated usage * fix test * add application loading in basic authenticator
This commit is contained in:
parent
386e80ca1b
commit
80ccb4e8a4
@ -13,11 +13,12 @@
|
||||
|
||||
import { isNil } from 'lodash';
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { Switch, useLocation } from 'react-router-dom';
|
||||
import { useAnalytics } from 'use-analytics';
|
||||
import { CustomEventTypes } from '../../generated/analytics/webAnalyticEventData';
|
||||
import { useApplicationStore } from '../../hooks/useApplicationStore';
|
||||
import AppContainer from '../AppContainer/AppContainer';
|
||||
import Loader from '../common/Loader/Loader';
|
||||
import { UnAuthenticatedAppRouter } from './UnAuthenticatedAppRouter';
|
||||
|
||||
const AppRouter = () => {
|
||||
@ -26,7 +27,7 @@ const AppRouter = () => {
|
||||
// web analytics instance
|
||||
const analytics = useAnalytics();
|
||||
|
||||
const { isAuthenticated } = useApplicationStore();
|
||||
const { isAuthenticated, isApplicationLoading } = useApplicationStore();
|
||||
|
||||
useEffect(() => {
|
||||
const { pathname } = location;
|
||||
@ -64,7 +65,22 @@ const AppRouter = () => {
|
||||
return () => targetNode.removeEventListener('click', handleClickEvent);
|
||||
}, [handleClickEvent]);
|
||||
|
||||
return isAuthenticated ? <AppContainer /> : <UnAuthenticatedAppRouter />;
|
||||
/**
|
||||
* isApplicationLoading is true when the application is loading in AuthProvider
|
||||
* and is false when the application is loaded.
|
||||
* If the application is loading, show the loader.
|
||||
* If the user is authenticated, show the AppContainer.
|
||||
* If the user is not authenticated, show the UnAuthenticatedAppRouter.
|
||||
* */
|
||||
if (isApplicationLoading) {
|
||||
return <Loader fullScreen />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
{isAuthenticated ? <AppContainer /> : <UnAuthenticatedAppRouter />}
|
||||
</Switch>
|
||||
);
|
||||
};
|
||||
|
||||
export default AppRouter;
|
||||
|
||||
@ -42,7 +42,7 @@ const BasicSignupPage = withSuspenseFallback(
|
||||
);
|
||||
|
||||
export const UnAuthenticatedAppRouter = () => {
|
||||
const { authConfig, isSigningIn } = useApplicationStore();
|
||||
const { authConfig, isSigningUp } = useApplicationStore();
|
||||
|
||||
const isBasicAuthProvider =
|
||||
authConfig &&
|
||||
@ -78,7 +78,7 @@ export const UnAuthenticatedAppRouter = () => {
|
||||
component={SamlCallback}
|
||||
path={[ROUTES.SAML_CALLBACK, ROUTES.AUTH_CALLBACK]}
|
||||
/>
|
||||
{!isSigningIn && (
|
||||
{!isSigningUp && (
|
||||
<Route exact path={ROUTES.HOME}>
|
||||
<Redirect to={ROUTES.SIGNIN} />
|
||||
</Route>
|
||||
|
||||
@ -25,6 +25,7 @@ import {
|
||||
} from '../../../rest/auth-API';
|
||||
|
||||
import { useApplicationStore } from '../../../hooks/useApplicationStore';
|
||||
import Loader from '../../common/Loader/Loader';
|
||||
import { useBasicAuth } from '../AuthProviders/BasicAuthProvider';
|
||||
|
||||
interface BasicAuthenticatorInterface {
|
||||
@ -41,6 +42,7 @@ const BasicAuthenticator = forwardRef(
|
||||
getRefreshToken,
|
||||
setRefreshToken,
|
||||
setOidcToken,
|
||||
isApplicationLoading,
|
||||
} = useApplicationStore();
|
||||
|
||||
const handleSilentSignIn = async (): Promise<AccessTokenResponse> => {
|
||||
@ -73,6 +75,17 @@ const BasicAuthenticator = forwardRef(
|
||||
},
|
||||
}));
|
||||
|
||||
/**
|
||||
* isApplicationLoading is true when the application is loading in AuthProvider
|
||||
* and is false when the application is loaded.
|
||||
* If the application is loading, show the loader.
|
||||
* If the user is authenticated, show the AppContainer.
|
||||
* If the user is not authenticated, show the UnAuthenticatedAppRouter.
|
||||
* */
|
||||
if (isApplicationLoading) {
|
||||
return <Loader fullScreen />;
|
||||
}
|
||||
|
||||
return <Fragment>{children}</Fragment>;
|
||||
}
|
||||
);
|
||||
|
||||
@ -25,7 +25,7 @@ export const GenericAuthenticator = forwardRef(
|
||||
({ children }: { children: ReactNode }, ref) => {
|
||||
const {
|
||||
setIsAuthenticated,
|
||||
setIsSigningIn,
|
||||
setIsSigningUp,
|
||||
removeOidcToken,
|
||||
setOidcToken,
|
||||
} = useApplicationStore();
|
||||
@ -33,7 +33,7 @@ export const GenericAuthenticator = forwardRef(
|
||||
|
||||
const handleLogin = () => {
|
||||
setIsAuthenticated(false);
|
||||
setIsSigningIn(true);
|
||||
setIsSigningUp(true);
|
||||
window.location.assign('api/v1/auth/login');
|
||||
};
|
||||
|
||||
|
||||
@ -65,13 +65,13 @@ const OidcAuthenticator = forwardRef<AuthenticatorRef, Props>(
|
||||
) => {
|
||||
const {
|
||||
isAuthenticated,
|
||||
setIsAuthenticated,
|
||||
isSigningIn,
|
||||
setIsSigningIn,
|
||||
isSigningUp,
|
||||
setIsSigningUp,
|
||||
updateAxiosInterceptors,
|
||||
currentUser,
|
||||
newUser,
|
||||
setOidcToken,
|
||||
isApplicationLoading,
|
||||
} = useApplicationStore();
|
||||
const history = useHistory();
|
||||
const userManager = useMemo(
|
||||
@ -80,7 +80,7 @@ const OidcAuthenticator = forwardRef<AuthenticatorRef, Props>(
|
||||
);
|
||||
|
||||
const login = () => {
|
||||
setIsSigningIn(true);
|
||||
setIsSigningUp(true);
|
||||
};
|
||||
|
||||
const logout = () => {
|
||||
@ -113,16 +113,23 @@ const OidcAuthenticator = forwardRef<AuthenticatorRef, Props>(
|
||||
return (
|
||||
<>
|
||||
<Switch>
|
||||
{/* render sign in page if user is not authenticated and not signing up
|
||||
* else redirect to my data page as user is authenticated and not signing up
|
||||
*/}
|
||||
<Route exact path={ROUTES.HOME}>
|
||||
{!isAuthenticated && !isSigningIn ? (
|
||||
{!isAuthenticated && !isSigningUp ? (
|
||||
<Redirect to={ROUTES.SIGNIN} />
|
||||
) : (
|
||||
<Redirect to={ROUTES.MY_DATA} />
|
||||
)}
|
||||
</Route>
|
||||
{!isSigningIn ? (
|
||||
|
||||
{/* render the sign in route only if user is not signing up */}
|
||||
{!isSigningUp ? (
|
||||
<Route exact component={SignInPage} path={ROUTES.SIGNIN} />
|
||||
) : null}
|
||||
|
||||
{/* callback route to handle the auth flow after user has successfully provided their consent */}
|
||||
<Route
|
||||
path={ROUTES.CALLBACK}
|
||||
render={() => (
|
||||
@ -135,7 +142,6 @@ const OidcAuthenticator = forwardRef<AuthenticatorRef, Props>(
|
||||
}}
|
||||
onSuccess={(user) => {
|
||||
setOidcToken(user.id_token);
|
||||
setIsAuthenticated(true);
|
||||
onLoginSuccess(user as OidcUser);
|
||||
}}
|
||||
/>
|
||||
@ -143,6 +149,7 @@ const OidcAuthenticator = forwardRef<AuthenticatorRef, Props>(
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* silent callback route to handle the silent auth flow */}
|
||||
<Route
|
||||
path={ROUTES.SILENT_CALLBACK}
|
||||
render={() => (
|
||||
@ -163,15 +170,21 @@ const OidcAuthenticator = forwardRef<AuthenticatorRef, Props>(
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
|
||||
{/* render the children only if user is authenticated */}
|
||||
{isAuthenticated ? (
|
||||
<Fragment>{children}</Fragment>
|
||||
) : !isSigningIn && isEmpty(currentUser) && isEmpty(newUser) ? (
|
||||
) : // render the sign in page if user is not authenticated and not signing up
|
||||
!isSigningUp && isEmpty(currentUser) && isEmpty(newUser) ? (
|
||||
<Redirect to={ROUTES.SIGNIN} />
|
||||
) : (
|
||||
// render the authenticator component to handle the auth flow while user is signing in
|
||||
<AppWithAuth />
|
||||
)}
|
||||
</Switch>
|
||||
{isSigningIn && <Loader fullScreen />}
|
||||
|
||||
{/* show loader when application is loading and user is signing up*/}
|
||||
{isApplicationLoading && isSigningUp && <Loader fullScreen />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -42,7 +42,6 @@ Object.defineProperty(window, 'localStorage', {
|
||||
});
|
||||
|
||||
const mockUseAuth0 = useAuth0 as jest.Mock;
|
||||
const mockSetIsAuthenticated = jest.fn();
|
||||
const mockHandleSuccessfulLogin = jest.fn();
|
||||
|
||||
jest.mock('@auth0/auth0-react', () => ({
|
||||
@ -53,7 +52,6 @@ jest.mock('../../../../hooks/useApplicationStore', () => {
|
||||
return {
|
||||
useApplicationStore: jest.fn(() => ({
|
||||
authConfig: {},
|
||||
setIsAuthenticated: mockSetIsAuthenticated,
|
||||
handleSuccessfulLogin: mockHandleSuccessfulLogin,
|
||||
setOidcToken: jest.fn(),
|
||||
})),
|
||||
@ -108,8 +106,6 @@ describe('Test Auth0Callback component', () => {
|
||||
// eslint-disable-next-line no-undef
|
||||
await new Promise(process.nextTick);
|
||||
|
||||
expect(mockSetIsAuthenticated).toHaveBeenCalledTimes(1);
|
||||
expect(mockSetIsAuthenticated).toHaveBeenCalledWith(true);
|
||||
expect(mockHandleSuccessfulLogin).toHaveBeenCalledTimes(1);
|
||||
expect(mockHandleSuccessfulLogin).toHaveBeenCalledWith({
|
||||
id_token: 'raw_id_token',
|
||||
|
||||
@ -20,13 +20,11 @@ import { OidcUser } from '../../AuthProviders/AuthProvider.interface';
|
||||
|
||||
const Auth0Callback: VFC = () => {
|
||||
const { isAuthenticated, user, getIdTokenClaims, error } = useAuth0();
|
||||
const { setIsAuthenticated, handleSuccessfulLogin, setOidcToken } =
|
||||
useApplicationStore();
|
||||
const { handleSuccessfulLogin, setOidcToken } = useApplicationStore();
|
||||
if (isAuthenticated) {
|
||||
getIdTokenClaims()
|
||||
.then((token) => {
|
||||
setOidcToken(token?.__raw || '');
|
||||
setIsAuthenticated(true);
|
||||
const oidcUser: OidcUser = {
|
||||
id_token: token?.__raw || '',
|
||||
scope: '',
|
||||
|
||||
@ -52,8 +52,8 @@ export interface IAuthContext {
|
||||
setIsAuthenticated: (authenticated: boolean) => void;
|
||||
authConfig?: AuthenticationConfiguration;
|
||||
authorizerConfig?: AuthorizerConfiguration;
|
||||
isSigningIn: boolean;
|
||||
setIsSigningIn: (authenticated: boolean) => void;
|
||||
isSigningUp: boolean;
|
||||
setIsSigningUp: (isSigningUp: boolean) => void;
|
||||
onLoginHandler: () => void;
|
||||
onLogoutHandler: () => void;
|
||||
currentUser?: User;
|
||||
|
||||
@ -109,16 +109,18 @@ export const AuthProvider = ({
|
||||
setHelperFunctionsRef,
|
||||
setCurrentUser,
|
||||
updateNewUser: setNewUserProfile,
|
||||
setIsAuthenticated: setIsUserAuthenticated,
|
||||
setIsAuthenticated,
|
||||
authConfig,
|
||||
setAuthConfig,
|
||||
setAuthorizerConfig,
|
||||
setIsSigningIn,
|
||||
setIsSigningUp,
|
||||
setJwtPrincipalClaims,
|
||||
removeRefreshToken,
|
||||
removeOidcToken,
|
||||
getOidcToken,
|
||||
getRefreshToken,
|
||||
isApplicationLoading,
|
||||
setApplicationLoading,
|
||||
} = useApplicationStore();
|
||||
const { activeDomain } = useDomainStore();
|
||||
|
||||
@ -127,7 +129,6 @@ export const AuthProvider = ({
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [timeoutId, setTimeoutId] = useState<number>();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [msalInstance, setMsalInstance] = useState<IPublicClientApplication>();
|
||||
|
||||
const authenticatorRef = useRef<AuthenticatorRef>(null);
|
||||
@ -140,7 +141,7 @@ export const AuthProvider = ({
|
||||
const clientType = authConfig?.clientType ?? ClientType.Public;
|
||||
|
||||
const onLoginHandler = () => {
|
||||
setLoading(true);
|
||||
setApplicationLoading(true);
|
||||
|
||||
authenticatorRef.current?.invokeLogin();
|
||||
|
||||
@ -151,7 +152,7 @@ export const AuthProvider = ({
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
authenticatorRef.current?.invokeLogout();
|
||||
setIsUserAuthenticated(false);
|
||||
setIsAuthenticated(false);
|
||||
|
||||
// reset the user details on logout
|
||||
setCurrentUser({} as User);
|
||||
@ -162,7 +163,7 @@ export const AuthProvider = ({
|
||||
// remove the refresh token on logout
|
||||
removeRefreshToken();
|
||||
|
||||
setLoading(false);
|
||||
setApplicationLoading(false);
|
||||
}, [timeoutId]);
|
||||
|
||||
const onRenewIdTokenHandler = () => {
|
||||
@ -191,8 +192,8 @@ export const AuthProvider = ({
|
||||
const resetUserDetails = (forceLogout = false) => {
|
||||
setCurrentUser({} as User);
|
||||
removeOidcToken();
|
||||
setIsUserAuthenticated(false);
|
||||
setLoading(false);
|
||||
setIsAuthenticated(false);
|
||||
setApplicationLoading(false);
|
||||
clearTimeout(timeoutId);
|
||||
if (forceLogout) {
|
||||
onLogoutHandler();
|
||||
@ -203,12 +204,12 @@ export const AuthProvider = ({
|
||||
};
|
||||
|
||||
const getLoggedInUserDetails = async () => {
|
||||
setLoading(true);
|
||||
setApplicationLoading(true);
|
||||
try {
|
||||
const res = await getLoggedInUser({ fields: userAPIQueryFields });
|
||||
if (res) {
|
||||
setCurrentUser(res);
|
||||
setIsUserAuthenticated(true);
|
||||
setIsAuthenticated(true);
|
||||
} else {
|
||||
resetUserDetails();
|
||||
}
|
||||
@ -224,7 +225,7 @@ export const AuthProvider = ({
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
setApplicationLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
@ -357,15 +358,15 @@ export const AuthProvider = ({
|
||||
}, [timeoutId]);
|
||||
|
||||
const handleFailedLogin = () => {
|
||||
setIsSigningIn(false);
|
||||
setIsUserAuthenticated(false);
|
||||
setLoading(false);
|
||||
setIsSigningUp(false);
|
||||
setIsAuthenticated(false);
|
||||
setApplicationLoading(false);
|
||||
history.push(ROUTES.SIGNIN);
|
||||
};
|
||||
|
||||
const handleSuccessfulLogin = async (user: OidcUser) => {
|
||||
setLoading(true);
|
||||
setIsUserAuthenticated(true);
|
||||
setApplicationLoading(true);
|
||||
setIsAuthenticated(true);
|
||||
const fields =
|
||||
authConfig?.provider === AuthProviderEnum.Basic
|
||||
? userAPIQueryFields + ',' + isEmailVerifyField
|
||||
@ -389,7 +390,7 @@ export const AuthProvider = ({
|
||||
if (err && err.response && err.response.status === 404) {
|
||||
setNewUserProfile(user.profile);
|
||||
setCurrentUser({} as User);
|
||||
setIsSigningIn(true);
|
||||
setIsSigningUp(true);
|
||||
history.push(ROUTES.SIGNUP);
|
||||
} else {
|
||||
// eslint-disable-next-line no-console
|
||||
@ -397,7 +398,7 @@ export const AuthProvider = ({
|
||||
history.push(ROUTES.SIGNIN);
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
setApplicationLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
@ -548,7 +549,7 @@ export const AuthProvider = ({
|
||||
updateAuthInstance(configJson);
|
||||
if (!getOidcToken()) {
|
||||
handleStoreProtectedRedirectPath();
|
||||
setLoading(false);
|
||||
setApplicationLoading(false);
|
||||
} else {
|
||||
if (location.pathname !== ROUTES.AUTH_CALLBACK) {
|
||||
getLoggedInUserDetails();
|
||||
@ -556,7 +557,7 @@ export const AuthProvider = ({
|
||||
}
|
||||
} else {
|
||||
// provider is either null or not supported
|
||||
setLoading(false);
|
||||
setApplicationLoading(false);
|
||||
showErrorToast(
|
||||
t('message.configured-sso-provider-is-not-supported', {
|
||||
provider: authConfig?.provider,
|
||||
@ -564,11 +565,11 @@ export const AuthProvider = ({
|
||||
);
|
||||
}
|
||||
} else {
|
||||
setLoading(false);
|
||||
setApplicationLoading(false);
|
||||
showErrorToast(t('message.auth-configuration-missing'));
|
||||
}
|
||||
} catch (error) {
|
||||
setLoading(false);
|
||||
setApplicationLoading(false);
|
||||
showErrorToast(
|
||||
error as AxiosError,
|
||||
t('server.entity-fetch-error', {
|
||||
@ -580,7 +581,11 @@ export const AuthProvider = ({
|
||||
|
||||
const getProtectedApp = () => {
|
||||
// Show loader if application in loading state
|
||||
const childElement = loading ? <Loader fullScreen /> : children;
|
||||
const childElement = isApplicationLoading ? (
|
||||
<Loader fullScreen />
|
||||
) : (
|
||||
children
|
||||
);
|
||||
|
||||
if (clientType === ClientType.Confidential) {
|
||||
return (
|
||||
@ -691,11 +696,11 @@ export const AuthProvider = ({
|
||||
return cleanup;
|
||||
}, []);
|
||||
|
||||
const isLoading =
|
||||
const isConfigLoading =
|
||||
!authConfig ||
|
||||
(authConfig.provider === AuthProviderEnum.Azure && !msalInstance);
|
||||
|
||||
return <>{isLoading ? <Loader fullScreen /> : getProtectedApp()}</>;
|
||||
return <>{isConfigLoading ? <Loader fullScreen /> : getProtectedApp()}</>;
|
||||
};
|
||||
|
||||
export default AuthProvider;
|
||||
|
||||
@ -31,8 +31,7 @@ export const OktaAuthProvider: FunctionComponent<Props> = ({
|
||||
children,
|
||||
onLoginSuccess,
|
||||
}: Props) => {
|
||||
const { authConfig, setIsAuthenticated, setOidcToken } =
|
||||
useApplicationStore();
|
||||
const { authConfig, setOidcToken } = useApplicationStore();
|
||||
const { clientId, issuer, redirectUri, scopes, pkce } =
|
||||
authConfig as OktaAuthOptions;
|
||||
|
||||
@ -63,7 +62,6 @@ export const OktaAuthProvider: FunctionComponent<Props> = ({
|
||||
_oktaAuth
|
||||
.getUser()
|
||||
.then((info) => {
|
||||
setIsAuthenticated(true);
|
||||
const user = {
|
||||
id_token: idToken,
|
||||
scope: scopes,
|
||||
|
||||
@ -31,6 +31,7 @@ export const OM_SESSION_KEY = 'om-session';
|
||||
export const useApplicationStore = create<ApplicationStore>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
isApplicationLoading: false,
|
||||
theme: getThemeConfig(),
|
||||
applicationConfig: {
|
||||
customTheme: getThemeConfig(),
|
||||
@ -40,7 +41,7 @@ export const useApplicationStore = create<ApplicationStore>()(
|
||||
isAuthenticated: Boolean(getOidcToken()),
|
||||
authConfig: undefined,
|
||||
authorizerConfig: undefined,
|
||||
isSigningIn: false,
|
||||
isSigningUp: false,
|
||||
jwtPrincipalClaims: [],
|
||||
userProfilePics: {},
|
||||
cachedEntityData: {},
|
||||
@ -77,8 +78,12 @@ export const useApplicationStore = create<ApplicationStore>()(
|
||||
setIsAuthenticated: (authenticated: boolean) => {
|
||||
set({ isAuthenticated: authenticated });
|
||||
},
|
||||
setIsSigningIn: (signingIn: boolean) => {
|
||||
set({ isSigningIn: signingIn });
|
||||
setIsSigningUp: (signingUp: boolean) => {
|
||||
set({ isSigningUp: signingUp });
|
||||
},
|
||||
|
||||
setApplicationLoading: (loading: boolean) => {
|
||||
set({ isApplicationLoading: loading });
|
||||
},
|
||||
|
||||
onLoginHandler: () => {
|
||||
|
||||
@ -42,6 +42,8 @@ export interface ApplicationStore
|
||||
extends IAuthContext,
|
||||
LogoConfiguration,
|
||||
LoginConfiguration {
|
||||
isApplicationLoading: boolean;
|
||||
setApplicationLoading: (loading: boolean) => void;
|
||||
userProfilePics: Record<string, User>;
|
||||
cachedEntityData: Record<string, EntityUnion>;
|
||||
selectedPersona: EntityReference;
|
||||
|
||||
@ -33,7 +33,7 @@ jest.mock('react-router-dom', () => ({
|
||||
|
||||
jest.mock('../../hooks/useApplicationStore', () => ({
|
||||
useApplicationStore: jest.fn(() => ({
|
||||
setIsSigningIn: jest.fn(),
|
||||
setIsSigningUp: jest.fn(),
|
||||
newUser: {
|
||||
name: '',
|
||||
email: '',
|
||||
|
||||
@ -41,7 +41,7 @@ const SignUp = () => {
|
||||
const { t } = useTranslation();
|
||||
const history = useHistory();
|
||||
const {
|
||||
setIsSigningIn,
|
||||
setIsSigningUp,
|
||||
jwtPrincipalClaims = [],
|
||||
authorizerConfig,
|
||||
updateCurrentUser,
|
||||
@ -67,7 +67,7 @@ const SignUp = () => {
|
||||
if (urlPathname) {
|
||||
setUrlPathnameExpiryAfterRoute(urlPathname);
|
||||
}
|
||||
setIsSigningIn(false);
|
||||
setIsSigningUp(false);
|
||||
history.push(ROUTES.HOME);
|
||||
} catch (error) {
|
||||
showErrorToast(
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user