/** * @copyright 2026 NHCarrigan * @license Naomi's Public License * @author Naomi Carrigan */ import { Injectable, signal, inject } from '@angular/core'; import { Router } from '@angular/router'; import { Observable, tap, catchError, switchMap, throwError, of } from 'rxjs'; import { ApiService } from './api.service'; import { AuthResponse, User } from '@library/shared-types'; import { environment } from '../../environments/environment'; import { HttpClient } from '@angular/common/http'; @Injectable({ providedIn: 'root' }) export class AuthService { private api = inject(ApiService); private router = inject(Router); private http = inject(HttpClient); private currentUser = signal(null); public readonly user = this.currentUser.asReadonly(); private refreshing = false; private refreshInterval?: ReturnType; login(): void { // Redirect to API login endpoint window.location.href = `${environment.apiUrl}/auth/login`; } getCurrentUser(): Observable { return this.api.get('/auth/me').pipe( tap(response => { this.currentUser.set(response.user); this.startRefreshTimer(); }), catchError(error => { if (error.status === 401) { return this.refreshToken().pipe( switchMap(() => this.api.get('/auth/me')), tap(response => { this.currentUser.set(response.user); this.startRefreshTimer(); }), catchError(() => { this.currentUser.set(null); this.stopRefreshTimer(); return throwError(() => error); }) ); } return throwError(() => error); }) ); } refreshToken(): Observable { if (this.refreshing) { return of({ user: this.currentUser()!, accessToken: '' }); } this.refreshing = true; return this.http.post( `${environment.apiUrl}/auth/refresh`, {}, { withCredentials: true } ).pipe( tap(response => { this.currentUser.set(response.user); this.refreshing = false; this.startRefreshTimer(); }), catchError(error => { this.refreshing = false; this.currentUser.set(null); this.stopRefreshTimer(); return throwError(() => error); }) ); } private startRefreshTimer(): void { this.stopRefreshTimer(); // Refresh token every 13 minutes (before 15-minute expiry) const refreshIntervalMs = 13 * 60 * 1000; this.refreshInterval = setInterval(() => { this.refreshToken().subscribe({ error: (err) => { console.error('Background token refresh failed:', err); this.stopRefreshTimer(); } }); }, refreshIntervalMs); } private stopRefreshTimer(): void { if (this.refreshInterval) { clearInterval(this.refreshInterval); this.refreshInterval = undefined; } } logout(): Observable<{ message: string }> { return this.api.post<{ message: string }>('/auth/logout', {}).pipe( tap(() => { this.currentUser.set(null); this.api.clearCsrfToken(); this.stopRefreshTimer(); this.router.navigate(['/']); }) ); } clearUser(): void { this.currentUser.set(null); this.stopRefreshTimer(); } isAuthenticated(): boolean { return this.user() !== null; } updateUser(user: User): void { this.currentUser.set(user); } isAdmin(): boolean { return this.user()?.isAdmin === true; } }