fix: migrate to IDbContextFactory and remove direct AppDbContext from DI (#11)

Reviewed-on: #11
Co-authored-by: Marek Jasiński <jasins.marek@gmail.com>
Co-committed-by: Marek Jasiński <jasins.marek@gmail.com>
This commit was merged in pull request #11.
This commit is contained in:
2026-05-07 16:39:21 +00:00
committed by Marek Jaisński
parent 3faecbb639
commit 2248a2b757
35 changed files with 983 additions and 215 deletions
@@ -6,7 +6,7 @@ namespace NexusReader.UI.Shared.Services;
public interface IIdentityService
{
Task<bool> RegisterAsync(string email, string password);
Task<bool> LoginAsync(string email, string password);
Task<bool> LoginAsync(string email, string password, bool rememberMe = false);
Task LogoutAsync();
Task<UserProfile?> GetProfileAsync();
Task<bool> RefreshTokenAsync();
@@ -45,7 +45,7 @@ public class IdentityService : IIdentityService
return response.IsSuccessStatusCode;
}
public async Task<bool> LoginAsync(string email, string password)
public async Task<bool> LoginAsync(string email, string password, bool rememberMe = false)
{
var response = await _httpClient.PostAsJsonAsync("identity/login", new { email, password });
@@ -59,7 +59,22 @@ public class IdentityService : IIdentityService
{
await _storageService.SaveSecureString(RefreshTokenKey, result.RefreshToken);
}
_authStateProvider.NotifyUserAuthentication(result.AccessToken);
// Option A: Fetch profile to get claims
var profile = await GetProfileAsync();
if (profile != null)
{
await _storageService.SaveSecureString("nexus_user_email", profile.Email);
await _storageService.SaveSecureString("nexus_user_tenant", profile.TenantId.ToString());
_authStateProvider.NotifyUserAuthentication(profile.Email, profile.TenantId.ToString());
}
else
{
// Fallback if profile fetch fails
_authStateProvider.NotifyUserAuthentication(email, "unknown");
}
return true;
}
}
@@ -71,6 +86,8 @@ public class IdentityService : IIdentityService
{
_storageService.RemoveSecure(TokenKey);
_storageService.RemoveSecure(RefreshTokenKey);
_storageService.RemoveSecure("nexus_user_email");
_storageService.RemoveSecure("nexus_user_tenant");
_authStateProvider.NotifyUserLogout();
}
@@ -105,7 +122,15 @@ public class IdentityService : IIdentityService
{
await _storageService.SaveSecureString(RefreshTokenKey, loginResult.RefreshToken);
}
_authStateProvider.NotifyUserAuthentication(loginResult.AccessToken);
var profile = await GetProfileAsync();
if (profile != null)
{
await _storageService.SaveSecureString("nexus_user_email", profile.Email);
await _storageService.SaveSecureString("nexus_user_tenant", profile.TenantId.ToString());
_authStateProvider.NotifyUserAuthentication(profile.Email, profile.TenantId.ToString());
}
return true;
}
}
@@ -19,15 +19,30 @@ public class NexusAuthenticationStateProvider : AuthenticationStateProvider
{
try
{
var result = await _storageService.GetSecureString(TokenKey);
var token = result.IsSuccess ? result.Value : null;
var tokenResult = await _storageService.GetSecureString(TokenKey);
var token = tokenResult.IsSuccess ? tokenResult.Value : null;
if (string.IsNullOrWhiteSpace(token))
{
return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
}
var identity = new ClaimsIdentity(ParseClaimsFromJwt(token), "jwt");
// For opaque tokens, we read the user info that was stored during login
var emailResult = await _storageService.GetSecureString("nexus_user_email");
var tenantIdResult = await _storageService.GetSecureString("nexus_user_tenant");
var claims = new List<Claim>();
if (emailResult.IsSuccess && !string.IsNullOrEmpty(emailResult.Value))
{
claims.Add(new Claim(ClaimTypes.Name, emailResult.Value));
claims.Add(new Claim(ClaimTypes.Email, emailResult.Value));
}
if (tenantIdResult.IsSuccess && !string.IsNullOrEmpty(tenantIdResult.Value))
{
claims.Add(new Claim("TenantId", tenantIdResult.Value));
}
var identity = new ClaimsIdentity(claims, "OpaqueBearer");
var user = new ClaimsPrincipal(identity);
return new AuthenticationState(user);
@@ -38,9 +53,16 @@ public class NexusAuthenticationStateProvider : AuthenticationStateProvider
}
}
public void NotifyUserAuthentication(string token)
public void NotifyUserAuthentication(string email, string tenantId)
{
var identity = new ClaimsIdentity(ParseClaimsFromJwt(token), "jwt");
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, email),
new Claim(ClaimTypes.Email, email),
new Claim("TenantId", tenantId)
};
var identity = new ClaimsIdentity(claims, "OpaqueBearer");
var user = new ClaimsPrincipal(identity);
var authState = Task.FromResult(new AuthenticationState(user));
NotifyAuthenticationStateChanged(authState);
@@ -52,30 +74,4 @@ public class NexusAuthenticationStateProvider : AuthenticationStateProvider
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);
}
}