diff --git a/src/app/core/interceptors/error.interceptor.spec.ts b/src/app/core/interceptors/error.interceptor.spec.ts index 8b9ef1e55..8d977ad30 100644 --- a/src/app/core/interceptors/error.interceptor.spec.ts +++ b/src/app/core/interceptors/error.interceptor.spec.ts @@ -9,6 +9,7 @@ import { Router } from '@angular/router'; import { SENTRY_TOKEN } from '@core/provider/sentry.provider'; import { AuthService } from '@core/services/auth.service'; +import { UserSelectors } from '@core/store/user'; import { ToastService } from '@osf/shared/services/toast.service'; import { ViewOnlyLinkHelperService } from '@osf/shared/services/view-only-link-helper.service'; @@ -17,6 +18,7 @@ import { AuthServiceMock, AuthServiceMockType } from '@testing/providers/auth-se import { LoaderServiceMock, provideLoaderServiceMock } from '@testing/providers/loader-service.mock'; import { RouterMockBuilder, RouterMockType } from '@testing/providers/router-provider.mock'; import { SentryMock, SentryMockType } from '@testing/providers/sentry-provider.mock'; +import { provideMockStore } from '@testing/providers/store-provider.mock'; import { ToastServiceMock, ToastServiceMockType } from '@testing/providers/toast-provider.mock'; import { ViewOnlyLinkHelperMock, ViewOnlyLinkHelperMockType } from '@testing/providers/view-only-link-helper.mock'; @@ -31,7 +33,12 @@ describe('errorInterceptor', () => { let viewOnlyHelperMock: ViewOnlyLinkHelperMockType; let sentryMock: SentryMockType; - function setup(platformId: 'browser' | 'server' = 'browser', viewOnly = false, routerUrl = '/dashboard') { + function setup( + platformId: 'browser' | 'server' = 'browser', + viewOnly = false, + routerUrl = '/dashboard', + isAuthenticated = false + ) { router = RouterMockBuilder.create().withUrl(routerUrl).withNavigate(vi.fn().mockResolvedValue(true)).build(); toastServiceMock = ToastServiceMock.simple(); loaderServiceMock = new LoaderServiceMock(); @@ -43,6 +50,9 @@ describe('errorInterceptor', () => { providers: [ provideOSFCore(), provideLoaderServiceMock(loaderServiceMock), + provideMockStore({ + selectors: [{ selector: UserSelectors.isAuthenticated, value: isAuthenticated }], + }), MockProvider(Router, router), MockProvider(ToastService, toastServiceMock), MockProvider(AuthService, authServiceMock), @@ -107,15 +117,30 @@ describe('errorInterceptor', () => { expect(toastServiceMock.showError).not.toHaveBeenCalled(); }); - it('should logout on 401 in browser when not view-only', async () => { - setup('browser', false); + it('should navigate to sign in on 401 in browser when anonymous and not view-only', async () => { + setup('browser', false, '/dashboard', false); const request = createRequest('/api/v2/nodes/abc'); const error = new HttpErrorResponse({ status: 401, error: {}, url: request.url }); const caught = await runInterceptor(request, error); expect(caught?.status).toBe(401); - expect(authServiceMock.logout).toHaveBeenCalled(); + expect(authServiceMock.navigateToSignIn).toHaveBeenCalled(); + expect(authServiceMock.logout).not.toHaveBeenCalled(); + expect(loaderServiceMock.hide).not.toHaveBeenCalled(); + expect(toastServiceMock.showError).not.toHaveBeenCalled(); + }); + + it('should logout on 401 in browser when authenticated and not view-only', async () => { + setup('browser', false, '/dashboard', true); + const request = createRequest('/api/v2/nodes/abc'); + const error = new HttpErrorResponse({ status: 401, error: {}, url: request.url }); + + const caught = await runInterceptor(request, error); + + expect(caught?.status).toBe(401); + expect(authServiceMock.logout).toHaveBeenCalledWith(window.location.href); + expect(authServiceMock.navigateToSignIn).not.toHaveBeenCalled(); expect(loaderServiceMock.hide).not.toHaveBeenCalled(); expect(toastServiceMock.showError).not.toHaveBeenCalled(); }); diff --git a/src/app/core/interceptors/error.interceptor.ts b/src/app/core/interceptors/error.interceptor.ts index 99848cc4b..7a914a208 100644 --- a/src/app/core/interceptors/error.interceptor.ts +++ b/src/app/core/interceptors/error.interceptor.ts @@ -1,3 +1,5 @@ +import { Store } from '@ngxs/store'; + import { throwError } from 'rxjs'; import { catchError } from 'rxjs/operators'; @@ -9,6 +11,7 @@ import { Router } from '@angular/router'; import { ERROR_MESSAGES } from '@core/constants/error-messages'; import { SENTRY_TOKEN } from '@core/provider/sentry.provider'; import { AuthService } from '@core/services/auth.service'; +import { UserSelectors } from '@core/store/user'; import { LoaderService } from '@osf/shared/services/loader.service'; import { ToastService } from '@osf/shared/services/toast.service'; import { ViewOnlyLinkHelperService } from '@osf/shared/services/view-only-link-helper.service'; @@ -23,6 +26,7 @@ export const errorInterceptor: HttpInterceptorFn = (req, next) => { const sentry = inject(SENTRY_TOKEN); const platformId = inject(PLATFORM_ID); const viewOnlyHelper = inject(ViewOnlyLinkHelperService); + const store = inject(Store); return next(req).pipe( catchError((error: HttpErrorResponse) => { @@ -55,7 +59,11 @@ export const errorInterceptor: HttpInterceptorFn = (req, next) => { if (error.status === 401) { if (!viewOnlyHelper.hasViewOnlyParam(router)) { if (isPlatformBrowser(platformId)) { - authService.logout(); + if (store.selectSnapshot(UserSelectors.isAuthenticated)) { + authService.logout(window.location.href); + } else { + authService.navigateToSignIn(); + } } } return throwError(() => error);