mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-11-03 20:19:31 +00:00
feat(ui): support /logout path to perform logout from api redirect (#20040)
This commit is contained in:
parent
9331a526ea
commit
30a8d64959
@ -349,7 +349,7 @@ public class AuthenticationCodeFlowHandler {
|
||||
if (session != null) {
|
||||
LOG.debug("Invalidating the session for logout");
|
||||
session.invalidate();
|
||||
httpServletResponse.sendRedirect(serverUrl);
|
||||
httpServletResponse.sendRedirect(serverUrl + "/logout");
|
||||
} else {
|
||||
LOG.error("No session store available for this web context");
|
||||
}
|
||||
|
||||
@ -26,6 +26,7 @@ import AppContainer from '../AppContainer/AppContainer';
|
||||
import Loader from '../common/Loader/Loader';
|
||||
import { UnAuthenticatedAppRouter } from './UnAuthenticatedAppRouter';
|
||||
|
||||
import { LogoutPage } from '../../pages/LogoutPage/LogoutPage';
|
||||
import SamlCallback from '../../pages/SamlCallback';
|
||||
|
||||
const AppRouter = () => {
|
||||
@ -86,6 +87,7 @@ const AppRouter = () => {
|
||||
return (
|
||||
<Switch>
|
||||
<Route exact component={PageNotFound} path={ROUTES.NOT_FOUND} />
|
||||
<Route exact component={LogoutPage} path={ROUTES.LOGOUT} />
|
||||
<Route
|
||||
exact
|
||||
component={AccessNotAllowedPage}
|
||||
|
||||
@ -20,6 +20,7 @@ import { useHistory } from 'react-router-dom';
|
||||
import { ROUTES } from '../../../constants/constants';
|
||||
import { useApplicationStore } from '../../../hooks/useApplicationStore';
|
||||
import { logoutUser, renewToken } from '../../../rest/LoginAPI';
|
||||
import TokenService from '../../../utils/Auth/TokenService/TokenServiceUtil';
|
||||
import { setOidcToken } from '../../../utils/LocalStorageUtils';
|
||||
|
||||
export const GenericAuthenticator = forwardRef(
|
||||
@ -37,6 +38,7 @@ export const GenericAuthenticator = forwardRef(
|
||||
const handleLogout = async () => {
|
||||
await logoutUser();
|
||||
|
||||
TokenService.getInstance().clearRefreshInProgress();
|
||||
history.push(ROUTES.SIGNIN);
|
||||
setOidcToken('');
|
||||
setIsAuthenticated(false);
|
||||
|
||||
@ -36,6 +36,7 @@ import { showErrorToast } from '../../../utils/ToastUtils';
|
||||
import { ROUTES } from '../../../constants/constants';
|
||||
import { useApplicationStore } from '../../../hooks/useApplicationStore';
|
||||
import { AccessTokenResponse, refreshSAMLToken } from '../../../rest/auth-API';
|
||||
import TokenService from '../../../utils/Auth/TokenService/TokenServiceUtil';
|
||||
import {
|
||||
getOidcToken,
|
||||
getRefreshToken,
|
||||
@ -76,24 +77,19 @@ const SamlAuthenticator = forwardRef<AuthenticatorRef, Props>(
|
||||
}
|
||||
};
|
||||
|
||||
const logout = () => {
|
||||
const logout = async () => {
|
||||
const token = getOidcToken();
|
||||
if (token) {
|
||||
postSamlLogout()
|
||||
.then(() => {
|
||||
setIsAuthenticated(false);
|
||||
try {
|
||||
onLogoutSuccess();
|
||||
} catch (err) {
|
||||
// TODO: Handle error on logout failure
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(err);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Error while logging out', err);
|
||||
});
|
||||
try {
|
||||
await postSamlLogout();
|
||||
setIsAuthenticated(false);
|
||||
onLogoutSuccess();
|
||||
TokenService.getInstance().clearRefreshInProgress();
|
||||
} catch (err) {
|
||||
// TODO: Handle error on logout failure
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(err);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -167,6 +167,7 @@ export const AuthProvider = ({
|
||||
resetWebAnalyticSession();
|
||||
};
|
||||
|
||||
// Handler to perform logout within application
|
||||
const onLogoutHandler = useCallback(() => {
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
@ -392,6 +393,7 @@ export const AuthProvider = ({
|
||||
]
|
||||
);
|
||||
|
||||
// Callback to cleanup session related info upon successful logout
|
||||
const handleSuccessfulLogout = () => {
|
||||
resetUserDetails();
|
||||
};
|
||||
|
||||
@ -39,6 +39,7 @@ import {
|
||||
import { resetWebAnalyticSession } from '../../../utils/WebAnalyticsUtils';
|
||||
|
||||
import { toLower } from 'lodash';
|
||||
import TokenService from '../../../utils/Auth/TokenService/TokenServiceUtil';
|
||||
import { extractDetailsFromToken } from '../../../utils/AuthProvider.util';
|
||||
import {
|
||||
getOidcToken,
|
||||
@ -181,6 +182,8 @@ const BasicAuthProvider = ({
|
||||
try {
|
||||
await logoutUser({ token, refreshToken });
|
||||
setOidcToken('');
|
||||
setRefreshToken('');
|
||||
TokenService.getInstance().clearRefreshInProgress();
|
||||
history.push(ROUTES.SIGNIN);
|
||||
} catch (error) {
|
||||
showErrorToast(error as AxiosError);
|
||||
|
||||
@ -131,6 +131,7 @@ export const ROUTES = {
|
||||
NOT_FOUND: '/404',
|
||||
FORBIDDEN: '/403',
|
||||
UNAUTHORISED: '/unauthorised',
|
||||
LOGOUT: '/logout',
|
||||
MY_DATA: '/my-data',
|
||||
TOUR: '/tour',
|
||||
REPORTS: '/reports',
|
||||
|
||||
@ -97,6 +97,9 @@ export const useApplicationStore = create<ApplicationStore>()((set, get) => ({
|
||||
onLoginHandler: () => {
|
||||
// This is a placeholder function that will be replaced by the actual function
|
||||
},
|
||||
/**
|
||||
* Handler to perform logout within application
|
||||
*/
|
||||
onLogoutHandler: () => {
|
||||
// This is a placeholder function that will be replaced by the actual function
|
||||
},
|
||||
@ -110,11 +113,6 @@ export const useApplicationStore = create<ApplicationStore>()((set, get) => ({
|
||||
updateAxiosInterceptors: () => {
|
||||
// This is a placeholder function that will be replaced by the actual function
|
||||
},
|
||||
trySilentSignIn: (forceLogout?: boolean) => {
|
||||
if (forceLogout) {
|
||||
// This is a placeholder function that will be replaced by the actual function
|
||||
}
|
||||
},
|
||||
updateCurrentUser: (user) => {
|
||||
set({ currentUser: user });
|
||||
},
|
||||
|
||||
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2025 Collate.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { render } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { useApplicationStore } from '../../hooks/useApplicationStore';
|
||||
import { LogoutPage } from './LogoutPage';
|
||||
|
||||
// Mock the useApplicationStore hook
|
||||
jest.mock('../../hooks/useApplicationStore', () => ({
|
||||
useApplicationStore: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('LogoutPage', () => {
|
||||
const mockOnLogoutHandler = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
// Reset mock before each test
|
||||
jest.clearAllMocks();
|
||||
|
||||
// Setup mock implementation
|
||||
(useApplicationStore as unknown as jest.Mock).mockReturnValue({
|
||||
onLogoutHandler: mockOnLogoutHandler,
|
||||
});
|
||||
});
|
||||
|
||||
it('should call onLogoutHandler on mount', () => {
|
||||
render(<LogoutPage />);
|
||||
|
||||
expect(mockOnLogoutHandler).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should render Loader component with fullScreen prop', () => {
|
||||
const { container } = render(<LogoutPage />);
|
||||
const loaderElement = container.firstChild;
|
||||
|
||||
expect(loaderElement).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2025 Collate.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React, { useEffect } from 'react';
|
||||
import Loader from '../../components/common/Loader/Loader';
|
||||
import { useApplicationStore } from '../../hooks/useApplicationStore';
|
||||
|
||||
export const LogoutPage = () => {
|
||||
const { onLogoutHandler } = useApplicationStore();
|
||||
|
||||
useEffect(() => {
|
||||
onLogoutHandler();
|
||||
}, []);
|
||||
|
||||
return <Loader fullScreen />;
|
||||
};
|
||||
@ -127,6 +127,7 @@ class TokenService {
|
||||
// Clear the refresh flag (used after refresh is complete)
|
||||
clearRefreshInProgress() {
|
||||
localStorage.removeItem(REFRESH_IN_PROGRESS_KEY);
|
||||
localStorage.removeItem(REFRESHED_KEY);
|
||||
}
|
||||
|
||||
// Check if a refresh is already in progress (used by other tabs)
|
||||
|
||||
@ -311,6 +311,7 @@ export const isProtectedRoute = (pathname: string) => {
|
||||
ROUTES.HOME,
|
||||
ROUTES.AUTH_CALLBACK,
|
||||
ROUTES.NOT_FOUND,
|
||||
ROUTES.LOGOUT,
|
||||
].indexOf(pathname) === -1
|
||||
);
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user