/** * @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'; import { ToastService } from '../services/toast.service'; @Injectable() export class AuthInterceptor implements HttpInterceptor { private router = inject(Router); private http = inject(HttpClient); private toast = inject(ToastService); private isRefreshing = false; private refreshTokenSubject = new BehaviorSubject(null); 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); } // Show toast for other HTTP errors this.showErrorToast(error); 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.toast.error('Your session has expired. Please log in again.'); 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')); }) ); } private showErrorToast(error: HttpErrorResponse): void { let message = 'Something went wrong. Please try again.'; switch (error.status) { case 400: message = error.error?.message || 'Invalid request. Please check your input.'; break; case 403: message = 'You do not have permission to perform this action.'; break; case 404: message = 'Resource not found.'; break; case 500: message = 'Server error. Please try again later.'; break; case 0: message = 'Network error. Please check your connection.'; break; } this.toast.error(message); } }