mirror of
https://github.com/ansible/awx.git
synced 2026-05-24 00:57:48 -02:30
Fix login redirect (#5386)
Allows the user to visit login page when the login redirect url is set. Also, redirects to login page once logging out and there is session from a SAML available. See: https://github.com/ansible/awx/issues/11012
This commit is contained in:
@@ -98,8 +98,13 @@ const AuthorizedRoutes = ({ routeConfig }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const ProtectedRoute = ({ children, ...rest }) => {
|
export function ProtectedRoute({ children, ...rest }) {
|
||||||
const { authRedirectTo, setAuthRedirectTo } = useSession();
|
const {
|
||||||
|
authRedirectTo,
|
||||||
|
setAuthRedirectTo,
|
||||||
|
loginRedirectOverride,
|
||||||
|
isUserBeingLoggedOut,
|
||||||
|
} = useSession();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -120,8 +125,16 @@ const ProtectedRoute = ({ children, ...rest }) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
loginRedirectOverride &&
|
||||||
|
!window.location.href.includes('/login') &&
|
||||||
|
!isUserBeingLoggedOut
|
||||||
|
) {
|
||||||
|
window.location.replace(loginRedirectOverride);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return <Redirect to="/login" />;
|
return <Redirect to="/login" />;
|
||||||
};
|
}
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { act } from 'react-dom/test-utils';
|
|||||||
import { RootAPI } from 'api';
|
import { RootAPI } from 'api';
|
||||||
import * as SessionContext from 'contexts/Session';
|
import * as SessionContext from 'contexts/Session';
|
||||||
import { mountWithContexts } from '../testUtils/enzymeHelpers';
|
import { mountWithContexts } from '../testUtils/enzymeHelpers';
|
||||||
import App from './App';
|
import App, { ProtectedRoute } from './App';
|
||||||
|
|
||||||
jest.mock('./api');
|
jest.mock('./api');
|
||||||
|
|
||||||
@@ -20,6 +20,8 @@ describe('<App />', () => {
|
|||||||
const contextValues = {
|
const contextValues = {
|
||||||
setAuthRedirectTo: jest.fn(),
|
setAuthRedirectTo: jest.fn(),
|
||||||
isSessionExpired: false,
|
isSessionExpired: false,
|
||||||
|
isUserBeingLoggedOut: false,
|
||||||
|
loginRedirectOverride: null,
|
||||||
};
|
};
|
||||||
jest
|
jest
|
||||||
.spyOn(SessionContext, 'useSession')
|
.spyOn(SessionContext, 'useSession')
|
||||||
@@ -32,4 +34,36 @@ describe('<App />', () => {
|
|||||||
expect(wrapper.length).toBe(1);
|
expect(wrapper.length).toBe(1);
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('redirect to login override', async () => {
|
||||||
|
const { location } = window;
|
||||||
|
delete window.location;
|
||||||
|
window.location = {
|
||||||
|
replace: jest.fn(),
|
||||||
|
href: '/',
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(window.location.replace).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
const contextValues = {
|
||||||
|
setAuthRedirectTo: jest.fn(),
|
||||||
|
isSessionExpired: false,
|
||||||
|
isUserBeingLoggedOut: false,
|
||||||
|
loginRedirectOverride: '/sso/test',
|
||||||
|
};
|
||||||
|
jest
|
||||||
|
.spyOn(SessionContext, 'useSession')
|
||||||
|
.mockImplementation(() => contextValues);
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
mountWithContexts(
|
||||||
|
<ProtectedRoute>
|
||||||
|
<div>foo</div>
|
||||||
|
</ProtectedRoute>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(window.location.replace).toHaveBeenCalled();
|
||||||
|
window.location = location;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,10 +5,11 @@ import React, {
|
|||||||
useRef,
|
useRef,
|
||||||
useCallback,
|
useCallback,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory, Redirect } from 'react-router-dom';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
import { RootAPI, MeAPI } from 'api';
|
import { RootAPI, MeAPI } from 'api';
|
||||||
import { isAuthenticated } from 'util/auth';
|
import { isAuthenticated } from 'util/auth';
|
||||||
|
import useRequest from 'hooks/useRequest';
|
||||||
import { SESSION_TIMEOUT_KEY } from '../constants';
|
import { SESSION_TIMEOUT_KEY } from '../constants';
|
||||||
|
|
||||||
// The maximum supported timeout for setTimeout(), in milliseconds,
|
// The maximum supported timeout for setTimeout(), in milliseconds,
|
||||||
@@ -72,8 +73,31 @@ function SessionProvider({ children }) {
|
|||||||
const [sessionTimeout, setSessionTimeout] = useStorage(SESSION_TIMEOUT_KEY);
|
const [sessionTimeout, setSessionTimeout] = useStorage(SESSION_TIMEOUT_KEY);
|
||||||
const [sessionCountdown, setSessionCountdown] = useState(0);
|
const [sessionCountdown, setSessionCountdown] = useState(0);
|
||||||
const [authRedirectTo, setAuthRedirectTo] = useState('/');
|
const [authRedirectTo, setAuthRedirectTo] = useState('/');
|
||||||
|
const [isUserBeingLoggedOut, setIsUserBeingLoggedOut] = useState(false);
|
||||||
|
|
||||||
|
const {
|
||||||
|
request: fetchLoginRedirectOverride,
|
||||||
|
result: { loginRedirectOverride },
|
||||||
|
isLoading,
|
||||||
|
} = useRequest(
|
||||||
|
useCallback(async () => {
|
||||||
|
const { data } = await RootAPI.read();
|
||||||
|
return {
|
||||||
|
loginRedirectOverride: data?.login_redirect_override,
|
||||||
|
};
|
||||||
|
}, []),
|
||||||
|
{
|
||||||
|
loginRedirectOverride: null,
|
||||||
|
isLoading: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchLoginRedirectOverride();
|
||||||
|
}, [fetchLoginRedirectOverride]);
|
||||||
|
|
||||||
const logout = useCallback(async () => {
|
const logout = useCallback(async () => {
|
||||||
|
setIsUserBeingLoggedOut(true);
|
||||||
if (!isSessionExpired.current) {
|
if (!isSessionExpired.current) {
|
||||||
setAuthRedirectTo('/logout');
|
setAuthRedirectTo('/logout');
|
||||||
}
|
}
|
||||||
@@ -82,14 +106,13 @@ function SessionProvider({ children }) {
|
|||||||
setSessionCountdown(0);
|
setSessionCountdown(0);
|
||||||
clearTimeout(sessionTimeoutId.current);
|
clearTimeout(sessionTimeoutId.current);
|
||||||
clearInterval(sessionIntervalId.current);
|
clearInterval(sessionIntervalId.current);
|
||||||
|
return <Redirect to="/login" />;
|
||||||
}, [setSessionTimeout, setSessionCountdown]);
|
}, [setSessionTimeout, setSessionCountdown]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isAuthenticated(document.cookie)) {
|
if (!isAuthenticated(document.cookie)) {
|
||||||
history.replace('/login');
|
|
||||||
return () => {};
|
return () => {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const calcRemaining = () => {
|
const calcRemaining = () => {
|
||||||
if (sessionTimeout) {
|
if (sessionTimeout) {
|
||||||
return Math.max(
|
return Math.max(
|
||||||
@@ -140,9 +163,15 @@ function SessionProvider({ children }) {
|
|||||||
clearInterval(sessionIntervalId.current);
|
clearInterval(sessionIntervalId.current);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SessionContext.Provider
|
<SessionContext.Provider
|
||||||
value={{
|
value={{
|
||||||
|
isUserBeingLoggedOut,
|
||||||
|
loginRedirectOverride,
|
||||||
authRedirectTo,
|
authRedirectTo,
|
||||||
handleSessionContinue,
|
handleSessionContinue,
|
||||||
isSessionExpired,
|
isSessionExpired,
|
||||||
|
|||||||
@@ -47,18 +47,12 @@ function AWXLogin({ alt, isAuthenticated }) {
|
|||||||
isLoading: isCustomLoginInfoLoading,
|
isLoading: isCustomLoginInfoLoading,
|
||||||
error: customLoginInfoError,
|
error: customLoginInfoError,
|
||||||
request: fetchCustomLoginInfo,
|
request: fetchCustomLoginInfo,
|
||||||
result: {
|
result: { brandName, logo, loginInfo, socialAuthOptions },
|
||||||
brandName,
|
|
||||||
logo,
|
|
||||||
loginInfo,
|
|
||||||
socialAuthOptions,
|
|
||||||
loginRedirectOverride,
|
|
||||||
},
|
|
||||||
} = useRequest(
|
} = useRequest(
|
||||||
useCallback(async () => {
|
useCallback(async () => {
|
||||||
const [
|
const [
|
||||||
{
|
{
|
||||||
data: { custom_logo, custom_login_info, login_redirect_override },
|
data: { custom_logo, custom_login_info },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
data: { BRAND_NAME },
|
data: { BRAND_NAME },
|
||||||
@@ -78,7 +72,6 @@ function AWXLogin({ alt, isAuthenticated }) {
|
|||||||
logo: logoSrc,
|
logo: logoSrc,
|
||||||
loginInfo: custom_login_info,
|
loginInfo: custom_login_info,
|
||||||
socialAuthOptions: authData,
|
socialAuthOptions: authData,
|
||||||
loginRedirectOverride: login_redirect_override,
|
|
||||||
};
|
};
|
||||||
}, []),
|
}, []),
|
||||||
{
|
{
|
||||||
@@ -118,10 +111,6 @@ function AWXLogin({ alt, isAuthenticated }) {
|
|||||||
if (isCustomLoginInfoLoading) {
|
if (isCustomLoginInfoLoading) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (!isAuthenticated(document.cookie) && loginRedirectOverride) {
|
|
||||||
window.location.replace(loginRedirectOverride);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (isAuthenticated(document.cookie)) {
|
if (isAuthenticated(document.cookie)) {
|
||||||
return <Redirect to={authRedirectTo || '/'} />;
|
return <Redirect to={authRedirectTo || '/'} />;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user