124 lines
3.3 KiB
TypeScript
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;
|
|
}
|
|
} |