From 207c80ad3eac4f68ada6b42a46c6fc7edf9643e2 Mon Sep 17 00:00:00 2001 From: Yury Bondarenko Date: Thu, 28 Aug 2025 15:26:07 +0700 Subject: [PATCH] Make sure authentication methods are retrieved if token is invalid but not expired --- src/app/core/auth/auth.actions.ts | 10 +++++++++- src/app/core/auth/auth.effects.spec.ts | 20 ++++++++++++++++---- src/app/core/auth/auth.effects.ts | 11 +++++++++-- 3 files changed, 34 insertions(+), 7 deletions(-) diff --git a/src/app/core/auth/auth.actions.ts b/src/app/core/auth/auth.actions.ts index 6bc4565682a..f6a6e3e46ac 100644 --- a/src/app/core/auth/auth.actions.ts +++ b/src/app/core/auth/auth.actions.ts @@ -68,8 +68,16 @@ export class AuthenticatedAction implements Action { public type: string = AuthActionTypes.AUTHENTICATED; payload: AuthTokenInfo; - constructor(token: AuthTokenInfo) { + /** + * Whether we should consider the given authentication info final. + * If the backend restarted we may have a token that hasn't expired yet, but it will be invalid anyway. + * In this case we'll have to check twice. + */ + checkAgain: boolean; + + constructor(token: AuthTokenInfo, checkAgain = false) { this.payload = token; + this.checkAgain = checkAgain; } } diff --git a/src/app/core/auth/auth.effects.spec.ts b/src/app/core/auth/auth.effects.spec.ts index 2e6ba917aae..ce9c3babb9a 100644 --- a/src/app/core/auth/auth.effects.spec.ts +++ b/src/app/core/auth/auth.effects.spec.ts @@ -131,7 +131,7 @@ describe('AuthEffects', () => { describe('when token is valid', () => { it('should return a AUTHENTICATED_SUCCESS action in response to a AUTHENTICATED action', () => { - actions = hot('--a-', { a: { type: AuthActionTypes.AUTHENTICATED, payload: token } }); + actions = hot('--a-', { a: new AuthenticatedAction(token) }); const expected = cold('--b-', { b: new AuthenticatedSuccessAction(true, token, EPersonMock._links.self.href) }); @@ -139,17 +139,29 @@ describe('AuthEffects', () => { }); }); - describe('when token is not valid', () => { + describe('when token is expired', () => { it('should return a AUTHENTICATED_ERROR action in response to a AUTHENTICATED action', () => { spyOn((authEffects as any).authService, 'authenticatedUser').and.returnValue(observableThrow(new Error('Message Error test'))); - actions = hot('--a-', { a: { type: AuthActionTypes.AUTHENTICATED, payload: token } }); + actions = hot('--a-', { a: new AuthenticatedAction(token) }); const expected = cold('--b-', { b: new AuthenticatedErrorAction(new Error('Message Error test')) }); expect(authEffects.authenticated$).toBeObservable(expected); }); }); + + describe('when token is not valid but also not expired (~ cookie)', () => { + it('should return a AUTHENTICATED_ERROR action in response to a AUTHENTICATED action', () => { + spyOn((authEffects as any).authService, 'authenticatedUser').and.returnValue(observableThrow(new Error('Message Error test'))); + + actions = hot('--a-', { a: new AuthenticatedAction(token, true) }); + + const expected = cold('--b-', { b: new CheckAuthenticationTokenCookieAction() }); + + expect(authEffects.authenticated$).toBeObservable(expected); + }); + }); }); describe('authenticatedSuccess$', () => { @@ -185,7 +197,7 @@ describe('AuthEffects', () => { actions = hot('--a-', { a: { type: AuthActionTypes.CHECK_AUTHENTICATION_TOKEN } }); - const expected = cold('--b-', { b: new AuthenticatedAction(token) }); + const expected = cold('--b-', { b: new AuthenticatedAction(token, true) }); expect(authEffects.checkToken$).toBeObservable(expected); }); diff --git a/src/app/core/auth/auth.effects.ts b/src/app/core/auth/auth.effects.ts index 281355b769e..cd446c5853d 100644 --- a/src/app/core/auth/auth.effects.ts +++ b/src/app/core/auth/auth.effects.ts @@ -88,7 +88,14 @@ export class AuthEffects { switchMap((action: AuthenticatedAction) => { return this.authService.authenticatedUser(action.payload).pipe( map((userHref: string) => new AuthenticatedSuccessAction((userHref !== null), action.payload, userHref)), - catchError((error) => observableOf(new AuthenticatedErrorAction(error))),); + catchError((error) => { + if (action.checkAgain) { + return observableOf(new CheckAuthenticationTokenCookieAction()); + } else { + return observableOf(new AuthenticatedErrorAction(error)); + } + }), + ); }) )); @@ -141,7 +148,7 @@ export class AuthEffects { public checkToken$: Observable = createEffect(() => this.actions$.pipe(ofType(AuthActionTypes.CHECK_AUTHENTICATION_TOKEN), switchMap(() => { return this.authService.hasValidAuthenticationToken().pipe( - map((token: AuthTokenInfo) => new AuthenticatedAction(token)), + map((token: AuthTokenInfo) => new AuthenticatedAction(token, true)), catchError((error) => observableOf(new CheckAuthenticationTokenCookieAction())) ); })