Skip to content

Commit

Permalink
HAI-1311 Redirect user to original route after login (#372)
Browse files Browse the repository at this point in the history
If user tries to access page that requires login, that path
is saved to session storage and user is redirected to login and
after successful login user is redirected to the path
in session storage.
  • Loading branch information
markohaarni authored Sep 20, 2023
1 parent 028efd6 commit 780dd03
Show file tree
Hide file tree
Showing 9 changed files with 104 additions and 16 deletions.
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@
"i18next-browser-languagedetector": "^7.0.1",
"immer": "8.0.2",
"jest-fetch-mock": "^3.0.3",
"jest-localstorage-mock": "^2.4.6",
"local-web-server": "^4.2.1",
"lodash": "^4.17.21",
"msw": "^0.49.0",
Expand Down
7 changes: 6 additions & 1 deletion src/common/routes/AppRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import Login from '../../domain/auth/components/Login';
import OidcCallback from '../../domain/auth/components/OidcCallback';
import { LOGIN_CALLBACK_PATH, LOGIN_PATH } from '../../domain/auth/constants';
import LocaleRoutes from './LocaleRoutes';
import { REDIRECT_PATH_KEY } from './constants';

const AppRoutes: React.FC<React.PropsWithChildren<unknown>> = () => {
const currentLocale = useLocale();
const redirectPath = sessionStorage.getItem(REDIRECT_PATH_KEY);

return (
<Routes>
Expand All @@ -17,7 +19,10 @@ const AppRoutes: React.FC<React.PropsWithChildren<unknown>> = () => {
{Object.values(LANGUAGES).map((locale) => (
<Route path={`/${locale}/*`} element={<LocaleRoutes />} key={locale} />
))}
<Route path="*" element={<Navigate to={`/${currentLocale}`} />} />
<Route
path="*"
element={<Navigate to={redirectPath ? redirectPath : `/${currentLocale}`} />}
/>
</Routes>
);
};
Expand Down
21 changes: 17 additions & 4 deletions src/common/routes/PrivateRoute.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,29 @@
import React from 'react';
import { Navigate } from 'react-router-dom';
import React, { useEffect } from 'react';
import { Navigate, useLocation } from 'react-router-dom';
import useUser from '../../domain/auth/useUser';
import { REDIRECT_PATH_KEY } from './constants';

type Props = {
element: JSX.Element;
};

const PrivateRoute: React.FC<React.PropsWithChildren<Props>> = ({ element }) => {
const { data: user } = useUser();
const isAuthenticated = Boolean(user?.profile);
const location = useLocation();

if (!user?.profile) {
return <Navigate to="/" />;
useEffect(() => {
if (isAuthenticated) {
sessionStorage.removeItem(REDIRECT_PATH_KEY);
}
}, [isAuthenticated]);

if (!isAuthenticated) {
// If user tries to access private route when not signed in,
// save URL path to session storage and navigate to login,
// so that user can be redirected to that route after login
sessionStorage.setItem(REDIRECT_PATH_KEY, location.pathname);
return <Navigate to="/login" />;
}

return element;
Expand Down
79 changes: 79 additions & 0 deletions src/common/routes/Routes.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import React from 'react';
import { render, waitFor } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import { I18nextProvider } from 'react-i18next';
import { QueryClient, QueryClientProvider } from 'react-query';
import { InitialEntry } from '@remix-run/router';
import { User } from 'oidc-client';
import { Provider } from 'react-redux';
import AppRoutes from './AppRoutes';
import { FeatureFlagsProvider } from '../components/featureFlags/FeatureFlagsContext';
import { GlobalNotificationProvider } from '../components/globalNotification/GlobalNotificationContext';
import authService from '../../domain/auth/authService';
import { store } from '../redux/store';
import i18n from '../../locales/i18n';
import { REDIRECT_PATH_KEY } from './constants';

afterEach(() => {
sessionStorage.clear();
});

const path = '/fi/johtoselvityshakemus';

const mockUser: Partial<User> = {
id_token: 'fffff-aaaaaa-11111',
access_token: '.BnutWVN1x7RSAP5bU2a-tXdVPuof_9pBNd_Ozw',
profile: {
iss: '',
sub: '',
aud: '',
exp: 0,
iat: 0,
},
};

function getWrapper(routerInitialEntries?: InitialEntry[]) {
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retryDelay: 0,
},
},
});

return render(
<MemoryRouter initialEntries={routerInitialEntries}>
<Provider store={store}>
<QueryClientProvider client={queryClient}>
<I18nextProvider i18n={i18n}>
<FeatureFlagsProvider>
<GlobalNotificationProvider>
<AppRoutes />
</GlobalNotificationProvider>
</FeatureFlagsProvider>
</I18nextProvider>
</QueryClientProvider>
</Provider>
</MemoryRouter>,
);
}

test('Should save path to session storage and navigate to login if trying to navigate to private route', async () => {
const login = jest.spyOn(authService, 'login').mockResolvedValue();

getWrapper([path]);

await waitFor(() => expect(login).toHaveBeenCalled());
expect(window.sessionStorage.getItem(REDIRECT_PATH_KEY)).toBe(path);
});

test('Should redirect to path stored in session storage after login if one exists', async () => {
sessionStorage.setItem(REDIRECT_PATH_KEY, path);
jest.spyOn(authService.userManager, 'getUser').mockResolvedValue(mockUser as User);

getWrapper();

await waitFor(() =>
expect(window.document.title).toBe('Haitaton - Luo uusi johtoselvityshakemus'),
);
});
1 change: 1 addition & 0 deletions src/common/routes/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const REDIRECT_PATH_KEY = 'redirect-path';
3 changes: 0 additions & 3 deletions src/domain/auth/authService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@ describe('authService', () => {

afterEach(() => {
localStorage.clear();
// eslint-disable-next-line
// @ts-ignore
localStorage.setItem.mockClear();
jest.restoreAllMocks();
});

Expand Down
2 changes: 1 addition & 1 deletion src/domain/hanke/accessRights/AccessRightsView.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { server } from '../../mocks/test-server';
import usersData from '../../mocks/data/users-data.json';
import { SignedInUser } from '../hankeUsers/hankeUser';

jest.setTimeout(30000);
jest.setTimeout(40000);

afterEach(cleanup);

Expand Down
1 change: 0 additions & 1 deletion src/setupTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';
import '@testing-library/jest-dom/extend-expect';
import 'jest-localstorage-mock';
import { GlobalWithFetchMock } from 'jest-fetch-mock';
import 'jest-canvas-mock';
import { server } from './domain/mocks/test-server';
Expand Down
5 changes: 0 additions & 5 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -10787,11 +10787,6 @@ jest-leak-detector@^27.5.1:
jest-get-type "^27.5.1"
pretty-format "^27.5.1"

jest-localstorage-mock@^2.4.6:
version "2.4.6"
resolved "https://registry.yarnpkg.com/jest-localstorage-mock/-/jest-localstorage-mock-2.4.6.tgz#ebb9481943bf52e0f8c864ae4858eb791d68149d"
integrity sha512-8n6tuqscEShpvC7vkq3BPabOGGszD1cdLAKTtTtCRqH2bWJIVfpV4aIhrDJstV7xLOjo56wSVZkoMbT7dWLIVg==

jest-matcher-utils@^27.5.1:
version "27.5.1"
resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz#9c0cdbda8245bc22d2331729d1091308b40cf8ab"
Expand Down

0 comments on commit 780dd03

Please sign in to comment.