96 lines
2.8 KiB
TypeScript
96 lines
2.8 KiB
TypeScript
import { Injectable, inject } from '@angular/core';
|
|
import { HttpClient } from '@angular/common/http';
|
|
import { BehaviorSubject, firstValueFrom, map, Observable, tap } from 'rxjs';
|
|
|
|
import { environment } from '../../environments/environment';
|
|
import { AppUser, AuthTokenResponse } from '../interfaces/auth';
|
|
|
|
const TOKEN_KEY = 'kmtn_access_token';
|
|
|
|
@Injectable({
|
|
providedIn: 'root',
|
|
})
|
|
export class AuthService {
|
|
private http = inject(HttpClient);
|
|
private baseUrl = `${environment.apiBaseUrl}/api/auth`;
|
|
|
|
private authStateSubject = new BehaviorSubject<AppUser | null>(null);
|
|
|
|
/** Observable that emits the current auth state. */
|
|
readonly authState$: Observable<AppUser | null> = this.authStateSubject.asObservable();
|
|
|
|
constructor() {
|
|
// Check for existing token on init
|
|
this._initAuth();
|
|
}
|
|
|
|
/** Login with username/password (bootstrap admin). */
|
|
login(username: string, password: string): Observable<AuthTokenResponse> {
|
|
return this.http.post<AuthTokenResponse>(`${this.baseUrl}/login`, {
|
|
username,
|
|
password,
|
|
}).pipe(
|
|
tap((resp) => localStorage.setItem(TOKEN_KEY, resp.access_token)),
|
|
);
|
|
}
|
|
|
|
/** Login with Google ID token. */
|
|
loginWithGoogle(idToken: string): Observable<AuthTokenResponse> {
|
|
return this.http.post<AuthTokenResponse>(`${this.baseUrl}/google`, {
|
|
id_token: idToken,
|
|
}).pipe(
|
|
tap((resp) => localStorage.setItem(TOKEN_KEY, resp.access_token)),
|
|
);
|
|
}
|
|
|
|
/** Clear stored token and reset auth state. */
|
|
logout(): void {
|
|
localStorage.removeItem(TOKEN_KEY);
|
|
this.authStateSubject.next(null);
|
|
}
|
|
|
|
/** Fetch current user info from the API. */
|
|
getCurrentUser(): Promise<AppUser | null> {
|
|
const token = this._getToken();
|
|
if (!token) return Promise.resolve(null);
|
|
|
|
return firstValueFrom(
|
|
this.http.get<AppUser>(`${this.baseUrl}/me`).pipe(
|
|
tap((user) => this.authStateSubject.next(user)),
|
|
map((user) => user),
|
|
)
|
|
).catch(() => {
|
|
this.logout();
|
|
return null;
|
|
});
|
|
}
|
|
|
|
/** Check if there is a stored token. */
|
|
isLoggedIn(): boolean {
|
|
return !!this._getToken();
|
|
}
|
|
|
|
/** Check if the current user is an admin (synchronous, based on cached state). */
|
|
isAdmin(): boolean {
|
|
return this.authStateSubject.value?.is_admin ?? false;
|
|
}
|
|
|
|
/** Get the current user synchronously (from cached state). */
|
|
getUser(): AppUser | null {
|
|
return this.authStateSubject.value;
|
|
}
|
|
|
|
// ── Private ──────────────────────────────────────────────
|
|
|
|
private _getToken(): string | null {
|
|
return localStorage.getItem(TOKEN_KEY);
|
|
}
|
|
|
|
private async _initAuth(): Promise<void> {
|
|
const token = this._getToken();
|
|
if (token) {
|
|
await this.getCurrentUser();
|
|
}
|
|
}
|
|
}
|