portfolio-admin/src/app/auth/auth.service.ts

124 lines
3.3 KiB
TypeScript

import { HttpClient } from '@angular/common/http';
import { inject, Injectable, PLATFORM_ID } from '@angular/core';
import { BehaviorSubject, map, Observable, of } from 'rxjs';
import { environment } from '../../environments/environment';
import { isPlatformBrowser } from '@angular/common';
import { Router } from '@angular/router';
interface ValidateOtpResponse {
accessToken: string;
}
interface RefreshTokenResponse {
accessToken: string;
}
@Injectable({
providedIn: 'root'
})
export class AuthService {
private readonly baseUrl = (environment.apiUrl ?? '').replace(/\/+$/, '');
private accessToken: string | null = null;
private platformId = inject(PLATFORM_ID);
private accessTokenSub = new BehaviorSubject<string | null>(null);
tokenReady$ = new BehaviorSubject<boolean>(false);
http = inject(HttpClient);
router = inject(Router);
private readonly storageKey = 'accessToken';
private api(path: string) {
return `${this.baseUrl}${path.startsWith('/') ? '' : '/'}${path}`;
}
// Call on app start, or from guard
async ensureTokenReady(): Promise<void>{
if(this.tokenReady$.value) return;
const stored = this.safeGetToken();
// try to restore from storage
if(stored){
this.accessTokenSub.next(stored);
this.tokenReady$.next(true);
return;
}
// // Optionally: try a silent refresh on startup to restore session using HttpOnly cookie
// try {
// const res = await firstValueFrom(this.refreshToken());
// this.safeSetToken(res.accessToken);
// }
// catch{
// console.warn('Silent token refresh failed on startup');
// }
// finally{
// this.tokenReady$.next(true);
// }
}
safeSetToken(token: string) {
this.accessToken = token;
if (isPlatformBrowser(this.platformId)) {
localStorage.setItem(this.storageKey, token);
}
}
private safeGetToken(): string | null {
if (isPlatformBrowser(this.platformId)) {
return localStorage.getItem(this.storageKey);
}
return null;
}
private safeRemoveToken() {
this.accessToken = null;
if (isPlatformBrowser(this.platformId)) {
localStorage.removeItem(this.storageKey);
}
}
sendOtp(email: string): Observable<unknown> {
const formData = new FormData();
formData.append('email', email);
return this.http.post(this.api('/api/v1/auth/GenerateOtp'), formData);
}
verifyOtp(userId: string, otpCode: string): Observable<void> {
const body = {
UserId: userId,
OtpCode: otpCode
};
return this.http.post<ValidateOtpResponse>(this.api('/api/v1/auth/ValidateOtp'), body).pipe(map((response: ValidateOtpResponse) => {
if (response && response.accessToken) {
this.accessToken = response.accessToken;
this.safeSetToken(response.accessToken);
}
}));
}
getAccessToken(): string | null {
return this.accessToken ?? this.safeGetToken();
}
logout(): Observable<void> {
this.accessToken = null;
this.safeRemoveToken();
this.router.navigate(['/login']);
return of();
}
refreshToken(): Observable<RefreshTokenResponse> {
return this.http.post<RefreshTokenResponse>(this.api('/api/v1/auth/RefreshToken'), {});
}
getApiKey(): string {
return environment.apiKey;
}
isLoggedIn(): boolean {
return this.safeGetToken() != null;
}
}