From 3bc2f37ab0409bfe6da435be81def147919abc2c Mon Sep 17 00:00:00 2001 From: Shaun Ford Date: Wed, 16 May 2018 16:44:25 -0700 Subject: [PATCH] Add interval to check session expiration (#157) * Add interval to check session expiration --- .../app/actions/authenticationActions.js | 68 ++++++++++++------- dashboard/app/constants/actions.js | 2 +- dashboard/app/main.js | 14 ++-- .../app/reducers/authenticationReducer.js | 10 +-- dashboard/package.json | 3 +- gradle.properties | 2 +- 6 files changed, 63 insertions(+), 36 deletions(-) diff --git a/dashboard/app/actions/authenticationActions.js b/dashboard/app/actions/authenticationActions.js index 8a02e760c..690ef6c9e 100644 --- a/dashboard/app/actions/authenticationActions.js +++ b/dashboard/app/actions/authenticationActions.js @@ -12,6 +12,7 @@ import ApiError from '../components/ApiError/ApiError' import ConfirmationBox from '../components/ConfirmationBox/ConfirmationBox' import * as modalActions from '../actions/modalActions' import * as manageSDBActions from '../actions/manageSafetyDepositBoxActions' +import * as workerTimers from 'worker-timers' import { getLogger } from 'logger' var log = getLogger('authentication-actions') @@ -25,15 +26,15 @@ const AUTH_ACTION_TIMEOUT = 60000 // 60 seconds in milliseconds /** * This action is dispatched when we have a valid response object from the CMS Auth endpoint * @param response The json response object from the ldap auth cms endpoint - * @param authTokenTimeoutId ID of the timeout that expires the user session + * @param sessionExpirationCheckIntervalId ID of the interval that checks to see if the user's session has expired * @returns {{type: string, payload: {tokenData: *}}} The object to dispatch to trigger the reducer to update the auth state */ -export function loginUserSuccess(response, authTokenTimeoutId) { +export function loginUserSuccess(response, sessionExpirationCheckIntervalId) { return { type: constants.LOGIN_USER_SUCCESS, payload: { tokenData: response.data.client_token, - authTokenTimeoutId: authTokenTimeoutId + sessionExpirationCheckIntervalId: sessionExpirationCheckIntervalId } } } @@ -80,21 +81,26 @@ function handleUserLogin(response, dispatch, redirectToWelcome=true) { let timeToExpireTokenInMillis = tokenExpiresDate.getTime() - now.getTime() - let sessionWarningTimeoutId = setTimeout(() => { + let sessionExpirationCheckIntervalInMillis = 2000 + let sessionExpirationCheckIntervalId = workerTimers.setInterval(() => { + let currentTimeInMillis = new Date().getTime() + let sessionExpirationTimeInMillis = tokenExpiresDate.getTime() + if (currentTimeInMillis >= sessionExpirationTimeInMillis) { + dispatch(handleSessionExpiration()) + } + }, sessionExpirationCheckIntervalInMillis) + + let sessionWarningTimeoutId = workerTimers.setTimeout(() => { dispatch(warnSessionExpiresSoon(token)) }, timeToExpireTokenInMillis - 120000) // warn two minutes before expiration dispatch(setSessionWarningTimeoutId(sessionWarningTimeoutId)) - let authTokenTimeoutId = setTimeout(() => { - dispatch(handleSessionExpiration()) - }, timeToExpireTokenInMillis) - sessionStorage.setItem('token', JSON.stringify(response.data)) sessionStorage.setItem('tokenExpiresDate', tokenExpiresDate) sessionStorage.setItem('userRespondedToSessionWarning', false) dispatch(messengerActions.clearAllMessages()) - dispatch(loginUserSuccess(response.data, authTokenTimeoutId)) + dispatch(loginUserSuccess(response.data, sessionExpirationCheckIntervalId)) dispatch(appActions.fetchSideBarData(token)) if (redirectToWelcome) { hashHistory.push("/") @@ -196,9 +202,9 @@ export function refreshAuth(token, redirectPath='/', redirect=true) { timeout: AUTH_ACTION_TIMEOUT }) .then(function (response) { - dispatch(handleRemoveAuthTokenTimeout()) - dispatch(handleRemoveSessionWarningTimeout()) - setTimeout(function(){ + dispatch(handleRemoveSessionExpirationCheck()) + dispatch(removeSessionWarningTimeout()) + workerTimers.setTimeout(function(){ handleUserLogin(response, dispatch, false) if (redirect) { hashHistory.push(redirectPath) @@ -232,8 +238,8 @@ export function logoutUser(token) { sessionStorage.removeItem('token') sessionStorage.removeItem('tokenExpiresDate') sessionStorage.removeItem('userRespondedToSessionWarning') - dispatch(handleRemoveAuthTokenTimeout()) - dispatch(handleRemoveSessionWarningTimeout()) + dispatch(handleRemoveSessionExpirationCheck()) + dispatch(removeSessionWarningTimeout()) dispatch(resetAuthState()) dispatch(headerActions.mouseOutUsername()) hashHistory.push('/login') @@ -253,8 +259,8 @@ export function handleSessionExpiration() { sessionStorage.removeItem('token') sessionStorage.removeItem('tokenExpiresDate') sessionStorage.removeItem('userRespondedToSessionWarning') - dispatch(handleRemoveAuthTokenTimeout()) - dispatch(handleRemoveSessionWarningTimeout()) + dispatch(handleRemoveSessionExpirationCheck()) + dispatch(removeSessionWarningTimeout()) dispatch(expireSession()) dispatch(modalActions.clearAllModals()) dispatch(manageSDBActions.resetToInitialState()) @@ -274,9 +280,9 @@ export function expireSession() { } } -export function removeAuthTokenTimeoutId() { +export function removeSessionExpirationCheck() { return { - type: constants.REMOVE_AUTH_TOKEN_TIMEOUT + type: constants.REMOVE_SESSION_EXPIRATION_CHECK_INTERVAL } } @@ -295,16 +301,30 @@ export function setSessionWarningTimeoutId(id) { } } -export function handleRemoveAuthTokenTimeout() { +export function handleRemoveSessionExpirationCheck() { return function(dispatch, getState) { - clearTimeout(getState().auth.authTokenTimeoutId) - dispatch(removeAuthTokenTimeoutId()) + let sessionExpirationCheckIntervalId = getState().auth.sessionExpirationCheckIntervalId + log.debug(`Removing session expiration check interval, id: ${sessionExpirationCheckIntervalId}`) + try { + workerTimers.clearInterval(sessionExpirationCheckIntervalId) + } catch(err) { + console.log(`Failed to clear auth token timeout, id=${sessionExpirationCheckIntervalId}`) + console.log(err) + } + dispatch(removeSessionExpirationCheck()) } } -export function handleRemoveSessionWarningTimeout() { +export function removeSessionWarningTimeout() { return function(dispatch, getState) { - clearTimeout(getState().auth.sessionWarningTimeoutId) + let sessionWarningTimeoutId = getState().auth.sessionWarningTimeoutId + log.debug(`Removing warning timeout, id: ${sessionWarningTimeoutId}`) + try { + workerTimers.clearTimeout(getState().auth.sessionWarningTimeoutId) + } catch(err) { + console.log(`Failed to clear session warning timeout, id=${sessionWarningTimeoutId}`) + console.log(err) + } dispatch(removeSessionWarningTimeoutId()) } } @@ -325,7 +345,7 @@ export function setSessionWarningTimeout(timeToWarnInMillis, tokenStr) { let userHasRespondedToSessionWarning = sessionStorage.getItem('userRespondedToSessionWarning') === "true"; if (! userHasRespondedToSessionWarning) { - let sessionWarningTimeoutId = setTimeout(() => { + let sessionWarningTimeoutId = workerTimers.setTimeout(() => { dispatch(warnSessionExpiresSoon(tokenStr)) }, timeToWarnInMillis) diff --git a/dashboard/app/constants/actions.js b/dashboard/app/constants/actions.js index 75151e3d8..13124ec13 100644 --- a/dashboard/app/constants/actions.js +++ b/dashboard/app/constants/actions.js @@ -5,7 +5,7 @@ export const RESET_USER_AUTH_STATE = 'RESET_USER_AUTH_STATE' export const SESSION_EXPIRED = 'SESSION_EXPIRED' export const LOGIN_MFA_REQUIRED = 'LOGIN_MFA_REQUIRED' export const SET_SESSION_WARNING_TIMEOUT_ID = "SET_SESSION_WARNING_TIMEOUT_ID" -export const REMOVE_AUTH_TOKEN_TIMEOUT = "REMOVE_AUTH_TOKEN_TIMEOUT" +export const REMOVE_SESSION_EXPIRATION_CHECK_INTERVAL = "REMOVE_SESSION_EXPIRATION_CHECK_INTERVAL" export const REMOVE_SESSION_WARNING_TIMEOUT = "REMOVE_SESSION_WARNING_TIMEOUT" // HEADER ACTIONS diff --git a/dashboard/app/main.js b/dashboard/app/main.js index 8ec3014d2..542bada85 100644 --- a/dashboard/app/main.js +++ b/dashboard/app/main.js @@ -11,6 +11,7 @@ import ManageSafeDepositBox from './components/ManageSafeDepositBox/ManageSafeDe import NotFound from './components/NotFound/NotFound' import configureStore from './store/configureStore' import { loginUserSuccess, handleSessionExpiration, setSessionWarningTimeout } from './actions/authenticationActions' +import * as workerTimers from 'worker-timers' import { getLogger } from 'logger' import './assets/styles/reactSelect.scss' @@ -40,11 +41,16 @@ if (token != null && token != "") { // warn two minutes before token expiration store.dispatch(setSessionWarningTimeout(dateTokenExpiresInMillis - 120000, token.data.client_token.client_token)) - let authTokenTimeoutId = setTimeout(() => { - store.dispatch(handleSessionExpiration()) - }, dateTokenExpiresInMillis) + let sessionExpirationCheckIntervalInMillis = 2000 + let sessionExpirationCheckIntervalId = workerTimers.setInterval(() => { + let currentTimeInMillis = new Date().getTime() + let sessionExpirationTimeInMillis = tokenExpiresDate.getTime() + if (currentTimeInMillis >= sessionExpirationTimeInMillis) { + store.dispatch(handleSessionExpiration()) + } + }, sessionExpirationCheckIntervalInMillis) - store.dispatch(loginUserSuccess(token, authTokenTimeoutId)) + store.dispatch(loginUserSuccess(token, sessionExpirationCheckIntervalId)) } // Create an enhanced history that syncs navigation events with the store diff --git a/dashboard/app/reducers/authenticationReducer.js b/dashboard/app/reducers/authenticationReducer.js index 16022ce20..92bc1caf6 100644 --- a/dashboard/app/reducers/authenticationReducer.js +++ b/dashboard/app/reducers/authenticationReducer.js @@ -13,7 +13,7 @@ const initialState = { isAdmin: false, groups: [], policies: null, - authTokenTimeoutId: null, + sessionExpirationCheckIntervalId: null, sessionWarningTimeoutId: null } @@ -36,7 +36,7 @@ export default createReducer(initialState, { userName: payload.tokenData.metadata.username, groups: payload.tokenData.metadata.groups.split(/,/), policies: payload.tokenData.policies, - authTokenTimeoutId: payload.authTokenTimeoutId, + sessionExpirationCheckIntervalId: payload.sessionExpirationCheckIntervalId, }) }, // logs the user out and resets user data @@ -65,11 +65,11 @@ export default createReducer(initialState, { sessionWarningTimeoutId: payload.sessionWarningTimeoutId, }) }, - // removes the timeout id of the auth token expire - [constants.REMOVE_AUTH_TOKEN_TIMEOUT]: (state) => { + // removes the interval id of the session expiration check + [constants.REMOVE_SESSION_EXPIRATION_CHECK_INTERVAL]: (state) => { return Object.assign({}, state, { - authTokenTimeoutId: null + sessionExpirationCheckIntervalId: null }) }, // removes the timeout id of the session warning diff --git a/dashboard/package.json b/dashboard/package.json index 554d4cd05..591a61f4a 100644 --- a/dashboard/package.json +++ b/dashboard/package.json @@ -38,7 +38,8 @@ "redux": "3.5.2", "redux-form": "5.3.4", "redux-logger": "2.6.1", - "redux-thunk": "2.1.0" + "redux-thunk": "2.1.0", + "worker-timers": "4.0.30" }, "devDependencies": { "babel-core": "6.10.4", diff --git a/gradle.properties b/gradle.properties index a3b074c66..a6ea88d3d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,6 +14,6 @@ # limitations under the License. # -version=3.15.2 +version=3.15.3 groupId=com.nike.cerberus artifactId=cms