Skip to content
This repository has been archived by the owner on Aug 4, 2020. It is now read-only.

Commit

Permalink
Merge pull request #18 from Nike-Inc/feature/allow_users_to_refresh_s…
Browse files Browse the repository at this point in the history
…ession

Allow users to renew their session and clear cached data on logout
  • Loading branch information
sdford authored May 23, 2017
2 parents 22c100d + 42debc7 commit 803496d
Show file tree
Hide file tree
Showing 11 changed files with 209 additions and 42 deletions.
6 changes: 6 additions & 0 deletions app/actions/appActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,4 +127,10 @@ export function loadManageSDBPage(id, path, vaultToken) {
dispatch(mSDBActions.fetchSDBDataFromCMS(id, vaultToken))
hashHistory.push(`/manage-safe-deposit-box/${id}`)
}
}

export function resetToInitialState() {
return {
type: constants.RESET_SIDEBAR_DATA
}
}
136 changes: 124 additions & 12 deletions app/actions/authenticationActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import * as cms from '../constants/cms'
import * as headerActions from '../actions/headerActions'
import * as cmsUtils from '../utils/cmsUtils'
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 { getLogger } from 'logger'

var log = getLogger('authentication-actions')
Expand All @@ -22,13 +25,15 @@ const AUTH_ACTION_TIMEOUT = 10000 // 10 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
* @returns {{type: string, payload: {tokenData: *}}} The object to dispatch to trigger the reducer to update the auth state
*/
export function loginUserSuccess(response) {
export function loginUserSuccess(response, authTokenTimeoutId) {
return {
type: constants.LOGIN_USER_SUCCESS,
payload: {
tokenData: response.data.client_token,
authTokenTimeoutId: authTokenTimeoutId
}
}
}
Expand Down Expand Up @@ -59,7 +64,7 @@ export function loginMfaRequired(response) {
/**
* Updates the state to indicate that the user is successfully authenticated.
*/
function handleUserLogin(response, dispatch) {
function handleUserLogin(response, dispatch, redirectToWelcome=true) {
let leaseDurationInSeconds = response.data.data.client_token.lease_duration
const millisecondsPerSecond = 1000
const bestGuessOfRequestLatencyInMilliseconds = 120 * millisecondsPerSecond // take 2 minutes off of duration to account for latency
Expand All @@ -68,19 +73,30 @@ function handleUserLogin(response, dispatch) {
let vaultTokenExpiresDateInMilliseconds = (now.getTime() + ((leaseDurationInSeconds * millisecondsPerSecond) - bestGuessOfRequestLatencyInMilliseconds))

let tokenExpiresDate = new Date()
let token = response.data.data.client_token.client_token
tokenExpiresDate.setTime(vaultTokenExpiresDateInMilliseconds)

log.debug(`Setting session timeout to ${tokenExpiresDate}`)

let timeToExpireTokenInMillis = tokenExpiresDate.getTime() - now.getTime()

setTimeout(() => {
dispatch(warnSessionExpiresSoon(token))
}, timeToExpireTokenInMillis - 120000) // warn two minutes before expiration

let authTokenTimeoutId = setTimeout(() => {
dispatch(handleSessionExpiration())
}, tokenExpiresDate.getTime() - now.getTime())
}, timeToExpireTokenInMillis)

sessionStorage.setItem('token', JSON.stringify(response.data))
sessionStorage.setItem('tokenExpiresDate', tokenExpiresDate)
sessionStorage.setItem('userRespondedToSessionWarning', false)
dispatch(messengerActions.clearAllMessages())
dispatch(loginUserSuccess(response.data))
dispatch(appActions.fetchSideBarData(response.data.data.client_token.client_token))
dispatch(loginUserSuccess(response.data, authTokenTimeoutId))
dispatch(appActions.fetchSideBarData(token))
if (redirectToWelcome) {
hashHistory.push("/")
}
}

/**
Expand Down Expand Up @@ -169,19 +185,20 @@ export function finalizeMfaLogin(otpToken, mfaDeviceId, stateToken) {
/**
* This action is dispatched to renew a users session token
*/
export function refreshAuth(token, redirect='/') {
export function refreshAuth(token, redirectPath='/', redirect=true) {
return function(dispatch) {
return axios({
url: environmentService.getDomain() + cms.USER_AUTH_PATH_REFRESH,
headers: {'X-Vault-Token': token},
timeout: AUTH_ACTION_TIMEOUT
})
.then(function (response) {
sessionStorage.setItem('token', JSON.stringify(response.data))
dispatch(loginUserSuccess(response.data))
hashHistory.push(redirect)
log.info(response)
dispatch(appActions.fetchSideBarData(response.data.data.client_token.client_token))
handleRemoveAuthTokenTimeout()
handleRemoveSessionWarningTimeout()
handleUserLogin(response, dispatch, false)
if (redirect) {
hashHistory.push(redirectPath)
}
})
.catch(function (response) {
log.error('Failed to login user', response)
Expand All @@ -207,6 +224,9 @@ export function logoutUser(token) {
dispatch(resetAuthState())
sessionStorage.removeItem('token')
sessionStorage.removeItem('tokenExpiresDate')
sessionStorage.removeItem('userRespondedToSessionWarning')
handleRemoveAuthTokenTimeout()
handleRemoveSessionWarningTimeout()
dispatch(headerActions.mouseOutUsername())
hashHistory.push('/login')
})
Expand All @@ -224,7 +244,13 @@ export function handleSessionExpiration() {
return function(dispatch) {
sessionStorage.removeItem('token')
sessionStorage.removeItem('tokenExpiresDate')
sessionStorage.removeItem('userRespondedToSessionWarning')
handleRemoveAuthTokenTimeout()
handleRemoveSessionWarningTimeout()
dispatch(expireSession())
dispatch(modalActions.clearAllModals())
dispatch(manageSDBActions.resetToInitialState())
dispatch(appActions.resetToInitialState())
}
}

Expand All @@ -238,4 +264,90 @@ export function expireSession() {
return {
type: constants.SESSION_EXPIRED
}
}
}

export function removeAuthTokenTimeoutId() {
return {
type: constants.REMOVE_AUTH_TOKEN_TIMEOUT
}
}

export function removeSessionWarningTimeoutId() {
return {
type: constants.REMOVE_SESSION_WARNING_TIMEOUT
}
}

export function setSessionWarningTimeoutId(id) {
return {
type: constants.SET_SESSION_WARNING_TIMEOUT_ID,
payload: {
sessionWarningTimeoutId: id
}
}
}

export function handleRemoveAuthTokenTimeout() {
return function(dispatch, getState) {
clearTimeout(getState().auth.authTokenTimeoutId)
dispatch(removeAuthTokenTimeoutId())
}
}

export function handleRemoveSessionWarningTimeout() {
return function(dispatch, getState) {
clearTimeout(getState().auth.sessionWarningTimeoutId)
dispatch(removeSessionWarningTimeoutId())
}
}

export function handleUserRespondedToSessionWarning() {
return function() {
sessionStorage.setItem('userRespondedToSessionWarning', true)
}
}

/**
* Warn the user at specified time that their session is about to expire
* @param timeToWarnInMillis - Time at which to warn user
* @param tokenStr - Token string
*/
export function setSessionWarningTimeout(timeToWarnInMillis, tokenStr) {
return function(dispatch) {
let userHasRespondedToSessionWarning = sessionStorage.getItem('userRespondedToSessionWarning') === "true";

if (! userHasRespondedToSessionWarning) {
let sessionWarningTimeoutId = setTimeout(() => {
dispatch(warnSessionExpiresSoon(tokenStr))
}, timeToWarnInMillis)

dispatch(setSessionWarningTimeoutId(sessionWarningTimeoutId))
}
}
}

/**
* Warn user that session is about to expire, and give the option to refresh session
* @param tokenStr - Token string
*/
export function warnSessionExpiresSoon(tokenStr) {

return function(dispatch) {
let yes = () => {
dispatch(refreshAuth(tokenStr, "/", false))
dispatch(handleUserRespondedToSessionWarning())
dispatch(modalActions.popModal())
}

let no = () => {
dispatch(handleUserRespondedToSessionWarning())
dispatch(modalActions.popModal())
}

let conf = <ConfirmationBox handleYes={yes}
handleNo={no}
message="Your session is about to expire. Would you like to stay logged in?"/>

dispatch(modalActions.pushModal(conf))
}
}
6 changes: 6 additions & 0 deletions app/actions/manageSafetyDepositBoxActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -367,3 +367,9 @@ export function updateStoredKeys(key) {
payload: key
}
}

export function resetToInitialState() {
return {
type: actions.RESET_SDB_DATA
}
}
6 changes: 6 additions & 0 deletions app/actions/modalActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,10 @@ export function popModal() {
return {
type: actions.POP_MODAL
}
}

export function clearAllModals() {
return {
type: actions.CLEAR_ALL_MODALS
}
}
7 changes: 7 additions & 0 deletions app/constants/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ export const LOGIN_USER_SUCCESS = 'LOGIN_USER_SUCCESS'
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_WARNING_TIMEOUT = "REMOVE_SESSION_WARNING_TIMEOUT"

// HEADER ACTIONS
export const USERNAME_CLICKED = 'MOUSE_HOVER_USERNAME'
Expand All @@ -19,6 +22,8 @@ export const FETCHING_SIDE_BAR_DATA = 'FETCHING_SIDE_BAR_DATA'
export const FETCHED_SIDE_BAR_DATA = 'FETCHED_SIDE_BAR_DATA'
export const MANAGE_BUCKET = 'MANAGE_BUCKET'
export const STORE_DOMAIN_DATA = 'STORE_DOMAIN_DATA'
export const RESET_SIDEBAR_DATA = 'RESET_SIDEBAR_DATA';


// Events for the create-new-bucket
export const CREATE_NEW_SDB_INIT = 'CREATE_NEW_SDB_INIT'
Expand All @@ -43,6 +48,7 @@ export const RESET_SUBMITTING_EDIT_SDB_REQUEST = 'RESET_SUBMITTING_EDIT_SDB_REQU
export const CLEAR_VAULT_DATA = 'CLEAR_VAULT_DATA'
export const SAVING_VAULT_SECRET = 'SAVING_VAULT_SECRET'
export const ADD_VAULT_KEY_IF_NOT_PRESET = 'ADD_VAULT_KEY_IF_NOT_PRESET'
export const RESET_SDB_DATA = 'RESET_SDB_DATA';

// Messenger
export const ADD_MESSAGE = 'ADD_MESSAGE'
Expand All @@ -52,6 +58,7 @@ export const CLEAR_ALL_MESSAGES = 'CLEAR_ALL_MESSAGES'
// Modal Stack
export const PUSH_MODAL = 'PUSH_MODAL'
export const POP_MODAL = 'POP_MODAL'
export const CLEAR_ALL_MODALS = 'CLEAR_ALL_MODALS'

// Metadata Actions
export const STORE_METADATA = 'STORE_METADATA'
Expand Down
16 changes: 11 additions & 5 deletions app/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import ManageSDBox from './components/ManageSDBox/ManageSDBox'
import SDBMetadataList from './components/SDBMetadataList/SDBMetadataList'
import NotFound from './components/NotFound/NotFound'
import configureStore from './store/configureStore'
import { loginUserSuccess, handleSessionExpiration } from './actions/authenticationActions'
import { loginUserSuccess, handleSessionExpiration, setSessionWarningTimeout } from './actions/authenticationActions'
import { getLogger } from 'logger'
import './assets/styles/reactSelect.scss'

Expand All @@ -34,11 +34,17 @@ if (token != null && token != "") {
let now = new Date()

log.debug(`Token expires on ${tokenExpiresDate}`)

setTimeout(() => {

let dateTokenExpiresInMillis = tokenExpiresDate.getTime() - now.getTime()

// warn two minutes before token expiration
store.dispatch(setSessionWarningTimeout(dateTokenExpiresInMillis - 120000, token.data.client_token.client_token))

let authTokenTimeoutId = setTimeout(() => {
store.dispatch(handleSessionExpiration())
}, tokenExpiresDate.getTime() - now.getTime())
store.dispatch(loginUserSuccess(token))
}, dateTokenExpiresInMillis)

store.dispatch(loginUserSuccess(token, authTokenTimeoutId))
}

// Create an enhanced history that syncs navigation events with the store
Expand Down
4 changes: 4 additions & 0 deletions app/reducers/appReducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,9 @@ export default createReducer(initialState, {
roles: payload.roles
}
})
},

[constants.RESET_SIDEBAR_DATA]: (state) => {
return initialState
}
})
Loading

0 comments on commit 803496d

Please sign in to comment.