@@ -6,7 +6,13 @@ import React, {
66} from "react"
77import { Amplify } from "aws-amplify"
88import { Hub } from "aws-amplify/utils"
9- import { signInWithRedirect , signOut , SignInWithRedirectInput } from "aws-amplify/auth"
9+ import {
10+ signInWithRedirect ,
11+ signOut ,
12+ SignInWithRedirectInput ,
13+ getCurrentUser ,
14+ fetchAuthSession
15+ } from "aws-amplify/auth"
1016import { authConfig } from "./configureAmplify"
1117
1218import { useLocalStorageState } from "@/helpers/useLocalStorageState"
@@ -37,6 +43,7 @@ export interface AuthContextType {
3743 cognitoSignIn : ( input ?: SignInWithRedirectInput ) => Promise < void >
3844 cognitoSignOut : ( redirectUri ?: string ) => Promise < boolean >
3945 clearAuthState : ( ) => void
46+ clearStorageCompletely : ( ) => number
4047 updateSelectedRole : ( value : RoleDetails ) => Promise < void >
4148 updateTrackerUserInfo : ( ) => Promise < TrackerUserInfoResult >
4249 updateInvalidSessionCause : ( cause : string ) => void
@@ -102,6 +109,27 @@ export const AuthProvider = ({children}: { children: React.ReactNode }) => {
102109 // updateTrackerUserInfo will set InvalidSessionCause to undefined
103110 }
104111
112+ const clearStorageCompletely = ( ) => {
113+ logger . info ( "Performing complete storage cleanup" )
114+ try {
115+ // Clear Cognito and Amplify localStorage
116+ const cognitoKeys = Object . keys ( localStorage ) . filter (
117+ key => key . includes ( "CognitoIdentityServiceProvider" ) ||
118+ key . includes ( "amplify" )
119+ )
120+ cognitoKeys . forEach ( key => localStorage . removeItem ( key ) )
121+
122+ // Clear all sessionStorage
123+ sessionStorage . clear ( )
124+
125+ logger . info ( `Cleared ${ cognitoKeys . length } Cognito keys from localStorage and all sessionStorage` )
126+ return cognitoKeys . length
127+ } catch ( storageError ) {
128+ logger . error ( "Error during complete storage cleanup" , storageError )
129+ return 0
130+ }
131+ }
132+
105133 const updateTrackerUserInfo = async ( ) => {
106134 const trackerUserInfo = await getTrackerUserInfo ( )
107135 setRolesWithAccess ( trackerUserInfo . rolesWithAccess )
@@ -124,33 +152,80 @@ export const AuthProvider = ({children}: { children: React.ReactNode }) => {
124152 const unsubscribe = Hub . listen ( "auth" , async ( { payload} ) => {
125153 logger . info ( "Auth event payload:" , payload )
126154 switch ( payload . event ) {
127- // On successful signIn or token refresh, get the latest user state
128155 case "signedIn" : {
129156 logger . info ( "Processing signedIn event" )
130157 logger . info ( "User %s logged in" , payload . data . username )
131- await updateTrackerUserInfo ( )
132158
133- setIsSignedIn ( true )
134- setIsSigningIn ( false )
135- setUser ( payload . data . username )
136- logger . info ( "Finished the signedIn event " )
159+ try {
160+ await updateTrackerUserInfo ( )
161+ setIsSignedIn ( true )
162+ setIsSigningIn ( false )
163+ setUser ( payload . data . username )
164+ logger . info ( "Finished the signedIn event" )
165+ } catch ( error : unknown ) {
166+ logger . error ( "Error during signedIn processing" , error )
167+ setError ( "Failed to complete sign-in process" )
168+ }
137169 break
138170 }
171+
139172 case "tokenRefresh" :
140173 logger . info ( "Processing tokenRefresh event" )
141174 setError ( null )
142175 break
176+
143177 case "signInWithRedirect" :
144178 logger . info ( "Processing signInWithRedirect event" )
145179 setError ( null )
146180 break
147181
148182 case "tokenRefresh_failure" :
149- case "signInWithRedirect_failure" :
150- logger . info ( "Processing tokenRefresh_failure or signInWithRedirect_failure event" )
183+ logger . info ( "Processing tokenRefresh_failure event" )
151184 clearAuthState ( )
152- setError ( "An error has occurred during the OAuth flow." )
185+ setError ( "Token refresh failed. Please sign in again." )
186+ break
187+
188+ case "signInWithRedirect_failure" : {
189+ logger . info ( "Processing signInWithRedirect_failure event" , payload )
190+
191+ // Check if this is the UserAlreadyAuthenticatedException
192+ const errorMessage = payload . data ?. error ?. message || ""
193+ const errorName = payload . data ?. error ?. name || ""
194+ const isUserAlreadyAuthError =
195+ errorMessage . includes ( "already a signed in user" ) ||
196+ errorName === "UserAlreadyAuthenticatedException"
197+
198+ if ( isUserAlreadyAuthError ) {
199+ logger . error ( "UserAlreadyAuthenticatedException during OAuth callback" )
200+
201+ // Clear all auth state
202+ clearAuthState ( )
203+
204+ // Clear Cognito localStorage
205+ try {
206+ const cognitoKeys = Object . keys ( localStorage ) . filter (
207+ key => key . includes ( "CognitoIdentityServiceProvider" ) ||
208+ key . includes ( "amplify" )
209+ )
210+ logger . info ( `Clearing ${ cognitoKeys . length } Cognito keys from localStorage` )
211+ cognitoKeys . forEach ( key => localStorage . removeItem ( key ) )
212+ } catch ( storageError ) {
213+ logger . error ( "Error clearing localStorage" , storageError )
214+ }
215+
216+ // Show user-friendly error
217+ setError ( "Session conflict detected. Redirecting to login..." )
218+
219+ // Redirect to login page after a brief delay
220+ setTimeout ( ( ) => {
221+ window . location . href = window . location . origin + "/login"
222+ } , 2000 )
223+ } else {
224+ clearAuthState ( )
225+ setError ( "An error has occurred during the OAuth flow." )
226+ }
153227 break
228+ }
154229
155230 case "customOAuthState" :
156231 logger . info ( "Processing customOAuthState event" )
@@ -165,7 +240,6 @@ export const AuthProvider = ({children}: { children: React.ReactNode }) => {
165240
166241 default :
167242 logger . info ( "Received unknown event" , payload )
168- // Other auth events? The type-defined cases are already handled above.
169243 break
170244 }
171245 } )
@@ -202,6 +276,9 @@ export const AuthProvider = ({children}: { children: React.ReactNode }) => {
202276 await signOut ( { global : true } )
203277 }
204278
279+ // Thorough cleanup for security (especially important on shared computers)
280+ clearStorageCompletely ( )
281+
205282 setIsSigningOut ( true )
206283 logger . info ( "Frontend amplify signout OK!" )
207284 return true
@@ -217,8 +294,64 @@ export const AuthProvider = ({children}: { children: React.ReactNode }) => {
217294 */
218295 const cognitoSignIn = async ( input ?: SignInWithRedirectInput ) => {
219296 logger . info ( "Initiating sign-in process..." )
220- await signInWithRedirect ( input )
221- setIsSigningIn ( true )
297+
298+ try {
299+ // Check if there's already an active session
300+ try {
301+ const currentUser = await getCurrentUser ( )
302+
303+ if ( currentUser ) {
304+ logger . info ( "Existing session detected before sign-in redirect" )
305+
306+ // Check if session is valid
307+ try {
308+ await fetchAuthSession ( { forceRefresh : true } )
309+ logger . info ( "Existing session is valid - clearing before new sign-in" )
310+ } catch {
311+ logger . info ( "Existing session is invalid - will clear" )
312+ }
313+
314+ // Clear the existing session before redirecting to new sign-in
315+ logger . info ( "Signing out existing session before new sign-in redirect" )
316+ await signOut ( { global : false } ) // Local signout only, don't redirect
317+
318+ // Small delay to ensure cleanup completes
319+ await new Promise ( resolve => setTimeout ( resolve , 300 ) )
320+ }
321+ } catch {
322+ // No existing user - proceed with sign-in
323+ logger . info ( "No existing session, proceeding with sign-in redirect" )
324+ }
325+
326+ // Now initiate the redirect
327+ await signInWithRedirect ( input )
328+ setIsSigningIn ( true )
329+
330+ } catch ( error : unknown ) {
331+ logger . error ( "Error during sign-in redirect initiation" , error )
332+
333+ if ( error && typeof error === "object" && "name" in error && error . name === "UserAlreadyAuthenticatedException" ) {
334+ logger . error ( "UserAlreadyAuthenticatedException before redirect - clearing session" )
335+
336+ // Clear Cognito localStorage
337+ try {
338+ const cognitoKeys = Object . keys ( localStorage ) . filter (
339+ key => key . includes ( "CognitoIdentityServiceProvider" ) ||
340+ key . includes ( "amplify" )
341+ )
342+ cognitoKeys . forEach ( key => localStorage . removeItem ( key ) )
343+ } catch ( storageError ) {
344+ logger . error ( "Error clearing localStorage" , storageError )
345+ }
346+
347+ // Retry the redirect
348+ logger . info ( "Retrying sign-in redirect after clearing session" )
349+ await signInWithRedirect ( input )
350+ setIsSigningIn ( true )
351+ } else {
352+ throw error
353+ }
354+ }
222355 }
223356
224357 const updateSelectedRole = async ( newRole : RoleDetails ) => {
@@ -250,6 +383,7 @@ export const AuthProvider = ({children}: { children: React.ReactNode }) => {
250383 cognitoSignIn,
251384 cognitoSignOut,
252385 clearAuthState,
386+ clearStorageCompletely,
253387 updateSelectedRole,
254388 updateTrackerUserInfo,
255389 updateInvalidSessionCause,
0 commit comments