Files
Nexus.Reader/src/NexusReader.UI.Shared/Services/IdentityService.cs
T

260 lines
10 KiB
C#

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<UserProfileDto?>? _profileTask;
private UserProfileDto? _cachedProfile;
private DateTime _lastFetchAttempt = DateTime.MinValue;
public event Func<Task>? OnStateInvalidated;
public IdentityService(
HttpClient httpClient,
INativeStorageService storageService,
AuthenticationStateProvider? authStateProvider = null)
{
_httpClient = httpClient;
_storageService = storageService;
_authStateProvider = authStateProvider;
}
public async Task<Result> 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<Result> 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<LoginResponse>();
}
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<string>());
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<Result> 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<Result<UserProfileDto>> 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<UserProfileDto?> 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<UserProfileDto>();
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<string>());
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<Result> 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<LoginResponse>();
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<string>());
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;
}
}