260 lines
10 KiB
C#
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;
|
|
}
|
|
}
|