refactor: consolidate project structure by migrating authentication, identity, and shared UI components while removing legacy Web Client files.
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user