diff --git a/src/App.js b/src/App.js index 19a0d545..b936c566 100644 --- a/src/App.js +++ b/src/App.js @@ -19,6 +19,9 @@ import Notifications from './pages/Notifications' import Context from './modules/Context' import ContextReducer from './modules/ContextReducer' import Footer from './components/Footer' +import IdleTimer from './utils/IdleTimer' +import logout from './utils/Logout' +const { sessionLimit } = require('./config.json') const initialState = { auth: false, @@ -56,6 +59,29 @@ const App = () => { } }, [userId]) + useEffect(() => { + if (!userId) return + + const timer = new IdleTimer({ + timeout: sessionLimit || 6 * 3600, + onTimeout: () => { + logout() + dispatch({ + type: 'DEAUTHENTICATE' + }) + }, + onExpired: () => { + dispatch({ + type: 'DEAUTHENTICATE' + }) + } + }) + + return () => { + timer.cleanUp() + } + }, [userId]) + return ( diff --git a/src/pages/Login.js b/src/pages/Login.js index 9d04240b..54241678 100644 --- a/src/pages/Login.js +++ b/src/pages/Login.js @@ -14,6 +14,7 @@ import AuthWrapper, { AuthLeftContainer, AuthRightContainer } from '../components/AuthWrapper' +const { sessionLimit } = require('../config.json') export const Login = (props) => { const { message } = props @@ -41,6 +42,10 @@ export const Login = (props) => { localStorage.setItem('id', payload.user.id) localStorage.setItem('username', payload.user.username) localStorage.setItem('email', payload.user.email) + localStorage.setItem( + '_expiredTime', + Date.now() + (sessionLimit || 3600) * 1000 + ) dispatch({ type: 'AUTHENTICATE' }) diff --git a/src/utils/IdleTimer.js b/src/utils/IdleTimer.js new file mode 100644 index 00000000..6da9ce34 --- /dev/null +++ b/src/utils/IdleTimer.js @@ -0,0 +1,56 @@ +/** + * @Class of a timeout tracker that constantly checks + * if the session has expired or not. When the session expires, + * the user is automatically logged out of the application. + * @parameters + * timeout - the time (in seconds) for which the session will be valid + * onTimeout - callback to be executed when the session is expired + * onExpired - callback to be executed if the user opens the applciation + * after the session has already expired + */ + +class IdleTimer { + constructor({ timeout, onTimeout, onExpired }) { + this.timeout = timeout + this.onTimeout = onTimeout + + // Get the expiration time from local storage and + // check if the session has already expired + const expiredTime = parseInt(localStorage.getItem('_expiredTime') || 0, 10) + + // If session has already expired, fire the onExpired callback + if (expiredTime > 0 && expiredTime < Date.now()) { + onExpired() + return + } + + // If the session has not expired, start checking the remaining time periodically + this.startInterval() + } + + // Function to check periodically if the session has expires + startInterval() { + // Run the check every 1 second + this.interval = setInterval(() => { + const expiredTime = parseInt( + localStorage.getItem('_expiredTime') || 0, + 10 + ) + + // Check if the session has expired + if (expiredTime < Date.now()) { + if (this.onTimeout) { + this.onTimeout() + this.cleanUp() + } + } + }, 1000) + } + + // cleanup code for the timeout tracker + cleanUp() { + localStorage.removeItem('_expiredTime') + clearInterval(this.interval) + } +} +export default IdleTimer diff --git a/src/utils/Logout.js b/src/utils/Logout.js new file mode 100644 index 00000000..848760b6 --- /dev/null +++ b/src/utils/Logout.js @@ -0,0 +1,11 @@ +import axios from 'axios' +import { navigate } from '@reach/router' +const { apiURL } = require('../config.json') + +const logout = async () => { + await axios.post(`${apiURL}/logout`, {}, { withCredentials: true }) + localStorage.clear() + navigate('/') +} + +export default logout