Skip to content

Commit 03eee3c

Browse files
committed
Fix error handling
1 parent 017c174 commit 03eee3c

File tree

1 file changed

+88
-22
lines changed
  • packages/react/src/components/presentation/SignIn/component-driven

1 file changed

+88
-22
lines changed

packages/react/src/components/presentation/SignIn/component-driven/SignIn.tsx

Lines changed: 88 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -198,13 +198,15 @@ const SignIn: FC<SignInProps> = ({className, size = 'medium', onSuccess, onError
198198
setFlowId(null);
199199
setIsFlowInitialized(false);
200200
sessionStorage.removeItem('asgardeo_session_data_key');
201+
// Reset refs to allow new flows to start properly
202+
oauthCodeProcessedRef.current = false;
201203
};
202204

203205
/**
204206
* Parse URL parameters used in flows.
205207
*/
206208
const getUrlParams = () => {
207-
const urlParams = new URL(window.location.href).searchParams;
209+
const urlParams = new URL(window?.location?.href ?? '').searchParams;
208210
return {
209211
code: urlParams.get('code'),
210212
error: urlParams.get('error'),
@@ -242,6 +244,7 @@ const SignIn: FC<SignInProps> = ({className, size = 'medium', onSuccess, onError
242244
* Clean up OAuth-related URL parameters from the browser URL.
243245
*/
244246
const cleanupOAuthUrlParams = (includeNonce = false): void => {
247+
if (!window?.location?.href) return;
245248
const url = new URL(window.location.href);
246249
url.searchParams.delete('error');
247250
url.searchParams.delete('error_description');
@@ -250,7 +253,19 @@ const SignIn: FC<SignInProps> = ({className, size = 'medium', onSuccess, onError
250253
if (includeNonce) {
251254
url.searchParams.delete('nonce');
252255
}
253-
window.history.replaceState({}, '', url.toString());
256+
window?.history?.replaceState({}, '', url.toString());
257+
};
258+
259+
/**
260+
* Clean up flow-related URL parameters (flowId, sessionDataKey) from the browser URL.
261+
* Used after flowId is set in state to prevent using invalidated flowId from URL.
262+
*/
263+
const cleanupFlowUrlParams = (): void => {
264+
if (!window?.location?.href) return;
265+
const url = new URL(window.location.href);
266+
url.searchParams.delete('flowId');
267+
url.searchParams.delete('sessionDataKey');
268+
window?.history?.replaceState({}, '', url.toString());
254269
};
255270

256271
/**
@@ -266,16 +281,17 @@ const SignIn: FC<SignInProps> = ({className, size = 'medium', onSuccess, onError
266281
'SIGN_IN_ERROR',
267282
'react',
268283
);
269-
setFlowError(err);
270-
onError?.(err);
284+
setError(err);
271285
cleanupOAuthUrlParams(true);
272286
};
273287

274288
/**
275289
* Set error state and call onError callback.
290+
* Ensures isFlowInitialized is true so errors can be displayed in the UI.
276291
*/
277292
const setError = (error: Error): void => {
278293
setFlowError(error);
294+
setIsFlowInitialized(true);
279295
onError?.(error);
280296
};
281297

@@ -286,7 +302,7 @@ const SignIn: FC<SignInProps> = ({className, size = 'medium', onSuccess, onError
286302
if (response.type === EmbeddedSignInFlowTypeV2.Redirection) {
287303
const redirectURL = (response.data as any)?.redirectURL || (response as any)?.redirectURL;
288304

289-
if (redirectURL) {
305+
if (redirectURL && window?.location) {
290306
if (response.flowId) {
291307
setFlowId(response.flowId);
292308
}
@@ -321,21 +337,44 @@ const SignIn: FC<SignInProps> = ({className, size = 'medium', onSuccess, onError
321337
storedFlowId,
322338
);
323339

340+
// Only process code if we have a valid flowId to use
324341
if (flowIdFromState) {
325342
setFlowId(flowIdFromState);
326343
setIsFlowInitialized(true);
327344
initializationAttemptedRef.current = true;
345+
// Clean up flowId from URL after setting it in state
346+
cleanupFlowUrlParams();
347+
} else {
348+
console.warn('[SignIn] OAuth code in URL but no valid flowId found. Cleaning up stale OAuth parameters.');
349+
cleanupOAuthUrlParams(true);
328350
}
329351
return;
330352
}
331353

354+
// If flowId is in URL or sessionStorage but no code and no active flow state
355+
if ((urlParams.flowId || storedFlowId) && !urlParams.code && !currentFlowId) {
356+
console.warn(
357+
'[SignIn] FlowId in URL/sessionStorage but no active flow state detected. '
358+
);
359+
setFlowId(null);
360+
sessionStorage.removeItem('asgardeo_flow_id');
361+
cleanupFlowUrlParams();
362+
// Continue to initialize with applicationId instead
363+
}
364+
332365
if (
333366
isInitialized &&
334367
!isLoading &&
335368
!isFlowInitialized &&
336369
!initializationAttemptedRef.current &&
337370
!currentFlowId
338371
) {
372+
// Clean up any stale OAuth parameters before starting a new flow
373+
const urlParams = getUrlParams();
374+
if (urlParams.code || urlParams.state) {
375+
console.debug('[SignIn] Cleaning up stale OAuth parameters before starting new flow');
376+
cleanupOAuthUrlParams(true);
377+
}
339378
initializationAttemptedRef.current = true;
340379
initializeFlow();
341380
}
@@ -348,6 +387,9 @@ const SignIn: FC<SignInProps> = ({className, size = 'medium', onSuccess, onError
348387
const initializeFlow = async (): Promise<void> => {
349388
const urlParams = getUrlParams();
350389

390+
// Reset OAuth code processed ref when starting a new flow
391+
oauthCodeProcessedRef.current = false;
392+
351393
handleSessionDataKey(urlParams.sessionDataKey);
352394

353395
const effectiveApplicationId = applicationId || urlParams.applicationId;
@@ -388,18 +430,25 @@ const SignIn: FC<SignInProps> = ({className, size = 'medium', onSuccess, onError
388430
setFlowId(flowId);
389431
setComponents(components);
390432
setIsFlowInitialized(true);
433+
// Clean up flowId from URL after setting it in state
434+
cleanupFlowUrlParams();
391435
}
392436
} catch (error) {
393437
const err = error as Error;
394-
setError(err);
395438
clearFlowState();
396-
initializationAttemptedRef.current = false;
397439

398-
throw new AsgardeoRuntimeError(
399-
`Failed to initialize authentication flow: ${error instanceof Error ? error.message : String(error)}`,
440+
// Extract error message
441+
const errorMessage = err instanceof Error ? err.message : String(err);
442+
443+
// Create error with backend message
444+
const displayError = new AsgardeoRuntimeError(
445+
errorMessage,
400446
'SIGN_IN_ERROR',
401447
'react',
402448
);
449+
setError(displayError);
450+
initializationAttemptedRef.current = false;
451+
return;
403452
}
404453
};
405454

@@ -422,7 +471,6 @@ const SignIn: FC<SignInProps> = ({className, size = 'medium', onSuccess, onError
422471
setIsSubmitting(true);
423472
setFlowError(null);
424473

425-
// Use effectiveFlowId - either from payload or currentFlowId
426474
const response: EmbeddedSignInFlowResponseV2 = await signIn({
427475
flowId: effectiveFlowId,
428476
...payload,
@@ -438,38 +486,46 @@ const SignIn: FC<SignInProps> = ({className, size = 'medium', onSuccess, onError
438486
if (response.flowStatus === EmbeddedSignInFlowStatusV2.Error) {
439487
console.error('[SignIn] Flow returned Error status, clearing flow state');
440488
clearFlowState();
489+
// Extract failureReason from response if available
490+
const failureReason = (response as any)?.failureReason;
491+
const errorMessage = failureReason || 'Authentication flow failed. Please try again.';
441492
const err = new AsgardeoRuntimeError(
442-
'Authentication flow failed. Please try again.',
493+
errorMessage,
443494
'SIGN_IN_ERROR',
444495
'react',
445496
);
446497
setError(err);
498+
cleanupFlowUrlParams();
447499
return;
448500
}
449501

450502
if (response.flowStatus === EmbeddedSignInFlowStatusV2.Complete) {
451503
// Get redirectUrl from response (from /oauth2/authorize) or fall back to afterSignInUrl
452-
const redirectUrl = (response as any).redirectUrl || (response as any).redirect_uri;
504+
const redirectUrl = (response as any)?.redirectUrl || (response as any)?.redirect_uri;
505+
const finalRedirectUrl = redirectUrl || afterSignInUrl;
506+
507+
// Clear submitting state before redirect
508+
setIsSubmitting(false);
453509

454510
// Clear all OAuth-related storage on successful completion
455511
setFlowId(null);
456512
setIsFlowInitialized(false);
457-
if (redirectUrl) {
458-
sessionStorage.removeItem('asgardeo_session_data_key');
459-
}
513+
sessionStorage.removeItem('asgardeo_flow_id');
514+
sessionStorage.removeItem('asgardeo_session_data_key');
460515

516+
// Clean up OAuth URL params before redirect
461517
cleanupOAuthUrlParams(true);
462518

463-
const finalRedirectUrl = redirectUrl || afterSignInUrl;
464-
465519
onSuccess &&
466520
onSuccess({
467521
redirectUrl: finalRedirectUrl,
468-
...response.data,
522+
...(response.data || {}),
469523
});
470524

471-
if (finalRedirectUrl) {
525+
if (finalRedirectUrl && window?.location) {
472526
window.location.href = finalRedirectUrl;
527+
} else {
528+
console.warn('[SignIn] Flow completed but no redirect URL available');
473529
}
474530

475531
return;
@@ -479,16 +535,23 @@ const SignIn: FC<SignInProps> = ({className, size = 'medium', onSuccess, onError
479535
if (flowId && components) {
480536
setFlowId(flowId);
481537
setComponents(components);
538+
// Clean up flowId from URL after setting it in state
539+
cleanupFlowUrlParams();
482540
}
483541
} catch (error) {
484542
const err = error as Error;
485-
setError(err);
486543
clearFlowState();
487-
throw new AsgardeoRuntimeError(
488-
`Failed to submit authentication flow: ${error instanceof Error ? error.message : String(error)}`,
544+
545+
// Extract error message
546+
const errorMessage = err instanceof Error ? err.message : String(err);
547+
548+
const displayError = new AsgardeoRuntimeError(
549+
errorMessage,
489550
'SIGN_IN_ERROR',
490551
'react',
491552
);
553+
setError(displayError);
554+
return;
492555
} finally {
493556
setIsSubmitting(false);
494557
}
@@ -502,6 +565,9 @@ const SignIn: FC<SignInProps> = ({className, size = 'medium', onSuccess, onError
502565
setError(error);
503566
};
504567

568+
/**
569+
* Handle OAuth code processing from external OAuth providers.
570+
*/
505571
useEffect(() => {
506572
const urlParams = getUrlParams();
507573
const storedFlowId = sessionStorage.getItem('asgardeo_flow_id');

0 commit comments

Comments
 (0)