From 2aae30e3c290d5e4a5bfc5cbe69358b2d458b8b3 Mon Sep 17 00:00:00 2001 From: Albin Henriksson <albhe428@student.liu.se> Date: Wed, 28 Apr 2021 14:47:54 +0200 Subject: [PATCH] Create checkAuthentication for competition --- client/src/Main.tsx | 28 ++++++++++++----- client/src/actions/competitionLogin.ts | 12 +++++++ client/src/actions/types.ts | 2 ++ client/src/utils/SecureRoute.tsx | 19 +++++++++--- ...st.ts => checkAuthenticationAdmin.test.ts} | 10 +++--- ...ication.ts => checkAuthenticationAdmin.ts} | 2 +- .../utils/checkAuthenticationCompetition.ts | 31 +++++++++++++++++++ 7 files changed, 87 insertions(+), 17 deletions(-) rename client/src/utils/{checkAuthentication.test.ts => checkAuthenticationAdmin.test.ts} (97%) rename client/src/utils/{checkAuthentication.ts => checkAuthenticationAdmin.ts} (94%) create mode 100644 client/src/utils/checkAuthenticationCompetition.ts diff --git a/client/src/Main.tsx b/client/src/Main.tsx index b999a977..deeb7a47 100644 --- a/client/src/Main.tsx +++ b/client/src/Main.tsx @@ -20,14 +20,28 @@ const Main: React.FC = () => { return ( <BrowserRouter> <Switch> - <SecureRoute login exact path="/" component={LoginPage} /> - <SecureRoute path="/admin" component={AdminPage} /> - <SecureRoute path="/editor/competition-id=:competitionId" component={PresentationEditorPage} /> + <SecureRoute authLevel={'login'} exact path="/" component={LoginPage} /> + <SecureRoute authLevel={'admin'} path="/admin" component={AdminPage} /> + <SecureRoute + authLevel={'admin'} + path="/editor/competition-id=:competitionId" + component={PresentationEditorPage} + /> <Route exact path="/:code" component={ViewSelectPage} /> - <Route exact path="/participant/id=:id&code=:code" component={ParticipantViewPage} /> - <SecureRoute exact path="/presenter/id=:id&code=:code" component={PresenterViewPage} /> - <Route exact path="/judge/id=:id&code=:code" component={JudgeViewPage} /> - <Route exact path="/audience/id=:id&code=:code" component={AudienceViewPage} /> + <SecureRoute + authLevel={'competition'} + exact + path="/participant/id=:id&code=:code" + component={ParticipantViewPage} + /> + <SecureRoute + authLevel={'competition'} + exact + path="/presenter/id=:id&code=:code" + component={PresenterViewPage} + /> + <SecureRoute authLevel={'competition'} exact path="/judge/id=:id&code=:code" component={JudgeViewPage} /> + <SecureRoute authLevel={'competition'} exact path="/audience/id=:id&code=:code" component={AudienceViewPage} /> </Switch> </BrowserRouter> ) diff --git a/client/src/actions/competitionLogin.ts b/client/src/actions/competitionLogin.ts index 093abdb8..b5d87756 100644 --- a/client/src/actions/competitionLogin.ts +++ b/client/src/actions/competitionLogin.ts @@ -26,3 +26,15 @@ export const loginCompetition = (code: string, history: History) => async (dispa console.log(err) }) } + +// Log out from competition and remove jwt token from local storage and axios +export const logoutCompetition = () => async (dispatch: AppDispatch) => { + localStorage.removeItem('token') + await axios.post('/api/auth/logout').then(() => { + delete axios.defaults.headers.common['Authorization'] + dispatch({ + type: Types.SET_COMPETITION_LOGIN_UNAUTHENTICATED, + }) + window.location.href = '/' //redirect to login page + }) +} diff --git a/client/src/actions/types.ts b/client/src/actions/types.ts index 445a8d86..815e9d48 100644 --- a/client/src/actions/types.ts +++ b/client/src/actions/types.ts @@ -14,6 +14,8 @@ export default { SET_SEARCH_USERS_TOTAL_COUNT: 'SET_SEARCH_USERS_TOTAL_COUNT', SET_ERRORS: 'SET_ERRORS', CLEAR_ERRORS: 'CLEAR_ERRORS', + SET_COMPETITION_LOGIN_AUTHENTICATED: 'SET_COMPETITION_LOGIN_AUTHENTICATED', + SET_COMPETITION_LOGIN_UNAUTHENTICATED: 'SET_COMPETITION_LOGIN_UNAUTHENTICATED', SET_COMPETITION_LOGIN_ERRORS: 'SET_COMPETITION_LOGIN_ERRORS', CLEAR_COMPETITION_LOGIN_ERRORS: 'CLEAR_COMPETITION_LOGIN_ERRORS', SET_UNAUTHENTICATED: 'SET_UNAUTHENTICATED', diff --git a/client/src/utils/SecureRoute.tsx b/client/src/utils/SecureRoute.tsx index c8c238df..9e977c4b 100644 --- a/client/src/utils/SecureRoute.tsx +++ b/client/src/utils/SecureRoute.tsx @@ -1,26 +1,37 @@ import React, { useEffect } from 'react' import { Redirect, Route, RouteProps } from 'react-router-dom' import { useAppSelector } from '../hooks' -import { CheckAuthentication } from './checkAuthentication' +import { CheckAuthenticationAdmin } from './checkAuthenticationAdmin' +import { CheckAuthenticationCompetition } from './checkAuthenticationCompetition' interface SecureRouteProps extends RouteProps { login?: boolean component: React.ComponentType<any> rest?: any + authLevel: 'competition' | 'admin' | 'login' } /** Utility component to use for authentication, replace all routes that should be private with secure routes*/ -const SecureRoute: React.FC<SecureRouteProps> = ({ login, component: Component, ...rest }: SecureRouteProps) => { +const SecureRoute: React.FC<SecureRouteProps> = ({ + login, + component: Component, + authLevel, + ...rest +}: SecureRouteProps) => { const authenticated = useAppSelector((state) => state.user.authenticated) const [initialized, setInitialized] = React.useState(false) useEffect(() => { const waitForAuthentication = async () => { - await CheckAuthentication() + if (authLevel === 'admin' || authLevel === 'login') { + await CheckAuthenticationAdmin() + } else { + await CheckAuthenticationCompetition() + } setInitialized(true) } waitForAuthentication() }, []) if (initialized) { - if (login) + if (authLevel === 'login') return ( <Route {...rest} render={(props) => (authenticated ? <Redirect to="/admin" /> : <Component {...props} />)} /> ) diff --git a/client/src/utils/checkAuthentication.test.ts b/client/src/utils/checkAuthenticationAdmin.test.ts similarity index 97% rename from client/src/utils/checkAuthentication.test.ts rename to client/src/utils/checkAuthenticationAdmin.test.ts index 6d12e1fc..4651813d 100644 --- a/client/src/utils/checkAuthentication.test.ts +++ b/client/src/utils/checkAuthenticationAdmin.test.ts @@ -1,7 +1,7 @@ import mockedAxios from 'axios' import Types from '../actions/types' import store from '../store' -import { CheckAuthentication } from './checkAuthentication' +import { CheckAuthenticationAdmin } from './CheckAuthenticationAdmin' it('dispatches correct actions when auth token is ok', async () => { const userRes: any = { @@ -20,7 +20,7 @@ it('dispatches correct actions when auth token is ok', async () => { const testToken = 'Bearer eyJ0eXAiOiJeyJ0eXAiOiJKV1QeyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE2MTgzMDQ5MzksImV4cCI6MzI1MTI1MjU3NTcsImF1ZCI6Ind3dy5leGFtcGxlLmNvbSIsInN1YiI6Impyb2NrZXRAZXhhbXBsZS5jb20iLCJHaXZlbk5hbWUiOiJKb2hubnkiLCJTdXJuYW1lIjoiUm9ja2V0IiwiRW1haWwiOiJqcm9ja2V0QGV4YW1wbGUuY29tIiwiUm9sZSI6WyJNYW5hZ2VyIiwiUHJvamVjdCBBZG1pbmlzdHJhdG9yIl19.DrOOcCo5jMR-SNERE0uRp_kolQ2HjX8-hHXYEMnIxSceyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE2MTgzMDQ5MzksImV4cCI6MzI1MTI1MjU3NTcsImF1ZCI6Ind3dy5leGFtcGxlLmNvbSIsInN1YiI6Impyb2NrZXRAZXhhbXBsZS5jb20iLCJHaXZlbk5hbWUiOiJKb2hubnkiLCJTdXJuYW1lIjoiUm9ja2V0IiwiRW1haWwiOiJqcm9ja2V0QGV4YW1wbGUuY29tIiwiUm9sZSI6WyJNYW5hZ2VyIiwiUHJvamVjdCBBZG1pbmlzdHJhdG9yIl19.DrOOcCo5jMR-SNERE0uRp_kolQ2HjX8-hHXYEMnIxSceyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE2MTgzMDQ5MzksImV4cCI6MzI1MTI1MjU3NTcsImF1ZCI6Ind3dy5leGFtcGxlLmNvbSIsInN1YiI6Impyb2NrZXRAZXhhbXBsZS5jb20iLCJHaXZlbk5hbWUiOiJKb2hubnkiLCJTdXJuYW1lIjoiUm9ja2V0IiwiRW1haWwiOiJqcm9ja2V0QGV4YW1wbGUuY29tIiwiUm9sZSI6WyJNYW5hZ2VyIiwiUHJvamVjdCBBZG1pbmlzdHJhdG9yIl19.DrOOcCo5jMR-SNERE0uRp_kolQ2HjX8-hHXYEMnIxSceyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE2MTgzMDQ5MzksImV4cCI6MzI1MTI1MjU3NTcsImF1ZCI6Ind3dy5leGFtcGxlLmNvbSIsInN1YiI6Impyb2NrZXRAZXhhbXBsZS5jb20iLCJHaXZlbk5hbWUiOiJKb2hubnkiLCJTdXJuYW1lIjoiUm9ja2V0IiwiRW1haWwiOiJqcm9ja2V0QGV4YW1wbGUuY29tIiwiUm9sZSI6WyJNYW5hZ2VyIiwiUHJvamVjdCBBZG1pbmlzdHJhdG9yIl19.DrOOcCo5jMR-SNERE0uRp_kolQ2HjX8-hHXYEMnIxSciLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE2MTgzMDQ5MzksImV4cCI6MzI1MTI1MjU3NTcsImF1ZCI6Ind3dy5leGFtcGxlLmNvbSIsInN1YiI6Impyb2NrZXRAZXhhbXBsZS5jb20iLCJHaXZlbk5hbWUiOiJKb2hubnkiLCJTdXJuYW1lIjoiUm9ja2V0IiwiRW1haWwiOiJqcm9ja2V0QGV4YW1wbGUuY29tIiwiUm9sZSI6WyJNYW5hZ2VyIiwiUHJvamVjdCBBZG1pbmlzdHJhdG9yIl19.DrOOcCo5jMR-SNERE0uRp_kolQ2HjX8-hHXYEMnIxScKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE2MTgzMDQ5MzksImV4cCI6MzI1MTI1MjU3NTcsImF1ZCI6Ind3dy5leGFtcGxlLmNvbSIsInN1YiI6Impyb2NrZXRAZXhhbXBsZS5jb20iLCJHaXZlbk5hbWUiOiJKb2hubnkiLCJTdXJuYW1lIjoiUm9ja2V0IiwiRW1haWwiOiJqcm9ja2V0QGV4YW1wbGUuY29tIiwiUm9sZSI6WyJNYW5hZ2VyIiwiUHJvamVjdCBBZG1pbmlzdHJhdG9yIl19.DrOOcCo5jMR-SNERE0uRp_kolQ2HjX8-hHXYEMnIxSc' localStorage.setItem('token', testToken) - await CheckAuthentication() + await CheckAuthenticationAdmin() expect(spy).toBeCalledWith({ type: Types.LOADING_USER }) expect(spy).toBeCalledWith({ type: Types.SET_AUTHENTICATED }) expect(spy).toBeCalledWith({ type: Types.SET_USER, payload: userRes.data }) @@ -39,7 +39,7 @@ it('dispatches correct actions when getting user data fails', async () => { const testToken = 'Bearer eyJ0eXAiOiJeyJ0eXAiOiJKV1QeyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE2MTgzMDQ5MzksImV4cCI6MzI1MTI1MjU3NTcsImF1ZCI6Ind3dy5leGFtcGxlLmNvbSIsInN1YiI6Impyb2NrZXRAZXhhbXBsZS5jb20iLCJHaXZlbk5hbWUiOiJKb2hubnkiLCJTdXJuYW1lIjoiUm9ja2V0IiwiRW1haWwiOiJqcm9ja2V0QGV4YW1wbGUuY29tIiwiUm9sZSI6WyJNYW5hZ2VyIiwiUHJvamVjdCBBZG1pbmlzdHJhdG9yIl19.DrOOcCo5jMR-SNERE0uRp_kolQ2HjX8-hHXYEMnIxSceyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE2MTgzMDQ5MzksImV4cCI6MzI1MTI1MjU3NTcsImF1ZCI6Ind3dy5leGFtcGxlLmNvbSIsInN1YiI6Impyb2NrZXRAZXhhbXBsZS5jb20iLCJHaXZlbk5hbWUiOiJKb2hubnkiLCJTdXJuYW1lIjoiUm9ja2V0IiwiRW1haWwiOiJqcm9ja2V0QGV4YW1wbGUuY29tIiwiUm9sZSI6WyJNYW5hZ2VyIiwiUHJvamVjdCBBZG1pbmlzdHJhdG9yIl19.DrOOcCo5jMR-SNERE0uRp_kolQ2HjX8-hHXYEMnIxSceyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE2MTgzMDQ5MzksImV4cCI6MzI1MTI1MjU3NTcsImF1ZCI6Ind3dy5leGFtcGxlLmNvbSIsInN1YiI6Impyb2NrZXRAZXhhbXBsZS5jb20iLCJHaXZlbk5hbWUiOiJKb2hubnkiLCJTdXJuYW1lIjoiUm9ja2V0IiwiRW1haWwiOiJqcm9ja2V0QGV4YW1wbGUuY29tIiwiUm9sZSI6WyJNYW5hZ2VyIiwiUHJvamVjdCBBZG1pbmlzdHJhdG9yIl19.DrOOcCo5jMR-SNERE0uRp_kolQ2HjX8-hHXYEMnIxSceyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE2MTgzMDQ5MzksImV4cCI6MzI1MTI1MjU3NTcsImF1ZCI6Ind3dy5leGFtcGxlLmNvbSIsInN1YiI6Impyb2NrZXRAZXhhbXBsZS5jb20iLCJHaXZlbk5hbWUiOiJKb2hubnkiLCJTdXJuYW1lIjoiUm9ja2V0IiwiRW1haWwiOiJqcm9ja2V0QGV4YW1wbGUuY29tIiwiUm9sZSI6WyJNYW5hZ2VyIiwiUHJvamVjdCBBZG1pbmlzdHJhdG9yIl19.DrOOcCo5jMR-SNERE0uRp_kolQ2HjX8-hHXYEMnIxSciLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE2MTgzMDQ5MzksImV4cCI6MzI1MTI1MjU3NTcsImF1ZCI6Ind3dy5leGFtcGxlLmNvbSIsInN1YiI6Impyb2NrZXRAZXhhbXBsZS5jb20iLCJHaXZlbk5hbWUiOiJKb2hubnkiLCJTdXJuYW1lIjoiUm9ja2V0IiwiRW1haWwiOiJqcm9ja2V0QGV4YW1wbGUuY29tIiwiUm9sZSI6WyJNYW5hZ2VyIiwiUHJvamVjdCBBZG1pbmlzdHJhdG9yIl19.DrOOcCo5jMR-SNERE0uRp_kolQ2HjX8-hHXYEMnIxScKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE2MTgzMDQ5MzksImV4cCI6MzI1MTI1MjU3NTcsImF1ZCI6Ind3dy5leGFtcGxlLmNvbSIsInN1YiI6Impyb2NrZXRAZXhhbXBsZS5jb20iLCJHaXZlbk5hbWUiOiJKb2hubnkiLCJTdXJuYW1lIjoiUm9ja2V0IiwiRW1haWwiOiJqcm9ja2V0QGV4YW1wbGUuY29tIiwiUm9sZSI6WyJNYW5hZ2VyIiwiUHJvamVjdCBBZG1pbmlzdHJhdG9yIl19.DrOOcCo5jMR-SNERE0uRp_kolQ2HjX8-hHXYEMnIxSc' localStorage.setItem('token', testToken) - await CheckAuthentication() + await CheckAuthenticationAdmin() expect(spy).toBeCalledWith({ type: Types.LOADING_USER }) expect(spy).toBeCalledWith({ type: Types.SET_UNAUTHENTICATED }) expect(spy).toBeCalledTimes(2) @@ -51,7 +51,7 @@ it('dispatches no actions when no token exists', async () => { return Promise.resolve({ data: {} }) }) const spy = jest.spyOn(store, 'dispatch') - await CheckAuthentication() + await CheckAuthenticationAdmin() expect(spy).not.toBeCalled() }) @@ -63,7 +63,7 @@ it('dispatches correct actions when token is expired', async () => { 'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE2MTgzMDY1MTUsImV4cCI6MTU4Njc3MDUxNSwiYXVkIjoid3d3LmV4YW1wbGUuY29tIiwic3ViIjoianJvY2tldEBleGFtcGxlLmNvbSIsIkdpdmVuTmFtZSI6IkpvaG5ueSIsIlN1cm5hbWUiOiJSb2NrZXQiLCJFbWFpbCI6Impyb2NrZXRAZXhhbXBsZS5jb20iLCJSb2xlIjpbIk1hbmFnZXIiLCJQcm9qZWN0IEFkbWluaXN0cmF0b3IiXX0.R5-oWGGumd-YWPoKyziJmVB8SdX6B9SsV6m7novIfgg' localStorage.setItem('token', testToken) const spy = jest.spyOn(store, 'dispatch') - await CheckAuthentication() + await CheckAuthenticationAdmin() expect(spy).toBeCalledWith({ type: Types.SET_UNAUTHENTICATED }) expect(spy).toBeCalledTimes(1) }) diff --git a/client/src/utils/checkAuthentication.ts b/client/src/utils/checkAuthenticationAdmin.ts similarity index 94% rename from client/src/utils/checkAuthentication.ts rename to client/src/utils/checkAuthenticationAdmin.ts index 9225aa29..3565f8e3 100644 --- a/client/src/utils/checkAuthentication.ts +++ b/client/src/utils/checkAuthenticationAdmin.ts @@ -8,7 +8,7 @@ const UnAuthorized = async () => { await logoutUser()(store.dispatch) } -export const CheckAuthentication = async () => { +export const CheckAuthenticationAdmin = async () => { const authToken = localStorage.token if (authToken) { const decodedToken: any = jwtDecode(authToken) diff --git a/client/src/utils/checkAuthenticationCompetition.ts b/client/src/utils/checkAuthenticationCompetition.ts new file mode 100644 index 00000000..533cfbfd --- /dev/null +++ b/client/src/utils/checkAuthenticationCompetition.ts @@ -0,0 +1,31 @@ +import axios from 'axios' +import jwtDecode from 'jwt-decode' +import { logoutCompetition } from '../actions/competitionLogin' +import Types from '../actions/types' +import { logoutUser } from '../actions/user' +import store from '../store' + +const UnAuthorized = async () => { + await logoutCompetition()(store.dispatch) +} + +export const CheckAuthenticationCompetition = async () => { + const authToken = localStorage.competitionToken + if (authToken) { + const decodedToken: any = jwtDecode(authToken) + if (decodedToken.exp * 1000 >= Date.now()) { + axios.defaults.headers.common['Authorization'] = authToken + await axios + .get('/api/auth/test') + .then((res) => { + store.dispatch({ type: Types.SET_COMPETITION_LOGIN_AUTHENTICATED }) + }) + .catch((error) => { + console.log(error) + UnAuthorized() + }) + } else { + await UnAuthorized() + } + } +} -- GitLab