From 76e4cbfc3b78ff4134ce1be1366c32ffc78653d8 Mon Sep 17 00:00:00 2001 From: aervin_ Date: Wed, 20 Nov 2024 11:36:58 -0600 Subject: [PATCH 1/5] NAS-132648: adds query param handling to signin.store and auth.service. forward query params on redirect in auth-guard.service --- src/app/pages/signin/store/signin.store.ts | 6 +++++- src/app/services/auth/auth-guard.service.ts | 8 ++++---- src/app/services/auth/auth.service.ts | 10 ++++++++++ src/main.ts | 3 ++- 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/src/app/pages/signin/store/signin.store.ts b/src/app/pages/signin/store/signin.store.ts index 432f18638ce..dcce548e30a 100644 --- a/src/app/pages/signin/store/signin.store.ts +++ b/src/app/pages/signin/store/signin.store.ts @@ -1,6 +1,6 @@ import { Inject, Injectable } from '@angular/core'; import { MatSnackBar } from '@angular/material/snack-bar'; -import { Router } from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; import { ComponentStore } from '@ngrx/component-store'; import { Actions, ofType } from '@ngrx/effects'; @@ -81,6 +81,7 @@ export class SigninStore extends ComponentStore { private authService: AuthService, private updateService: UpdateService, private actions$: Actions, + private activatedRoute: ActivatedRoute, @Inject(WINDOW) private window: Window, ) { super(initialState); @@ -240,6 +241,9 @@ export class SigninStore extends ComponentStore { } private handleLoginWithToken(): Observable { + this.authService.setQueryTokenIfExists( + this.activatedRoute.snapshot.queryParamMap.get('token'), + ); return this.tokenLastUsedService.isTokenWithinTimeline$.pipe(take(1)).pipe( filter((isTokenWithinTimeline) => { if (!isTokenWithinTimeline) { diff --git a/src/app/services/auth/auth-guard.service.ts b/src/app/services/auth/auth-guard.service.ts index 07416920502..028c2dfe81a 100644 --- a/src/app/services/auth/auth-guard.service.ts +++ b/src/app/services/auth/auth-guard.service.ts @@ -1,5 +1,5 @@ import { Inject, Injectable } from '@angular/core'; -import { ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router'; +import { ActivatedRouteSnapshot, Router, RouterStateSnapshot } from '@angular/router'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; import { WINDOW } from 'app/helpers/window.helper'; import { AuthService } from 'app/services/auth/auth.service'; @@ -20,13 +20,13 @@ export class AuthGuardService { }); } - canActivate(_: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { + canActivate({ queryParams }: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean { if (this.isAuthenticated) { return true; } - this.window.sessionStorage.setItem('redirectUrl', state.url); - this.router.navigate(['/signin']); + this.window.sessionStorage.setItem('redirectUrl', state.url.split('?')[0]); + this.router.navigate(['/signin'], { queryParams }); return false; } diff --git a/src/app/services/auth/auth.service.ts b/src/app/services/auth/auth.service.ts index 7e4a3e2ae22..6cc691da5fe 100644 --- a/src/app/services/auth/auth.service.ts +++ b/src/app/services/auth/auth.service.ts @@ -131,6 +131,16 @@ export class AuthService { ); } + setQueryTokenIfExists(token: string | null): void { + if (!token || this.window.location.protocol !== 'https:') { + return; + } + + this.token = token; + this.tokenLastUsedService.updateTokenLastUsed(); + this.latestTokenGenerated$.next(token); + } + loginWithToken(): Observable { if (!this.token) { return of(LoginResult.NoToken); diff --git a/src/main.ts b/src/main.ts index 6272c7019bd..5645a86ec9e 100644 --- a/src/main.ts +++ b/src/main.ts @@ -11,6 +11,7 @@ import { provideRouter, PreloadAllModules, withComponentInputBinding, + withDebugTracing, } from '@angular/router'; import { provideEffects } from '@ngrx/effects'; import { provideRouterStore } from '@ngrx/router-store'; @@ -121,6 +122,6 @@ bootstrapApplication(AppComponent, { provideCharts(withDefaultRegisterables()), provideHttpClient(withInterceptorsFromDi()), provideAnimations(), - provideRouter(rootRoutes, withPreloading(PreloadAllModules), withComponentInputBinding()), + provideRouter(rootRoutes, withPreloading(PreloadAllModules), withComponentInputBinding(), withDebugTracing()), ], }); From b0fbbc64ddbbb3b279ff7cac1c9bc98fde3c137a Mon Sep 17 00:00:00 2001 From: aervin_ Date: Wed, 20 Nov 2024 13:02:26 -0600 Subject: [PATCH 2/5] NAS-132648: patch signin.store tests for now --- src/app/core/testing/utils/empty-auth.service.ts | 1 + src/app/pages/signin/store/signin.store.spec.ts | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/app/core/testing/utils/empty-auth.service.ts b/src/app/core/testing/utils/empty-auth.service.ts index caaa144c20f..14f350f1145 100644 --- a/src/app/core/testing/utils/empty-auth.service.ts +++ b/src/app/core/testing/utils/empty-auth.service.ts @@ -15,4 +15,5 @@ export class EmptyAuthService { logout = getMissingInjectionErrorFactory(AuthService.name); refreshUser = getMissingInjectionErrorFactory(AuthService.name); loginWithToken = getMissingInjectionErrorFactory(AuthService.name); + setQueryTokenIfExists = getMissingInjectionErrorFactory(AuthService.name); } diff --git a/src/app/pages/signin/store/signin.store.spec.ts b/src/app/pages/signin/store/signin.store.spec.ts index e42682b9d1d..6598e645b1c 100644 --- a/src/app/pages/signin/store/signin.store.spec.ts +++ b/src/app/pages/signin/store/signin.store.spec.ts @@ -1,10 +1,10 @@ -import { Router } from '@angular/router'; +import { ActivatedRoute, Router } from '@angular/router'; import { createServiceFactory, SpectatorService } from '@ngneat/spectator'; import { mockProvider } from '@ngneat/spectator/jest'; import { BehaviorSubject, firstValueFrom, of } from 'rxjs'; import { MockApiService } from 'app/core/testing/classes/mock-api.service'; import { getTestScheduler } from 'app/core/testing/utils/get-test-scheduler.utils'; -import { mockCall, mockApi } from 'app/core/testing/utils/mock-api.utils'; +import { mockApi, mockCall } from 'app/core/testing/utils/mock-api.utils'; import { FailoverDisabledReason } from 'app/enums/failover-disabled-reason.enum'; import { FailoverStatus } from 'app/enums/failover-status.enum'; import { LoginResult } from 'app/enums/login-result.enum'; @@ -66,6 +66,7 @@ describe('SigninStore', () => { }, }, }, + mockProvider(ActivatedRoute, { snapshot: { queryParamMap: { get: jest.fn() } } }), ], }); @@ -82,6 +83,7 @@ describe('SigninStore', () => { }); jest.spyOn(authService, 'loginWithToken').mockReturnValue(of(LoginResult.Success)); jest.spyOn(authService, 'clearAuthToken').mockReturnValue(null); + jest.spyOn(authService, 'setQueryTokenIfExists').mockReturnValue(undefined) }); describe('selectors', () => { From 8570662906ea20f895f9d0b34f623c04c2c81dc2 Mon Sep 17 00:00:00 2001 From: aervin_ Date: Fri, 22 Nov 2024 15:07:37 -0600 Subject: [PATCH 3/5] NAS-132648: flag in websocket-handler.service to determine if a socket connection to the NAS has opened at least once. adds queryToken to signin.store state. --- .../core/guards/websocket-connection.guard.ts | 2 +- .../core/testing/utils/empty-auth.service.ts | 2 +- .../pages/signin/store/signin.store.spec.ts | 24 ++++++++++++++++--- src/app/pages/signin/store/signin.store.ts | 22 ++++++++++++----- src/app/services/auth/auth.service.ts | 4 +--- .../websocket/websocket-handler.service.ts | 6 +++++ 6 files changed, 46 insertions(+), 14 deletions(-) diff --git a/src/app/core/guards/websocket-connection.guard.ts b/src/app/core/guards/websocket-connection.guard.ts index 74ac29dac8f..8db95e20ff1 100644 --- a/src/app/core/guards/websocket-connection.guard.ts +++ b/src/app/core/guards/websocket-connection.guard.ts @@ -18,7 +18,7 @@ export class WebSocketConnectionGuard { private translate: TranslateService, ) { this.wsManager.isClosed$.pipe(untilDestroyed(this)).subscribe((isClosed) => { - if (isClosed) { + if (isClosed && this.wsManager.hasOpenedOnce) { this.resetUi(); // TODO: Test why manually changing close status is needed // Test a shutdown function to see how UI acts when this isn't done diff --git a/src/app/core/testing/utils/empty-auth.service.ts b/src/app/core/testing/utils/empty-auth.service.ts index 14f350f1145..e1fdec923f5 100644 --- a/src/app/core/testing/utils/empty-auth.service.ts +++ b/src/app/core/testing/utils/empty-auth.service.ts @@ -15,5 +15,5 @@ export class EmptyAuthService { logout = getMissingInjectionErrorFactory(AuthService.name); refreshUser = getMissingInjectionErrorFactory(AuthService.name); loginWithToken = getMissingInjectionErrorFactory(AuthService.name); - setQueryTokenIfExists = getMissingInjectionErrorFactory(AuthService.name); + setQueryToken = getMissingInjectionErrorFactory(AuthService.name); } diff --git a/src/app/pages/signin/store/signin.store.spec.ts b/src/app/pages/signin/store/signin.store.spec.ts index 6598e645b1c..7306702911a 100644 --- a/src/app/pages/signin/store/signin.store.spec.ts +++ b/src/app/pages/signin/store/signin.store.spec.ts @@ -66,7 +66,7 @@ describe('SigninStore', () => { }, }, }, - mockProvider(ActivatedRoute, { snapshot: { queryParamMap: { get: jest.fn() } } }), + mockProvider(ActivatedRoute, { snapshot: { queryParamMap: { get: jest.fn(() => null) } } }), ], }); @@ -83,7 +83,7 @@ describe('SigninStore', () => { }); jest.spyOn(authService, 'loginWithToken').mockReturnValue(of(LoginResult.Success)); jest.spyOn(authService, 'clearAuthToken').mockReturnValue(null); - jest.spyOn(authService, 'setQueryTokenIfExists').mockReturnValue(undefined) + jest.spyOn(authService, 'setQueryToken').mockReturnValue(undefined); }); describe('selectors', () => { @@ -97,6 +97,7 @@ describe('SigninStore', () => { wasAdminSet: true, isLoading: false, loginBanner: '', + queryToken: null as string | null, }; beforeEach(() => { spectator.service.setState(initialState); @@ -144,6 +145,7 @@ describe('SigninStore', () => { failover: { status: FailoverStatus.Single, }, + queryToken: null, }); }); @@ -160,6 +162,7 @@ describe('SigninStore', () => { failover: { status: FailoverStatus.Single, }, + queryToken: null, }); }); @@ -179,6 +182,7 @@ describe('SigninStore', () => { ips: ['123.23.44.54'], status: FailoverStatus.Master, }, + queryToken: null, }); }); @@ -189,7 +193,7 @@ describe('SigninStore', () => { expect(spectator.inject(Router).navigateByUrl).toHaveBeenCalledWith('/dashboard'); }); - it('should not call "loginWithToken" if token is not within timeline and clear auth token', () => { + it('should not call "loginWithToken" if token is not within timeline and clear auth token and queryToken is null', () => { isTokenWithinTimeline$.next(false); spectator.service.init(); @@ -197,6 +201,18 @@ describe('SigninStore', () => { expect(authService.loginWithToken).not.toHaveBeenCalled(); expect(spectator.inject(Router).navigateByUrl).not.toHaveBeenCalled(); }); + + it('should call "loginWithToken" if queryToken is not null', async () => { + isTokenWithinTimeline$.next(false); + const token = 'token'; + const activatedRoute = spectator.inject(ActivatedRoute); + jest.spyOn(activatedRoute.snapshot.queryParamMap, 'get').mockImplementationOnce(() => token); + spectator.service.init(); + const state = await firstValueFrom(spectator.service.state$); + expect(state.queryToken).toBe(token); + expect(authService.setQueryToken).toHaveBeenCalledWith(token); + expect(authService.loginWithToken).toHaveBeenCalled(); + }); }); describe('init - failover subscriptions', () => { @@ -231,6 +247,7 @@ describe('SigninStore', () => { ips: ['123.23.44.54'], status: FailoverStatus.Importing, }, + queryToken: null, }); }); @@ -262,6 +279,7 @@ describe('SigninStore', () => { ips: ['123.23.44.54'], status: FailoverStatus.Master, }, + queryToken: null, }, }); }); diff --git a/src/app/pages/signin/store/signin.store.ts b/src/app/pages/signin/store/signin.store.ts index dcce548e30a..3c8c2d9e7e7 100644 --- a/src/app/pages/signin/store/signin.store.ts +++ b/src/app/pages/signin/store/signin.store.ts @@ -35,6 +35,7 @@ interface SigninState { disabledReasons?: FailoverDisabledReason[]; }; loginBanner: string; + queryToken: string | null; } const initialState: SigninState = { @@ -42,6 +43,7 @@ const initialState: SigninState = { wasAdminSet: true, failover: null, loginBanner: null, + queryToken: null, }; @UntilDestroy() @@ -51,6 +53,7 @@ export class SigninStore extends ComponentStore { wasAdminSet$ = this.select((state) => state.wasAdminSet); failover$ = this.select((state) => state.failover); isLoading$ = this.select((state) => state.isLoading); + queryToken$ = this.select((state) => state.queryToken); failoverAllowsLogin$ = this.select((state) => { return [FailoverStatus.Single, FailoverStatus.Master].includes(state.failover?.status); }); @@ -88,9 +91,13 @@ export class SigninStore extends ComponentStore { } setLoadingState = this.updater((state, isLoading: boolean) => ({ ...state, isLoading })); + setQueryToken = this.updater((state, queryToken: string | null) => ({ ...state, queryToken })); init = this.effect((trigger$: Observable) => trigger$.pipe( - tap(() => this.setLoadingState(true)), + tap(() => { + this.setLoadingState(true); + this.setQueryToken(this.activatedRoute.snapshot.queryParamMap.get('token')); + }), switchMap(() => forkJoin([ this.checkIfAdminPasswordSet(), this.checkForLoginBanner(), @@ -241,11 +248,14 @@ export class SigninStore extends ComponentStore { } private handleLoginWithToken(): Observable { - this.authService.setQueryTokenIfExists( - this.activatedRoute.snapshot.queryParamMap.get('token'), - ); - return this.tokenLastUsedService.isTokenWithinTimeline$.pipe(take(1)).pipe( - filter((isTokenWithinTimeline) => { + return combineLatest([this.tokenLastUsedService.isTokenWithinTimeline$, this.queryToken$]).pipe( + take(1), + tap(([_, queryToken]) => this.authService.setQueryToken(queryToken)), + filter(([isTokenWithinTimeline, queryToken]) => { + if (queryToken) { + return true; + } + if (!isTokenWithinTimeline) { this.authService.clearAuthToken(); } diff --git a/src/app/services/auth/auth.service.ts b/src/app/services/auth/auth.service.ts index 6cc691da5fe..2b88fde5acd 100644 --- a/src/app/services/auth/auth.service.ts +++ b/src/app/services/auth/auth.service.ts @@ -131,14 +131,12 @@ export class AuthService { ); } - setQueryTokenIfExists(token: string | null): void { + setQueryToken(token: string | null): void { if (!token || this.window.location.protocol !== 'https:') { return; } this.token = token; - this.tokenLastUsedService.updateTokenLastUsed(); - this.latestTokenGenerated$.next(token); } loginWithToken(): Observable { diff --git a/src/app/services/websocket/websocket-handler.service.ts b/src/app/services/websocket/websocket-handler.service.ts index 37cb7e83dae..e2a1b47dce8 100644 --- a/src/app/services/websocket/websocket-handler.service.ts +++ b/src/app/services/websocket/websocket-handler.service.ts @@ -67,6 +67,11 @@ export class WebSocketHandlerService { return this.wsConnection.stream$ as Observable; } + private hasOpened = false; + get hasOpenedOnce(): boolean { + return this.hasOpened; + } + private readonly triggerNextCall$ = new Subject(); private activeCalls = 0; private readonly queuedCalls: { id: string; [key: string]: unknown }[] = []; @@ -228,6 +233,7 @@ export class WebSocketHandlerService { } private onOpen(): void { + this.hasOpened = true; if (this.reconnectTimerSubscription) { this.wsConnection.close(); return; From f8a50d1a10e5f556df027a6dbefb6d4b1f016b19 Mon Sep 17 00:00:00 2001 From: aervin_ Date: Mon, 25 Nov 2024 08:03:02 -0600 Subject: [PATCH 4/5] NAS-132648: remove router debugging --- src/main.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/main.ts b/src/main.ts index 5645a86ec9e..9e579f5025c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -7,11 +7,7 @@ import { MAT_SNACK_BAR_DEFAULT_OPTIONS, MatSnackBarConfig } from '@angular/mater import { BrowserModule, bootstrapApplication } from '@angular/platform-browser'; import { provideAnimations } from '@angular/platform-browser/animations'; import { - withPreloading, - provideRouter, - PreloadAllModules, - withComponentInputBinding, - withDebugTracing, + withPreloading, provideRouter, PreloadAllModules, withComponentInputBinding, } from '@angular/router'; import { provideEffects } from '@ngrx/effects'; import { provideRouterStore } from '@ngrx/router-store'; @@ -122,6 +118,6 @@ bootstrapApplication(AppComponent, { provideCharts(withDefaultRegisterables()), provideHttpClient(withInterceptorsFromDi()), provideAnimations(), - provideRouter(rootRoutes, withPreloading(PreloadAllModules), withComponentInputBinding(), withDebugTracing()), + provideRouter(rootRoutes, withPreloading(PreloadAllModules), withComponentInputBinding()), ], }); From 911e1d47e897a2e72ab47be048ef9f8c480c2fea Mon Sep 17 00:00:00 2001 From: aervin_ Date: Tue, 26 Nov 2024 10:42:09 -0600 Subject: [PATCH 5/5] NAS-132648: add method for handling query token login. remove queryToken from signin.store state. --- .../pages/signin/store/signin.store.spec.ts | 10 +--- src/app/pages/signin/store/signin.store.ts | 57 +++++++++++-------- 2 files changed, 35 insertions(+), 32 deletions(-) diff --git a/src/app/pages/signin/store/signin.store.spec.ts b/src/app/pages/signin/store/signin.store.spec.ts index 7306702911a..1616a85b3c4 100644 --- a/src/app/pages/signin/store/signin.store.spec.ts +++ b/src/app/pages/signin/store/signin.store.spec.ts @@ -97,7 +97,6 @@ describe('SigninStore', () => { wasAdminSet: true, isLoading: false, loginBanner: '', - queryToken: null as string | null, }; beforeEach(() => { spectator.service.setState(initialState); @@ -145,7 +144,6 @@ describe('SigninStore', () => { failover: { status: FailoverStatus.Single, }, - queryToken: null, }); }); @@ -162,7 +160,6 @@ describe('SigninStore', () => { failover: { status: FailoverStatus.Single, }, - queryToken: null, }); }); @@ -182,7 +179,6 @@ describe('SigninStore', () => { ips: ['123.23.44.54'], status: FailoverStatus.Master, }, - queryToken: null, }); }); @@ -202,14 +198,12 @@ describe('SigninStore', () => { expect(spectator.inject(Router).navigateByUrl).not.toHaveBeenCalled(); }); - it('should call "loginWithToken" if queryToken is not null', async () => { + it('should call "loginWithToken" if queryToken is not null', () => { isTokenWithinTimeline$.next(false); const token = 'token'; const activatedRoute = spectator.inject(ActivatedRoute); jest.spyOn(activatedRoute.snapshot.queryParamMap, 'get').mockImplementationOnce(() => token); spectator.service.init(); - const state = await firstValueFrom(spectator.service.state$); - expect(state.queryToken).toBe(token); expect(authService.setQueryToken).toHaveBeenCalledWith(token); expect(authService.loginWithToken).toHaveBeenCalled(); }); @@ -247,7 +241,6 @@ describe('SigninStore', () => { ips: ['123.23.44.54'], status: FailoverStatus.Importing, }, - queryToken: null, }); }); @@ -279,7 +272,6 @@ describe('SigninStore', () => { ips: ['123.23.44.54'], status: FailoverStatus.Master, }, - queryToken: null, }, }); }); diff --git a/src/app/pages/signin/store/signin.store.ts b/src/app/pages/signin/store/signin.store.ts index 3c8c2d9e7e7..18862b0d220 100644 --- a/src/app/pages/signin/store/signin.store.ts +++ b/src/app/pages/signin/store/signin.store.ts @@ -35,7 +35,6 @@ interface SigninState { disabledReasons?: FailoverDisabledReason[]; }; loginBanner: string; - queryToken: string | null; } const initialState: SigninState = { @@ -43,7 +42,6 @@ const initialState: SigninState = { wasAdminSet: true, failover: null, loginBanner: null, - queryToken: null, }; @UntilDestroy() @@ -53,7 +51,6 @@ export class SigninStore extends ComponentStore { wasAdminSet$ = this.select((state) => state.wasAdminSet); failover$ = this.select((state) => state.failover); isLoading$ = this.select((state) => state.isLoading); - queryToken$ = this.select((state) => state.queryToken); failoverAllowsLogin$ = this.select((state) => { return [FailoverStatus.Single, FailoverStatus.Master].includes(state.failover?.status); }); @@ -71,6 +68,14 @@ export class SigninStore extends ComponentStore { private failoverStatusSubscription: Subscription; private disabledReasonsSubscription: Subscription; + private handleLoginResult = (loginResult: LoginResult): void => { + if (loginResult !== LoginResult.Success) { + this.authService.clearAuthToken(); + } else { + this.handleSuccessfulLogin(); + } + }; + constructor( private api: ApiService, private translate: TranslateService, @@ -91,13 +96,9 @@ export class SigninStore extends ComponentStore { } setLoadingState = this.updater((state, isLoading: boolean) => ({ ...state, isLoading })); - setQueryToken = this.updater((state, queryToken: string | null) => ({ ...state, queryToken })); init = this.effect((trigger$: Observable) => trigger$.pipe( - tap(() => { - this.setLoadingState(true); - this.setQueryToken(this.activatedRoute.snapshot.queryParamMap.get('token')); - }), + tap(() => this.setLoadingState(true)), switchMap(() => forkJoin([ this.checkIfAdminPasswordSet(), this.checkForLoginBanner(), @@ -105,7 +106,14 @@ export class SigninStore extends ComponentStore { this.updateService.hardRefreshIfNeeded(), ])), tap(() => this.setLoadingState(false)), - switchMap(() => this.handleLoginWithToken()), + switchMap(() => { + const queryToken = this.activatedRoute.snapshot.queryParamMap.get('token'); + if (queryToken) { + return this.handleLoginWithQueryToken(queryToken); + } + + return this.handleLoginWithToken(); + }), )); handleSuccessfulLogin = this.effect((trigger$: Observable) => trigger$.pipe( @@ -247,15 +255,24 @@ export class SigninStore extends ComponentStore { .subscribe((event) => this.setFailoverDisabledReasons(event.disabled_reasons)); } + private handleLoginWithQueryToken(token: string): Observable { + this.authService.setQueryToken(token); + + return this.authService.loginWithToken().pipe( + tap(this.handleLoginResult.bind(this)), + tapResponse( + () => {}, + (error: unknown) => { + this.dialogService.error(this.errorHandler.parseError(error)); + }, + ), + ); + } + private handleLoginWithToken(): Observable { - return combineLatest([this.tokenLastUsedService.isTokenWithinTimeline$, this.queryToken$]).pipe( + return this.tokenLastUsedService.isTokenWithinTimeline$.pipe( take(1), - tap(([_, queryToken]) => this.authService.setQueryToken(queryToken)), - filter(([isTokenWithinTimeline, queryToken]) => { - if (queryToken) { - return true; - } - + filter((isTokenWithinTimeline) => { if (!isTokenWithinTimeline) { this.authService.clearAuthToken(); } @@ -263,13 +280,7 @@ export class SigninStore extends ComponentStore { return isTokenWithinTimeline; }), switchMap(() => this.authService.loginWithToken()), - tap((loginResult) => { - if (loginResult !== LoginResult.Success) { - this.authService.clearAuthToken(); - } else { - this.handleSuccessfulLogin(); - } - }), + tap(this.handleLoginResult.bind(this)), tapResponse( () => {}, (error: unknown) => {