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