From 81b51a16eaebf9170c58a4c35c0c9aa23625e3f3 Mon Sep 17 00:00:00 2001 From: Peter Chapman Date: Mon, 6 Jul 2026 15:49:52 +1200 Subject: [PATCH] SF-3816 Fix error being logged when a user has an invalid token --- .../src/assets/i18n/checking_en.json | 2 ++ .../src/xforge-common/auth.service.spec.ts | 25 +++++++++++++------ .../src/xforge-common/auth.service.ts | 8 ++++++ 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/SIL.XForge.Scripture/ClientApp/src/assets/i18n/checking_en.json b/src/SIL.XForge.Scripture/ClientApp/src/assets/i18n/checking_en.json index de5ce5e6594..6400d0064c3 100644 --- a/src/SIL.XForge.Scripture/ClientApp/src/assets/i18n/checking_en.json +++ b/src/SIL.XForge.Scripture/ClientApp/src/assets/i18n/checking_en.json @@ -334,6 +334,8 @@ "error_occurred_login": "An error occurred during login.", "failed_to_retrieve_suggestions": "Couldn't get translation suggestions.", "failed_to_update_avatar": "Your avatar image could not be updated.", + "login": "Log in", + "login_expired": "Your log in has expired. Please log in again.", "suggestion_engine_requires_retrain": "Couldn't get translation suggestions. Please retrain the suggestions on the Overview page.", "go_to_retrain": "Go to Overview", "try_again": "Try Again" diff --git a/src/SIL.XForge.Scripture/ClientApp/src/xforge-common/auth.service.spec.ts b/src/SIL.XForge.Scripture/ClientApp/src/xforge-common/auth.service.spec.ts index f5b1474289a..efca2dd9a6d 100644 --- a/src/SIL.XForge.Scripture/ClientApp/src/xforge-common/auth.service.spec.ts +++ b/src/SIL.XForge.Scripture/ClientApp/src/xforge-common/auth.service.spec.ts @@ -524,6 +524,17 @@ describe('AuthService', () => { mockedConsole.verify(); })); + it('should display prompt to log in again when the token grant is invalid', fakeAsync(() => { + const callback = (env: TestEnvironment): void => { + env.setInvalidGrantResponse(); + mockedConsole.expectAndHide(/Unknown or invalid refresh token/); + }; + const env = new TestEnvironment({ isOnline: true, isNewlyLoggedIn: true, callback }); + expect(env.isLoggedIn).toBe(false); + verify(mockedWebAuth.getTokenSilently(anything())).once(); + verify(mockedDialogService.message(anything(), anything())).once(); + })); + it('should redirect to url after successful login', fakeAsync(() => { const env = new TestEnvironment({ isOnline: true, @@ -943,6 +954,13 @@ class TestEnvironment { when(mockedWebAuth.getTokenSilently(anything())).thenResolve(this.auth0Response!.token); } + setInvalidGrantResponse(): void { + const grantError = new GenericError('invalid_grant', 'Unknown or invalid refresh token.'); + when(mockedWebAuth.getTokenSilently()).thenThrow(grantError); + when(mockedWebAuth.getTokenSilently(anything())).thenThrow(grantError); + when(mockedWebAuth.getIdTokenClaims()).thenThrow(grantError); + } + setLoginRequiredResponse(): void { const loginError = new GenericError('login_required', 'Not logged in'); when(mockedWebAuth.getTokenSilently()).thenThrow(loginError); @@ -950,13 +968,6 @@ class TestEnvironment { when(mockedWebAuth.getIdTokenClaims()).thenThrow(loginError); } - setMissingTokenResponse(): void { - const tokenError = new GenericError('missing_refresh_token', 'Invalid token'); - when(mockedWebAuth.getTokenSilently()).thenThrow(tokenError); - when(mockedWebAuth.getTokenSilently(anything())).thenThrow(tokenError); - when(mockedWebAuth.getIdTokenClaims()).thenThrow(tokenError); - } - setOnline(isOnline: boolean = true): void { this.testOnlineStatusService.setIsOnline(isOnline); } diff --git a/src/SIL.XForge.Scripture/ClientApp/src/xforge-common/auth.service.ts b/src/SIL.XForge.Scripture/ClientApp/src/xforge-common/auth.service.ts index 4c5a9f02281..cd38d683fb4 100644 --- a/src/SIL.XForge.Scripture/ClientApp/src/xforge-common/auth.service.ts +++ b/src/SIL.XForge.Scripture/ClientApp/src/xforge-common/auth.service.ts @@ -535,6 +535,14 @@ export class AuthService { private async handleLoginError(method: string, error: unknown): Promise { console.error(error); + + // Handle invalid_grant from Auth0 + if (typeof error === 'object' && error !== null && 'error' in error && error.error === 'invalid_grant') { + await this.dialogService.message('error_messages.login_expired', 'error_messages.login'); + return; + } + + // Unknown log in error this.reportingService.silentError(`Error occurred in ${method}`, ErrorReportingService.normalizeError(error)); await this.dialogService.message('error_messages.error_occurred_login', 'error_messages.try_again'); }