using System.Net.Http.Json; using Microsoft.AspNetCore.Components.Authorization; using NexusReader.Application.Abstractions.Services; using NexusReader.Application.DTOs.User; using NexusReader.Application.Constants; using FluentResults; namespace NexusReader.UI.Shared.Services; public class IdentityService : IIdentityService { private readonly HttpClient _httpClient; private readonly INativeStorageService _storageService; private readonly AuthenticationStateProvider? _authStateProvider; private const string TokenKey = StorageKeys.AuthToken; private const string RefreshTokenKey = StorageKeys.RefreshToken; private Task? _profileTask; private UserProfileDto? _cachedProfile; private DateTime _lastFetchAttempt = DateTime.MinValue; public event Func? OnStateInvalidated; public IdentityService( HttpClient httpClient, INativeStorageService storageService, AuthenticationStateProvider? authStateProvider = null) { _httpClient = httpClient; _storageService = storageService; _authStateProvider = authStateProvider; } public async Task RegisterAsync(string email, string password) { try { var response = await _httpClient.PostAsJsonAsync("identity/register", new { email, password }); return response.IsSuccessStatusCode ? Result.Ok() : Result.Fail("Rejestracja nie powiodła się."); } catch (Exception ex) { return Result.Fail(new Error("Błąd sieci podczas rejestracji.").CausedBy(ex)); } } public async Task LoginAsync(string email, string password, bool rememberMe = false) { try { var response = await _httpClient.PostAsJsonAsync("identity/login", new { email, password }); if (response.IsSuccessStatusCode) { _cachedProfile = null; LoginResponse? result = null; try { result = await response.Content.ReadFromJsonAsync(); } catch (System.Text.Json.JsonException ex) { return Result.Fail(new Error("Błąd przetwarzania odpowiedzi serwera.").CausedBy(ex)); } if (result != null && !string.IsNullOrEmpty(result.AccessToken)) { await _storageService.SaveSecureString(TokenKey, result.AccessToken); if (!string.IsNullOrEmpty(result.RefreshToken)) { await _storageService.SaveSecureString(RefreshTokenKey, result.RefreshToken); } } (_authStateProvider as NexusAuthenticationStateProvider)?.ClearCache(); if (OnStateInvalidated != null) await OnStateInvalidated.Invoke(); var profileResult = await GetProfileAsync(); if (profileResult.IsSuccess) { var profile = profileResult.Value; await _storageService.SaveSecureString(StorageKeys.UserEmail, profile.Email); await _storageService.SaveSecureString(StorageKeys.UserTenant, profile.TenantId.ToString()); var rolesStr = string.Join(",", profile.Roles ?? Array.Empty()); await _storageService.SaveSecureString(StorageKeys.UserRoles, rolesStr); (_authStateProvider as NexusAuthenticationStateProvider)?.NotifyUserAuthentication(profile.Email, profile.TenantId.ToString(), rolesStr); } else { (_authStateProvider as NexusAuthenticationStateProvider)?.NotifyUserAuthentication(email, "unknown", ""); } return Result.Ok(); } return Result.Fail("Nieprawidłowy e-mail lub hasło."); } catch (Exception ex) { return Result.Fail(new Error("Błąd połączenia z serwerem.").CausedBy(ex)); } } public async Task LogoutAsync() { try { _cachedProfile = null; if (System.OperatingSystem.IsBrowser()) { await _storageService.SaveSecureString(TokenKey, ""); await _storageService.SaveSecureString(RefreshTokenKey, ""); await _storageService.SaveSecureString(StorageKeys.UserEmail, ""); await _storageService.SaveSecureString(StorageKeys.UserTenant, ""); await _storageService.SaveSecureString(StorageKeys.UserRoles, ""); } if (OnStateInvalidated != null) await OnStateInvalidated.Invoke(); (_authStateProvider as NexusAuthenticationStateProvider)?.ClearCache(); (_authStateProvider as NexusAuthenticationStateProvider)?.NotifyUserLogout(); return Result.Ok(); } catch (Exception ex) { return Result.Fail(new Error("Błąd podczas wylogowywania.").CausedBy(ex)); } } public async Task> GetProfileAsync() { if (_cachedProfile != null) { return Result.Ok(_cachedProfile); } if (_profileTask != null) { var p = await _profileTask; return p != null ? Result.Ok(p) : Result.Fail("Nie znaleziono profilu."); } _profileTask = GetProfileInternalAsync(); var result = await _profileTask; return result != null ? Result.Ok(result) : Result.Fail("Błąd podczas pobierania profilu."); } private async Task GetProfileInternalAsync() { if (!System.OperatingSystem.IsBrowser()) { return null; } if (DateTime.UtcNow - _lastFetchAttempt < TimeSpan.FromSeconds(5)) { return null; } _lastFetchAttempt = DateTime.UtcNow; try { var response = await _httpClient.GetAsync("identity/profile"); if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized) { await LogoutAsync(); return null; } if (response.IsSuccessStatusCode) { var profile = await response.Content.ReadFromJsonAsync(); if (profile != null) { _cachedProfile = profile; await _storageService.SaveSecureString(StorageKeys.UserEmail, profile.Email); await _storageService.SaveSecureString(StorageKeys.UserTenant, profile.TenantId.ToString()); var rolesStr = string.Join(",", profile.Roles ?? Array.Empty()); await _storageService.SaveSecureString(StorageKeys.UserRoles, rolesStr); (_authStateProvider as NexusAuthenticationStateProvider)?.NotifyUserAuthentication(profile.Email, profile.TenantId.ToString(), rolesStr); } return profile; } return null; } catch { return null; } finally { _profileTask = null; } } public async Task RefreshTokenAsync() { try { var result = await _storageService.GetSecureString(RefreshTokenKey); var refreshToken = result.IsSuccess ? result.Value : null; if (string.IsNullOrEmpty(refreshToken)) return Result.Fail("Brak tokena odświeżania."); var response = await _httpClient.PostAsJsonAsync("identity/refresh", new { refreshToken }); if (response.IsSuccessStatusCode) { var loginResult = await response.Content.ReadFromJsonAsync(); if (loginResult != null && !string.IsNullOrEmpty(loginResult.AccessToken)) { await _storageService.SaveSecureString(TokenKey, loginResult.AccessToken); if (!string.IsNullOrEmpty(loginResult.RefreshToken)) { await _storageService.SaveSecureString(RefreshTokenKey, loginResult.RefreshToken); } _cachedProfile = null; (_authStateProvider as NexusAuthenticationStateProvider)?.ClearCache(); if (OnStateInvalidated != null) await OnStateInvalidated.Invoke(); var profileResult = await GetProfileAsync(); if (profileResult.IsSuccess) { var profile = profileResult.Value; await _storageService.SaveSecureString(StorageKeys.UserEmail, profile.Email); await _storageService.SaveSecureString(StorageKeys.UserTenant, profile.TenantId.ToString()); var rolesStr = string.Join(",", profile.Roles ?? Array.Empty()); await _storageService.SaveSecureString(StorageKeys.UserRoles, rolesStr); (_authStateProvider as NexusAuthenticationStateProvider)?.NotifyUserAuthentication(profile.Email, profile.TenantId.ToString(), rolesStr); } return Result.Ok(); } } return Result.Fail("Sesja wygasła."); } catch (Exception ex) { return Result.Fail(new Error("Błąd odświeżania sesji.").CausedBy(ex)); } } private class LoginResponse { public string TokenType { get; set; } = string.Empty; public string AccessToken { get; set; } = string.Empty; public int ExpiresIn { get; set; } public string RefreshToken { get; set; } = string.Empty; } }