Skip to content

Commit

Permalink
Session timeout adjustments - try to address sleep behavior (#2786)
Browse files Browse the repository at this point in the history
* Adjust header log - should be default not error

* Force refreshSession failures to display timeout, doesn matter if we thought we were logged in

* Apply suggestions from code review

Weird styling issues getting through

Co-authored-by: Jason Lin <[email protected]>

* add newline

---------

Co-authored-by: Jason Lin <[email protected]>
  • Loading branch information
haworku and JasonLin0991 authored Oct 1, 2024
1 parent 980e83a commit 6913a66
Show file tree
Hide file tree
Showing 4 changed files with 43 additions and 52 deletions.
2 changes: 1 addition & 1 deletion services/app-web/src/components/Header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const Header = ({
const { currentRoute: route } = useCurrentRoute()

const handleLogout = async () => {
await logout({ type: 'ERROR' })
await logout({type: 'DEFAULT'})
// no need to handle errors, logout will handle
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export const SessionTimeoutModal = ({
)

const handleLogoutSession = async () => {
idleTimer.message({ action: SESSION_ACTIONS.LOGOUT_SESSION }, true)
idleTimer.message({action: SESSION_ACTIONS.LOGOUT_SESSION_BY_CHOICE}, true)
}
const handleContinueSession = async () => {
idleTimer.activate()
Expand Down
12 changes: 4 additions & 8 deletions services/app-web/src/contexts/AuthContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,6 @@ function AuthProvider({
/*
Refetches current user via graphql to confirm authentication
Also can reconfirm authentication, if unexpectedly logged out we know that session may have timed out or user was logged out of another tab
@param {failureRedirectPath} passed through to logout which is called on certain checkAuth failures
Use this function to reconfirm the user is logged in. Also used in CognitoLogin
*/
Expand All @@ -188,17 +187,14 @@ function AuthProvider({
/*
Refreshes the cognito session token
Also can reconfirm authentication, if unexpectedly logged out we know that session may have timed out
@param {failureRedirectPath} passed through to logout which is called on certain checkAuth failures
*/
const refreshAuth = async () => {
if (authMode !== 'LOCAL') {
const result = await extendSession()
if (result instanceof Error) {
if (loggedInUser) {
await logout({
type: 'TIMEOUT',
})
}
if (result instanceof Error){
await logout({
type: 'TIMEOUT'
})
}
}
return
Expand Down
79 changes: 37 additions & 42 deletions services/app-web/src/pages/Wrapper/AuthenticatedRouteWrapper.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React from 'react'
import { ModalRef } from '@trussworks/react-uswds'
import { createRef } from 'react'
import { createRef} from 'react'
import { useAuth } from '../../contexts/AuthContext'
import { useLDClient } from 'launchdarkly-react-client-sdk'
import { featureFlags } from '../../common-code/featureFlags'
Expand All @@ -9,8 +9,9 @@ import { IdleTimerProvider } from 'react-idle-timer'
import { usePage } from '../../contexts/PageContext'

const SESSION_ACTIONS = {
LOGOUT_SESSION: 'LOGOUT_SESSION',
CONTINUE_SESSION: 'CONTINUE_SESSSION',
LOGOUT_SESSION_BY_CHOICE: 'LOGOUT_SESSION_BY_CHOICE',
LOGOUT_SESSION_BY_TIMEOUT: 'LOGOUT_SESSION_BY_TIMEOUT',
CONTINUE_SESSION: 'CONTINUE_SESSSION'
}

// AuthenticatedRouteWrapper control access to protected routes and the session timeout modal
Expand All @@ -22,14 +23,14 @@ const AuthenticatedRouteWrapper = ({
}): React.ReactElement => {
const modalRef = createRef<ModalRef>()
const ldClient = useLDClient()
const { logout, refreshAuth } = useAuth()
const { activeModalRef, updateModalRef } = usePage()
const {logout, refreshAuth} = useAuth()
const {activeModalRef, updateModalRef} = usePage()

const openSessionTimeoutModal = () => {
// Make sure we close any active modals for session timeout, should overrides the focus trap
if (activeModalRef && activeModalRef !== modalRef) {
activeModalRef.current?.toggleModal(undefined, false)
updateModalRef({ updatedModalRef: modalRef })
updateModalRef({updatedModalRef: modalRef})
}

modalRef.current?.toggleModal(undefined, true)
Expand All @@ -39,75 +40,69 @@ const AuthenticatedRouteWrapper = ({
}
const logoutBySessionTimeout = async () => {
closeSessionTimeoutModal()
await logout({ type: 'TIMEOUT' })
}
await logout({type: 'TIMEOUT'})}

const logoutByUserChoice = async () => {
closeSessionTimeoutModal()
await logout({ type: 'DEFAULT' })
await logout({type: 'DEFAULT'})
}
const refreshSession = async () => {
closeSessionTimeoutModal()
await refreshAuth()
}

// For multi-tab support we emit messages related to user actions on the session timeout modal
const onMessage = async ({
action,
}: {
action: 'LOGOUT_SESSION' | 'CONTINUE_SESSION'
}) => {
switch (action) {
case 'LOGOUT_SESSION':
await logoutByUserChoice()
break
case 'CONTINUE_SESSION':
const onMessage = async ({action}: {action: 'LOGOUT_SESSION_BY_CHOICE' | 'LOGOUT_SESSION_BY_TIMEOUT' | 'CONTINUE_SESSION'}) => {
switch (action) {
case 'LOGOUT_SESSION_BY_CHOICE':
await logoutByUserChoice()
break;
case 'LOGOUT_SESSION_BY_TIMEOUT':
await logoutBySessionTimeout()
break;
case 'CONTINUE_SESSION':
await refreshSession()
break
default:
default:
// no op
}
}

// All time increment constants must be milliseconds
const RECHECK_FREQUENCY = 500
const SESSION_TIMEOUT_COUNTDOWN = 2 * 60 * 1000
const SESSION_DURATION: number =
ldClient?.variation(
featureFlags.MINUTES_UNTIL_SESSION_EXPIRES.flag,
featureFlags.MINUTES_UNTIL_SESSION_EXPIRES.defaultValue
) *
60 *
1000 // controlled by feature flag for testing in lower environments
const SHOW_SESSION_EXPIRATION: boolean = ldClient?.variation(
// All time increment constants must be milliseconds
const RECHECK_FREQUENCY = 500
const SESSION_TIMEOUT_COUNTDOWN = 2 * 60 * 1000
const SESSION_DURATION: number = ldClient?.variation(
featureFlags.MINUTES_UNTIL_SESSION_EXPIRES.flag,
featureFlags.MINUTES_UNTIL_SESSION_EXPIRES.defaultValue
) * 60 * 1000 // controlled by feature flag for testing in lower environments
const SHOW_SESSION_EXPIRATION:boolean = ldClient?.variation(
featureFlags.SESSION_EXPIRING_MODAL.flag,
featureFlags.SESSION_EXPIRING_MODAL.defaultValue
) // controlled by feature flag for testing in lower environments
)// controlled by feature flag for testing in lower environments
let promptCountdown = SESSION_TIMEOUT_COUNTDOWN // may be reassigned if session duration is shorter time period

// Session duration must be longer than prompt countdown to allow IdleTimer to load
if (SESSION_DURATION <= SESSION_TIMEOUT_COUNTDOWN) {
promptCountdown = SESSION_DURATION - 1000
}

return (
<IdleTimerProvider
return (<IdleTimerProvider
onIdle={logoutBySessionTimeout}
onActive={refreshSession}
onPrompt={
SHOW_SESSION_EXPIRATION ? openSessionTimeoutModal : undefined
}
onPrompt={ SHOW_SESSION_EXPIRATION ? openSessionTimeoutModal: undefined}
promptBeforeIdle={promptCountdown}
timeout={SESSION_DURATION}
throttle={RECHECK_FREQUENCY}
// cross tab props
onMessage={onMessage}
syncTimers={RECHECK_FREQUENCY}
crossTab={true}
>
>
{children}
<SessionTimeoutModal modalRef={modalRef} />
</IdleTimerProvider>
)
<SessionTimeoutModal
modalRef={modalRef}
/>
</IdleTimerProvider>)
}

export { SESSION_ACTIONS, AuthenticatedRouteWrapper }
export {SESSION_ACTIONS, AuthenticatedRouteWrapper}

0 comments on commit 6913a66

Please sign in to comment.