refactor: consolidate project structure by migrating authentication, identity, and shared UI components while removing legacy Web Client files.

This commit is contained in:
2026-04-28 20:23:40 +02:00
parent 131981992c
commit 10efed0369
124 changed files with 2822 additions and 2213 deletions
@@ -0,0 +1,87 @@
using System.Net.Http.Json;
using NexusReader.Application.Abstractions.Services;
namespace NexusReader.UI.Shared.Services;
public interface IIdentityService
{
Task<bool> RegisterAsync(string email, string password);
Task<bool> LoginAsync(string email, string password);
Task LogoutAsync();
Task<UserProfile?> GetProfileAsync();
}
public record UserProfile(
string Email,
int AITokenLimit,
int AITokensUsed,
string CurrentPlan,
Guid TenantId);
public class IdentityService : IIdentityService
{
private readonly HttpClient _httpClient;
private readonly INativeStorageService _storageService;
private readonly NexusAuthenticationStateProvider _authStateProvider;
private const string TokenKey = "nexus_auth_token";
public IdentityService(
HttpClient httpClient,
INativeStorageService storageService,
NexusAuthenticationStateProvider authStateProvider)
{
_httpClient = httpClient;
_storageService = storageService;
_authStateProvider = authStateProvider;
}
public async Task<bool> RegisterAsync(string email, string password)
{
var response = await _httpClient.PostAsJsonAsync("identity/register", new { email, password });
return response.IsSuccessStatusCode;
}
public async Task<bool> LoginAsync(string email, string password)
{
var response = await _httpClient.PostAsJsonAsync("identity/login", new { email, password });
if (response.IsSuccessStatusCode)
{
var result = await response.Content.ReadFromJsonAsync<LoginResponse>();
if (result != null && !string.IsNullOrEmpty(result.AccessToken))
{
await _storageService.SaveSecureString(TokenKey, result.AccessToken);
_authStateProvider.NotifyUserAuthentication(result.AccessToken);
return true;
}
}
return false;
}
public async Task LogoutAsync()
{
_storageService.RemoveSecure(TokenKey);
_authStateProvider.NotifyUserLogout();
}
public async Task<UserProfile?> GetProfileAsync()
{
try
{
return await _httpClient.GetFromJsonAsync<UserProfile>("identity/profile");
}
catch
{
return null;
}
}
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;
}
}
@@ -0,0 +1,81 @@
using System.Security.Claims;
using System.Text.Json;
using Microsoft.AspNetCore.Components.Authorization;
using NexusReader.Application.Abstractions.Services;
namespace NexusReader.UI.Shared.Services;
public class NexusAuthenticationStateProvider : AuthenticationStateProvider
{
private readonly INativeStorageService _storageService;
private const string TokenKey = "nexus_auth_token";
public NexusAuthenticationStateProvider(INativeStorageService storageService)
{
_storageService = storageService;
}
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
try
{
var result = await _storageService.GetSecureString(TokenKey);
var token = result.IsSuccess ? result.Value : null;
if (string.IsNullOrWhiteSpace(token))
{
return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
}
var identity = new ClaimsIdentity(ParseClaimsFromJwt(token), "jwt");
var user = new ClaimsPrincipal(identity);
return new AuthenticationState(user);
}
catch (Exception)
{
return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
}
}
public void NotifyUserAuthentication(string token)
{
var identity = new ClaimsIdentity(ParseClaimsFromJwt(token), "jwt");
var user = new ClaimsPrincipal(identity);
var authState = Task.FromResult(new AuthenticationState(user));
NotifyAuthenticationStateChanged(authState);
}
public void NotifyUserLogout()
{
var guest = new ClaimsPrincipal(new ClaimsIdentity());
var authState = Task.FromResult(new AuthenticationState(guest));
NotifyAuthenticationStateChanged(authState);
}
private IEnumerable<Claim> ParseClaimsFromJwt(string jwt)
{
var claims = new List<Claim>();
var payload = jwt.Split('.')[1];
var jsonBytes = ParseBase64WithoutPadding(payload);
var keyValuePairs = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonBytes);
if (keyValuePairs != null)
{
claims.AddRange(keyValuePairs.Select(kvp => new Claim(kvp.Key, kvp.Value.ToString() ?? string.Empty)));
}
return claims;
}
private byte[] ParseBase64WithoutPadding(string base64)
{
switch (base64.Length % 4)
{
case 2: base64 += "=="; break;
case 3: base64 += "="; break;
}
return Convert.FromBase64String(base64);
}
}