Skip to content

Commit 789bea6

Browse files
upgraded login procedure
1 parent 4cf9dd7 commit 789bea6

File tree

2 files changed

+205
-14
lines changed

2 files changed

+205
-14
lines changed

packages/cpt-ui/src/context/AuthProvider.tsx

Lines changed: 147 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,13 @@ import React, {
66
} from "react"
77
import {Amplify} from "aws-amplify"
88
import {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"
1016
import {authConfig} from "./configureAmplify"
1117

1218
import {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,

packages/cpt-ui/src/context/ErrorBoundary.tsx

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React, {Component, ReactNode} from "react"
22
import {AwsRum} from "aws-rum-web"
33
import {AwsRumContext} from "./AwsRumProvider"
4+
import {logger} from "@/helpers/logger"
45

56
interface ErrorBoundaryProps {
67
children: ReactNode;
@@ -32,6 +33,9 @@ class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
3233
// Declare the context type
3334
declare context: React.ContextType<typeof AwsRumContext>
3435

36+
// Prevent multiple cleanup attempts
37+
private isCleaningUp = false
38+
3539
constructor(props: ErrorBoundaryProps) {
3640
super(props)
3741
this.state = {
@@ -58,17 +62,70 @@ class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
5862
// Correctly access the context value
5963
if (this.context) {
6064
const rumInstance: AwsRum = this.context
65+
66+
// Enhanced detection for authentication errors
67+
const isAuthError =
68+
error.name === "UserAlreadyAuthenticatedException" ||
69+
error.message?.includes("already a signed in user") ||
70+
error.message?.includes("User already authenticated") ||
71+
(error as unknown as {code?: string})?.code === "UserAlreadyAuthenticatedException"
72+
6173
// modify the error we send to rum to include the trace id we show to user
6274
// we use record event rather than record error so that session attributes are included
6375
const customError = {
6476
errorTraceId: this.state.errorTraceId,
6577
message: error.message,
6678
stack: error.stack,
67-
errorInfo: errorInfo
79+
errorInfo: errorInfo,
80+
errorType: error.name,
81+
isUserAlreadyAuthenticatedException: isAuthError
6882
}
6983
rumInstance.recordEvent("errorBoundaryCatch", customError)
7084
// but we also record an error to try and get get the real line numbers
7185
rumInstance.recordError(error)
86+
87+
// Handle authentication errors with thorough cleanup
88+
if (isAuthError && !this.isCleaningUp) {
89+
this.isCleaningUp = true
90+
logger.warn("UserAlreadyAuthenticatedException reached error boundary - clearing session")
91+
92+
try {
93+
// Check if we're in a browser environment and clear localStorage
94+
if (typeof window !== "undefined" && typeof localStorage !== "undefined") {
95+
const cognitoKeys = Object.keys(localStorage).filter(
96+
key => key.includes("CognitoIdentityServiceProvider") ||
97+
key.includes("amplify")
98+
)
99+
cognitoKeys.forEach(key => localStorage.removeItem(key))
100+
101+
rumInstance.recordEvent("errorBoundary_clearedCognitoSession", {
102+
clearedKeys: cognitoKeys.length,
103+
timestamp: new Date().toISOString()
104+
})
105+
}
106+
107+
// Clear sessionStorage
108+
if (typeof sessionStorage !== "undefined") {
109+
sessionStorage.clear()
110+
}
111+
112+
// Redirect to login after cleanup
113+
setTimeout(() => {
114+
if (typeof window !== "undefined") {
115+
window.location.href = window.location.origin + "/login"
116+
}
117+
}, 1000)
118+
119+
} catch (storageError) {
120+
logger.error("Error clearing storage in error boundary", storageError)
121+
rumInstance.recordError(storageError as Error)
122+
}
123+
124+
// Reset cleanup flag after a delay
125+
setTimeout(() => {
126+
this.isCleaningUp = false
127+
}, 5000)
128+
}
72129
}
73130
}
74131

0 commit comments

Comments
 (0)