diff --git a/packages/auth/src/providers/cognito/utils/signInStore.ts b/packages/auth/src/providers/cognito/utils/signInStore.ts index 0028ab71067..1963b98db6f 100644 --- a/packages/auth/src/providers/cognito/utils/signInStore.ts +++ b/packages/auth/src/providers/cognito/utils/signInStore.ts @@ -1,6 +1,8 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 +import { ConsoleLogger } from '@aws-amplify/core'; + import { CognitoAuthSignInDetails } from '../types'; import { ChallengeName } from './clients/CognitoIdentityProvider/types'; @@ -27,6 +29,22 @@ type Store = (reducer: Reducer) => { type Reducer = (state: State, action: Action) => State; +const logger = new ConsoleLogger('Auth signInStore'); + +// Minutes until stored session invalidates +const EXPIRATION_MINUTES = 5; +const MS_TO_EXPIRY = 1000 * 60 * EXPIRATION_MINUTES; +const SIGN_IN_STATE_KEYS = [ + 'username', + 'challengeName', + 'signInSession', + 'expiry', +].reduce((keys: Record, key) => { + keys[key] = `CognitoSignInState.${key}`; + + return keys; +}, {}); + const signInReducer: Reducer = (state, action) => { switch (action.type) { case 'SET_SIGN_IN_SESSION': @@ -49,22 +67,58 @@ const signInReducer: Reducer = (state, action) => { username: action.value, }; case 'SET_INITIAL_STATE': - return defaultState(); + return getInitialState(); default: return state; } }; -function defaultState(): SignInState { +const isExpired = (expiryDate: string): boolean => { + const expiryTimestamp = Number(expiryDate); + const currentTimestamp = Date.now(); + + return expiryTimestamp <= currentTimestamp; +}; + +const clearPersistedSignInState = () => { + for (const key in SIGN_IN_STATE_KEYS) { + sessionStorage.removeItem(key); + } +}; + +const getDefaultState = (): SignInState => ({ + username: undefined, + challengeName: undefined, + signInSession: undefined, +}); + +// Hydrate signInStore from sessionStorage +const getInitialState = (): SignInState => { + const expiry = sessionStorage.getItem(SIGN_IN_STATE_KEYS.expiry) as string; + if (isExpired(expiry)) { + logger.warn('Sign-in session expired'); + clearPersistedSignInState(); + + return getDefaultState(); + } + + const username = + sessionStorage.getItem(SIGN_IN_STATE_KEYS.username) ?? ('' as string); + const challengeName = (sessionStorage.getItem( + SIGN_IN_STATE_KEYS.challengeName, + ) ?? '') as ChallengeName; + const signInSession = + sessionStorage.getItem(SIGN_IN_STATE_KEYS.signInSession) ?? ('' as string); + return { - username: undefined, - challengeName: undefined, - signInSession: undefined, + username, + challengeName, + signInSession, }; -} +}; const createStore: Store = reducer => { - let currentState = reducer(defaultState(), { type: 'SET_INITIAL_STATE' }); + let currentState = reducer(getDefaultState(), { type: 'SET_INITIAL_STATE' }); return { getState: () => currentState, @@ -81,8 +135,27 @@ export function setActiveSignInState(state: SignInState): void { type: 'SET_SIGN_IN_STATE', value: state, }); + + // Save the local state into sessionStorage + persistSignInState(state); } +// Free saved sign in states up from both memory and sessionStorage export function cleanActiveSignInState(): void { signInStore.dispatch({ type: 'SET_INITIAL_STATE' }); + clearPersistedSignInState(); } + +const persistSignInState = ({ + challengeName = '' as ChallengeName, + signInSession = '', + username = '', +}: SignInState) => { + sessionStorage.setItem(SIGN_IN_STATE_KEYS.username, username); + sessionStorage.setItem(SIGN_IN_STATE_KEYS.challengeName, challengeName); + sessionStorage.setItem(SIGN_IN_STATE_KEYS.signInSession, signInSession); + sessionStorage.setItem( + SIGN_IN_STATE_KEYS.expiry, + String(Date.now() + MS_TO_EXPIRY), + ); +};