diff --git a/src/app/core/services/auth/auth.interceptor.spec.ts b/src/app/core/services/auth/auth.interceptor.spec.ts index 1489b9c..a9a4b95 100644 --- a/src/app/core/services/auth/auth.interceptor.spec.ts +++ b/src/app/core/services/auth/auth.interceptor.spec.ts @@ -1,4 +1,4 @@ -import { HttpClient } from '@angular/common/http'; +import { HttpClient, HttpErrorResponse } from '@angular/common/http'; import { provideHttpClient, withInterceptors } from '@angular/common/http'; import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing'; import { computed, signal } from '@angular/core'; @@ -69,7 +69,7 @@ describe('authInterceptor', () => { httpController.verify(); }); - it('clears the local session when an authenticated request returns 401', () => { + it('propagates a 401 without clearing the local session', () => { const authService = createAuthServiceStub('expired-token'); TestBed.configureTestingModule({ @@ -83,14 +83,21 @@ describe('authInterceptor', () => { const client = TestBed.inject(HttpClient); const httpController = TestBed.inject(HttpTestingController); + let receivedError: unknown; + let receivedStatus: number | null = null; client.get(`${environment.url}me`).subscribe({ - error: () => undefined + error: (error: HttpErrorResponse) => { + receivedError = error; + receivedStatus = error.status; + } }); const request = httpController.expectOne(`${environment.url}me`); request.flush({ message: 'Unauthorized' }, { status: 401, statusText: 'Unauthorized' }); - expect(authService.clearSession).toHaveBeenCalledTimes(1); + expect(receivedError).toBeInstanceOf(HttpErrorResponse); + expect(receivedStatus).toBe(401); + expect(authService.clearSession).not.toHaveBeenCalled(); httpController.verify(); }); }); diff --git a/src/app/core/services/auth/auth.interceptor.ts b/src/app/core/services/auth/auth.interceptor.ts index 408faa9..7d1ad0b 100644 --- a/src/app/core/services/auth/auth.interceptor.ts +++ b/src/app/core/services/auth/auth.interceptor.ts @@ -1,12 +1,6 @@ -import { - HttpErrorResponse, - HttpEvent, - HttpHandlerFn, - HttpInterceptorFn, - HttpRequest -} from '@angular/common/http'; +import { HttpEvent, HttpHandlerFn, HttpInterceptorFn, HttpRequest } from '@angular/common/http'; import { inject } from '@angular/core'; -import { Observable, catchError, throwError } from 'rxjs'; +import { Observable } from 'rxjs'; import { AuthService } from './auth.service'; @@ -25,13 +19,5 @@ export const authInterceptor: HttpInterceptorFn = ( }) : request; - return next(authenticatedRequest).pipe( - catchError((error: unknown) => { - if (error instanceof HttpErrorResponse && error.status === 401 && token) { - authService.clearSession(); - } - - return throwError(() => error); - }) - ); + return next(authenticatedRequest); }; diff --git a/src/app/core/services/auth/auth.service.spec.ts b/src/app/core/services/auth/auth.service.spec.ts index 7692049..325551a 100644 --- a/src/app/core/services/auth/auth.service.spec.ts +++ b/src/app/core/services/auth/auth.service.spec.ts @@ -95,7 +95,7 @@ describe('AuthService', () => { httpController.verify(); }); - it('clears the session when bootstrap receives 401 from /me', async () => { + it('propagates a 401 from /me without clearing the session', async () => { cookieStore['shopit.auth.token'] = 'expired-token'; TestBed.configureTestingModule({ @@ -116,11 +116,11 @@ describe('AuthService', () => { request.flush({ message: 'Unauthorized' }, { status: 401, statusText: 'Unauthorized' }); - await expect(bootstrapPromise).resolves.toBeUndefined(); + await expect(bootstrapPromise).rejects.toMatchObject({ status: 401 }); expect(service.user()).toBeNull(); - expect(service.token()).toBeNull(); - expect(service.isAuthenticated()).toBe(false); - expect(cookieStore['shopit.auth.token']).toBeUndefined(); + expect(service.token()).toBe('expired-token'); + expect(service.isAuthenticated()).toBe(true); + expect(cookieStore['shopit.auth.token']).toBe('expired-token'); httpController.verify(); }); @@ -168,7 +168,7 @@ describe('AuthService', () => { httpController.verify(); }); - it('clears local session even when logout request fails', () => { + it('propagates logout errors without clearing the local session', () => { TestBed.configureTestingModule({ providers: [ provideHttpClient(), @@ -202,9 +202,9 @@ describe('AuthService', () => { expect(logoutRequest.request.method).toBe('POST'); logoutRequest.flush({ message: 'Unauthorized' }, { status: 401, statusText: 'Unauthorized' }); - expect(service.token()).toBeNull(); - expect(service.user()).toBeNull(); - expect(cookieStore['shopit.auth.token']).toBeUndefined(); + expect(service.token()).toBe('plain-text-token'); + expect(service.user()?.email).toBe('ada@example.com'); + expect(cookieStore['shopit.auth.token']).toBe('plain-text-token'); httpController.verify(); }); diff --git a/src/app/core/services/auth/auth.service.ts b/src/app/core/services/auth/auth.service.ts index 94c7fb9..ce00c09 100644 --- a/src/app/core/services/auth/auth.service.ts +++ b/src/app/core/services/auth/auth.service.ts @@ -1,4 +1,4 @@ -import { HttpClient, HttpErrorResponse } from '@angular/common/http'; +import { HttpClient } from '@angular/common/http'; import { isPlatformBrowser, isPlatformServer } from '@angular/common'; import { computed, inject, Injectable, PLATFORM_ID, signal, TransferState, makeStateKey } from '@angular/core'; import { firstValueFrom, map, Observable, of, tap } from 'rxjs'; @@ -57,10 +57,7 @@ export class AuthService { } return this.http.post(`${environment.url}logout`, {}).pipe( - tap({ - next: () => this.clearSession(), - error: () => this.clearSession() - }) + tap(() => this.clearSession()) ); } @@ -78,18 +75,9 @@ export class AuthService { return; } - try { - const user = await firstValueFrom(this.loadCurrentUser()); - if (isPlatformServer(this.platformId)) { - this.transferState.set(AUTH_USER_SSR_STATE_KEY, user); - } - } catch (error) { - if (this.isUnauthorizedError(error)) { - this.clearSession(); - return; - } - - throw error; + const user = await firstValueFrom(this.loadCurrentUser()); + if (isPlatformServer(this.platformId)) { + this.transferState.set(AUTH_USER_SSR_STATE_KEY, user); } } @@ -115,8 +103,4 @@ export class AuthService { this.userState.set(user); this.cookieService.set(AUTH_TOKEN_COOKIE_KEY, token); } - - private isUnauthorizedError(error: unknown): error is HttpErrorResponse { - return error instanceof HttpErrorResponse && error.status === 401; - } } diff --git a/src/app/core/services/cookie/cookie.service.ts b/src/app/core/services/cookie/cookie.service.ts index 340e3f4..a855628 100644 --- a/src/app/core/services/cookie/cookie.service.ts +++ b/src/app/core/services/cookie/cookie.service.ts @@ -23,12 +23,9 @@ export class CookieService { return null; } - set(name: string, value: string, days = 7): void { + set(name: string, value: string): void { if (isPlatformBrowser(this.platformId)) { - const date = new Date(); - date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000); - const expires = `expires=${date.toUTCString()}`; - this.document.cookie = `${name}=${value};${expires};path=/;SameSite=Lax`; + this.document.cookie = `${name}=${value};path=/;SameSite=Lax`; } }