/** * @copyright 2026 NHCarrigan * @license Naomi's Public License * @author Naomi Carrigan */ import { Injectable, inject } from '@angular/core'; import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor, HttpErrorResponse, HttpClient } from '@angular/common/http'; import { Observable, catchError, throwError, switchMap, BehaviorSubject, filter, take } from 'rxjs'; import { Router } from '@angular/router'; import { environment } from '../../environments/environment'; @Injectable() export class AuthInterceptor implements HttpInterceptor { private isRefreshing = false; private refreshTokenSubject = new BehaviorSubject(null); constructor( private router: Router, private http: HttpClient ) {} intercept(request: HttpRequest, next: HttpHandler): Observable> { // Clone the request to add withCredentials const authReq = request.clone({ withCredentials: true }); return next.handle(authReq).pipe( catchError((error: HttpErrorResponse) => { if (error.status === 401 && !request.url.includes('/auth/refresh') && !request.url.includes('/auth/logout')) { return this.handle401Error(authReq, next); } return throwError(() => error); }) ); } private handle401Error(request: HttpRequest, next: HttpHandler): Observable> { if (!this.isRefreshing) { this.isRefreshing = true; this.refreshTokenSubject.next(null); return this.http.post( `${environment.apiUrl}/auth/refresh`, {}, { withCredentials: true } ).pipe( switchMap(() => { this.isRefreshing = false; this.refreshTokenSubject.next(true); return next.handle(request); }), catchError((err) => { this.isRefreshing = false; this.refreshTokenSubject.next(false); this.router.navigate(['/']); return throwError(() => err); }) ); } return this.refreshTokenSubject.pipe( filter(result => result !== null), take(1), switchMap(success => { if (success) { return next.handle(request); } return throwError(() => new Error('Token refresh failed')); }) ); } }