import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { environment } from '../../environments/environment'; import { BehaviorSubject } from 'rxjs'; import { tap } from 'rxjs/operators'; import { MveAuditService } from './mve-audit.service'; export interface RegisterPayload { name: string; email: string; phone: string; password: string; confirmPassword: string; } export interface LoginPayload { email: string; password: string; } export interface LoginOptions { rememberMe?: boolean; } export interface LoginResponse { token?: string; accessToken?: string; } export interface AuthUserProfile { id: string; nome: string; email: string; tenantId: string; roles: string[]; } @Injectable({ providedIn: 'root' }) export class AuthService { private baseUrl = `${environment.apiUrl}/auth`; private userProfileSubject = new BehaviorSubject(null); readonly userProfile$ = this.userProfileSubject.asObservable(); private readonly tokenStorageKey = 'token'; private readonly tokenExpiresAtKey = 'tokenExpiresAt'; private readonly rememberMeHours = 6; constructor( private http: HttpClient, private readonly mveAuditService: MveAuditService ) { this.syncUserProfileFromToken(); } register(payload: RegisterPayload) { return this.http.post<{ token: string }>(`${this.baseUrl}/register`, payload) .pipe(tap(r => this.setToken(r.token))); } login(payload: LoginPayload, options?: LoginOptions) { return this.http.post(`${this.baseUrl}/login`, payload) .pipe( tap((r) => { const token = this.resolveLoginToken(r); if (!token) return; this.setToken(token, options?.rememberMe ?? false); }) ); } logout() { if (typeof window === 'undefined') { this.mveAuditService.clearCache(); this.userProfileSubject.next(null); return; } this.mveAuditService.clearCache(); this.clearTokenStorage(localStorage); this.clearTokenStorage(sessionStorage); this.userProfileSubject.next(null); } setToken(token: string, rememberMe = false) { if (typeof window === 'undefined') return; this.mveAuditService.clearCache(); this.clearTokenStorage(localStorage); this.clearTokenStorage(sessionStorage); if (rememberMe) { const expiresAt = Date.now() + this.rememberMeHours * 60 * 60 * 1000; localStorage.setItem(this.tokenStorageKey, token); localStorage.setItem(this.tokenExpiresAtKey, String(expiresAt)); } else { sessionStorage.setItem(this.tokenStorageKey, token); } this.syncUserProfileFromToken(); } get token(): string | null { if (typeof window === 'undefined') return null; this.cleanupExpiredRememberSession(); const sessionToken = sessionStorage.getItem(this.tokenStorageKey); if (sessionToken) return sessionToken; return localStorage.getItem(this.tokenStorageKey); } isLoggedIn(): boolean { return !!this.token; } get currentUserProfile(): AuthUserProfile | null { return this.userProfileSubject.value; } syncUserProfileFromToken() { this.userProfileSubject.next(this.buildProfileFromToken()); } updateUserProfile(profile: Pick) { const current = this.userProfileSubject.value; if (!current) return; this.userProfileSubject.next({ ...current, nome: profile.nome.trim(), email: profile.email.trim().toLowerCase(), }); } getTokenPayload(): Record | null { const token = this.token; if (!token) return null; try { const payload = token.split('.')[1]; const base64 = payload.replace(/-/g, '+').replace(/_/g, '/'); const json = decodeURIComponent( atob(base64) .split('') .map(c => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)) .join('') ); return JSON.parse(json); } catch { return null; } } getRoles(): string[] { const payload = this.getTokenPayload(); if (!payload) return []; return this.extractRoles(payload); } private extractRoles(payload: Record): string[] { const possibleKeys = [ 'role', 'roles', 'http://schemas.microsoft.com/ws/2008/06/identity/claims/role', ]; let roles: string[] = []; for (const key of possibleKeys) { const value = payload[key]; if (!value) continue; if (Array.isArray(value)) roles = roles.concat(value); else roles.push(String(value)); } return roles.map(r => r.toLowerCase()); } private buildProfileFromToken(): AuthUserProfile | null { const payload = this.getTokenPayload(); if (!payload) return null; const id = String( payload['sub'] ?? payload['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier'] ?? '' ).trim(); const nome = String(payload['name'] ?? '').trim(); const email = String( payload['email'] ?? payload['http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress'] ?? '' ).trim().toLowerCase(); const tenantId = String( payload['tenantId'] ?? payload['tenant'] ?? payload['TenantId'] ?? '' ).trim(); if (!id || !tenantId) return null; return { id, nome, email, tenantId, roles: this.extractRoles(payload), }; } hasRole(role: string): boolean { const target = (role || '').toLowerCase(); if (!target) return false; return this.getRoles().includes(target); } private cleanupExpiredRememberSession() { const token = localStorage.getItem(this.tokenStorageKey); if (!token) return; const expiresAtRaw = localStorage.getItem(this.tokenExpiresAtKey); if (!expiresAtRaw) { this.clearTokenStorage(localStorage); return; } const expiresAt = Number(expiresAtRaw); if (!Number.isFinite(expiresAt)) { this.clearTokenStorage(localStorage); return; } if (Date.now() > expiresAt) { this.clearTokenStorage(localStorage); } } private clearTokenStorage(storage: Storage) { storage.removeItem(this.tokenStorageKey); storage.removeItem(this.tokenExpiresAtKey); } private resolveLoginToken(response: LoginResponse | null | undefined): string | null { const raw = response?.token ?? response?.accessToken ?? null; const token = (raw ?? '').toString().trim(); return token || null; } }