134 lines
4.3 KiB
TypeScript
134 lines
4.3 KiB
TypeScript
import { computed, Injectable, inject, signal } from '@angular/core';
|
|
import { BehaviorSubject, Observable, catchError, finalize, map, shareReplay, throwError } from 'rxjs';
|
|
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
|
|
import { LoginRequest, LoginResponse, UserProfile } from '../../models/auth/auth.model';
|
|
import { TokenStorageService } from './token-storage.service';
|
|
import { AppContextService } from '../../services/context/app-context.service';
|
|
import { AUTH_ENDPOINTS } from '../../../core/end-points/auth/auth.endpoints';
|
|
|
|
@Injectable({ providedIn: 'root' })
|
|
export class AuthService {
|
|
private readonly http = inject(HttpClient);
|
|
private readonly tokenStorage = inject(TokenStorageService);
|
|
private readonly appContextService = inject(AppContextService);
|
|
|
|
private readonly userSubject = new BehaviorSubject<UserProfile | null>(null);
|
|
readonly user$ = this.userSubject.asObservable();
|
|
readonly currentUserSignal = signal<UserProfile | null>(null);
|
|
private readonly accessTokenSignal = signal<string | null>(null);
|
|
readonly isAuthenticatedSignal = computed(() => !!this.accessTokenSignal() && !!this.currentUserSignal());
|
|
|
|
private refreshRequest$: Observable<LoginResponse> | null = null;
|
|
|
|
constructor() {
|
|
this.restoreAuthState();
|
|
}
|
|
|
|
login(payload: LoginRequest, rememberMe: boolean): Observable<LoginResponse> {
|
|
return this.http.post<LoginResponse>(`${AUTH_ENDPOINTS.login}`, payload).pipe(
|
|
map((response) => {
|
|
const user = this.normalizeUserProfile(response);
|
|
this.tokenStorage.saveAuth(response, rememberMe);
|
|
this.setAuthState(user, this.tokenStorage.getAccessToken());
|
|
return response;
|
|
})
|
|
);
|
|
}
|
|
|
|
refreshAccessToken(): Observable<LoginResponse> {
|
|
if (this.refreshRequest$) {
|
|
return this.refreshRequest$;
|
|
}
|
|
|
|
const refreshToken = this.tokenStorage.getRefreshToken();
|
|
if (!refreshToken) {
|
|
this.logout();
|
|
return throwError(() => new HttpErrorResponse({ status: 401, statusText: 'Refresh token missing' }));
|
|
}
|
|
|
|
this.refreshRequest$ = this.http.post<LoginResponse>(`${AUTH_ENDPOINTS.refresh}`, { refreshToken }).pipe(
|
|
map((response) => {
|
|
const user = this.normalizeUserProfile(response, this.currentUserSignal());
|
|
const storageType = this.tokenStorage.getStorageType();
|
|
const rememberMe = storageType === 'local';
|
|
this.tokenStorage.saveAuth(response, rememberMe);
|
|
this.setAuthState(user, this.tokenStorage.getAccessToken());
|
|
return response;
|
|
}),
|
|
catchError((error) => {
|
|
this.logout();
|
|
return throwError(() => error);
|
|
}),
|
|
finalize(() => {
|
|
this.refreshRequest$ = null;
|
|
}),
|
|
shareReplay(1)
|
|
);
|
|
|
|
return this.refreshRequest$;
|
|
}
|
|
|
|
logout(): void {
|
|
this.tokenStorage.clearAuth();
|
|
this.appContextService.clearContext();
|
|
this.setAuthState(null, null);
|
|
this.refreshRequest$ = null;
|
|
}
|
|
|
|
get accessToken(): string | null {
|
|
return this.tokenStorage.getAccessToken();
|
|
}
|
|
|
|
get refreshToken(): string | null {
|
|
return this.tokenStorage.getRefreshToken();
|
|
}
|
|
|
|
get isAuthenticated(): boolean {
|
|
return this.isAuthenticatedSignal();
|
|
}
|
|
|
|
get isLoggedIn(): boolean {
|
|
return this.isAuthenticatedSignal();
|
|
}
|
|
|
|
get currentUser(): UserProfile | null {
|
|
return this.currentUserSignal();
|
|
}
|
|
|
|
private restoreAuthState(): void {
|
|
const storedUser = this.tokenStorage.getUser();
|
|
const storedAccessToken = this.tokenStorage.getAccessToken();
|
|
|
|
if (storedUser && storedAccessToken) {
|
|
this.setAuthState(storedUser, storedAccessToken);
|
|
return;
|
|
}
|
|
|
|
this.setAuthState(null, storedAccessToken ?? null);
|
|
}
|
|
|
|
private setAuthState(user: UserProfile | null, token: string | null): void {
|
|
this.userSubject.next(user);
|
|
this.currentUserSignal.set(user);
|
|
this.accessTokenSignal.set(token);
|
|
}
|
|
|
|
private normalizeUserProfile(response: LoginResponse, fallbackUser: UserProfile | null = null): UserProfile | null {
|
|
if (response.user) {
|
|
return response.user;
|
|
}
|
|
|
|
const userId = response.userId?.trim();
|
|
const email = response.email?.trim();
|
|
if (userId || email) {
|
|
return {
|
|
id: userId ?? fallbackUser?.id ?? '',
|
|
email: email ?? fallbackUser?.email ?? '',
|
|
roles: response.roles,
|
|
};
|
|
}
|
|
|
|
return fallbackUser;
|
|
}
|
|
}
|