Refactor: Web Consolidation and Identity Stabilization #40

Merged
mjasin merged 37 commits from feature/issue-33 into develop 2026-05-11 19:16:31 +00:00
6 changed files with 55 additions and 12 deletions
Showing only changes of commit 0c3fccc91e - Show all commits
@@ -17,6 +17,8 @@ public record UserProfileDto
/// Summary of the last read book. /// Summary of the last read book.
/// </summary> /// </summary>
public LastReadBookDto? LastReadBook { get; init; } public LastReadBookDto? LastReadBook { get; init; }
public string[] Roles { get; init; } = Array.Empty<string>();
} }
public record LastReadBookDto public record LastReadBookDto
@@ -48,7 +48,11 @@ public class GetUserProfileQueryHandler : IRequestHandler<GetUserProfileQuery, R
Progress = e.Progress, Progress = e.Progress,
LastChapter = e.LastChapter ?? "Rozpoczynanie...", LastChapter = e.LastChapter ?? "Rozpoczynanie...",
LastChapterIndex = e.LastChapterIndex LastChapterIndex = e.LastChapterIndex
}).FirstOrDefault() }).FirstOrDefault(),
Roles = dbContext.UserRoles
.Where(ur => ur.UserId == u.Id)
.Join(dbContext.Roles, ur => ur.RoleId, r => r.Id, (ur, r) => r.Name!)
.ToArray()
}) })
.FirstOrDefaultAsync(cancellationToken); .FirstOrDefaultAsync(cancellationToken);
@@ -6,4 +6,5 @@ public static class StorageKeys
public const string RefreshToken = "nexus_refresh_token"; public const string RefreshToken = "nexus_refresh_token";
public const string UserEmail = "nexus_user_email"; public const string UserEmail = "nexus_user_email";
public const string UserTenant = "nexus_user_tenant"; public const string UserTenant = "nexus_user_tenant";
public const string UserRoles = "nexus_user_roles";
} }
@@ -23,7 +23,8 @@ public record UserProfile(
Guid TenantId, Guid TenantId,
SubscriptionPlanDto Plan, SubscriptionPlanDto Plan,
int AverageQuizScore, int AverageQuizScore,
LastReadBookDto? LastReadBook) LastReadBookDto? LastReadBook,
string[] Roles)
{ {
// Helper properties for UI compatibility // Helper properties for UI compatibility
public string CurrentPlan => Plan?.Name ?? PlanConstants.DefaultPlanName; public string CurrentPlan => Plan?.Name ?? PlanConstants.DefaultPlanName;
@@ -104,11 +105,15 @@ public class IdentityService : IIdentityService
var profile = profileResult.Value; var profile = profileResult.Value;
await _storageService.SaveSecureString(StorageKeys.UserEmail, profile.Email); await _storageService.SaveSecureString(StorageKeys.UserEmail, profile.Email);
await _storageService.SaveSecureString(StorageKeys.UserTenant, profile.TenantId.ToString()); await _storageService.SaveSecureString(StorageKeys.UserTenant, profile.TenantId.ToString());
(_authStateProvider as NexusAuthenticationStateProvider)?.NotifyUserAuthentication(profile.Email, 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 else
{ {
(_authStateProvider as NexusAuthenticationStateProvider)?.NotifyUserAuthentication(email, "unknown"); (_authStateProvider as NexusAuthenticationStateProvider)?.NotifyUserAuthentication(email, "unknown", "");
} }
return Result.Ok(); return Result.Ok();
@@ -132,6 +137,7 @@ public class IdentityService : IIdentityService
await _storageService.SaveSecureString(RefreshTokenKey, ""); await _storageService.SaveSecureString(RefreshTokenKey, "");
await _storageService.SaveSecureString(StorageKeys.UserEmail, ""); await _storageService.SaveSecureString(StorageKeys.UserEmail, "");
await _storageService.SaveSecureString(StorageKeys.UserTenant, ""); await _storageService.SaveSecureString(StorageKeys.UserTenant, "");
await _storageService.SaveSecureString(StorageKeys.UserRoles, "");
} }
if (OnStateInvalidated != null) await OnStateInvalidated.Invoke(); if (OnStateInvalidated != null) await OnStateInvalidated.Invoke();
@@ -197,7 +203,11 @@ public class IdentityService : IIdentityService
_cachedProfile = profile; _cachedProfile = profile;
await _storageService.SaveSecureString(StorageKeys.UserEmail, profile.Email); await _storageService.SaveSecureString(StorageKeys.UserEmail, profile.Email);
await _storageService.SaveSecureString(StorageKeys.UserTenant, profile.TenantId.ToString()); await _storageService.SaveSecureString(StorageKeys.UserTenant, profile.TenantId.ToString());
(_authStateProvider as NexusAuthenticationStateProvider)?.NotifyUserAuthentication(profile.Email, 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 profile;
} }
@@ -246,7 +256,11 @@ public class IdentityService : IIdentityService
var profile = profileResult.Value; var profile = profileResult.Value;
await _storageService.SaveSecureString(StorageKeys.UserEmail, profile.Email); await _storageService.SaveSecureString(StorageKeys.UserEmail, profile.Email);
await _storageService.SaveSecureString(StorageKeys.UserTenant, profile.TenantId.ToString()); await _storageService.SaveSecureString(StorageKeys.UserTenant, profile.TenantId.ToString());
(_authStateProvider as NexusAuthenticationStateProvider)?.NotifyUserAuthentication(profile.Email, 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.Ok();
@@ -38,10 +38,15 @@ public class NexusAuthenticationStateProvider : AuthenticationStateProvider
{ {
var emailResult = await _storageService.GetSecureString(StorageKeys.UserEmail); var emailResult = await _storageService.GetSecureString(StorageKeys.UserEmail);
var tenantIdResult = await _storageService.GetSecureString(StorageKeys.UserTenant); var tenantIdResult = await _storageService.GetSecureString(StorageKeys.UserTenant);
var rolesResult = await _storageService.GetSecureString(StorageKeys.UserRoles);
if (emailResult.IsSuccess && !string.IsNullOrEmpty(emailResult.Value)) if (emailResult.IsSuccess && !string.IsNullOrEmpty(emailResult.Value))
{ {
_cachedState = CreateState(emailResult.Value, tenantIdResult.IsSuccess ? tenantIdResult.Value! : "unknown", "OpaqueBearer"); _cachedState = CreateState(
emailResult.Value,
tenantIdResult.IsSuccess ? tenantIdResult.Value! : "unknown",
"OpaqueBearer",
rolesResult.IsSuccess ? rolesResult.Value! : "");
return _cachedState; return _cachedState;
} }
} }
@@ -51,7 +56,12 @@ public class NexusAuthenticationStateProvider : AuthenticationStateProvider
if (storedEmailResult.IsSuccess && !string.IsNullOrEmpty(storedEmailResult.Value)) if (storedEmailResult.IsSuccess && !string.IsNullOrEmpty(storedEmailResult.Value))
{ {
var tenantIdResult = await _storageService.GetSecureString(StorageKeys.UserTenant); var tenantIdResult = await _storageService.GetSecureString(StorageKeys.UserTenant);
_cachedState = CreateState(storedEmailResult.Value, tenantIdResult.IsSuccess ? tenantIdResult.Value! : "unknown", "CookieAuth"); var rolesResult = await _storageService.GetSecureString(StorageKeys.UserRoles);
_cachedState = CreateState(
storedEmailResult.Value,
tenantIdResult.IsSuccess ? tenantIdResult.Value! : "unknown",
"CookieAuth",
rolesResult.IsSuccess ? rolesResult.Value! : "");
return _cachedState; return _cachedState;
} }
@@ -67,7 +77,7 @@ public class NexusAuthenticationStateProvider : AuthenticationStateProvider
} }
} }
private AuthenticationState CreateState(string email, string tenantId, string authType) private AuthenticationState CreateState(string email, string tenantId, string authType, string rolesStr = "")
{ {
var claims = new List<Claim> var claims = new List<Claim>
{ {
@@ -75,13 +85,23 @@ public class NexusAuthenticationStateProvider : AuthenticationStateProvider
new Claim(ClaimTypes.Email, email), new Claim(ClaimTypes.Email, email),
new Claim("TenantId", tenantId) new Claim("TenantId", tenantId)
}; };
if (!string.IsNullOrEmpty(rolesStr))
{
var roles = rolesStr.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var role in roles)
{
claims.Add(new Claim(ClaimTypes.Role, role.Trim()));
}
}
var identity = new ClaimsIdentity(claims, authType); var identity = new ClaimsIdentity(claims, authType);
return new AuthenticationState(new ClaimsPrincipal(identity)); return new AuthenticationState(new ClaimsPrincipal(identity));
} }
public void NotifyUserAuthentication(string email, string tenantId) public void NotifyUserAuthentication(string email, string tenantId, string rolesStr = "")
{ {
_cachedState = CreateState(email, tenantId, "OpaqueBearer"); _cachedState = CreateState(email, tenantId, "OpaqueBearer", rolesStr);
NotifyAuthenticationStateChanged(Task.FromResult(_cachedState)); NotifyAuthenticationStateChanged(Task.FromResult(_cachedState));
} }
@@ -48,6 +48,7 @@ public class ServerIdentityService : IIdentityService
await _storageService.SaveSecureString(StorageKeys.RefreshToken, ""); await _storageService.SaveSecureString(StorageKeys.RefreshToken, "");
await _storageService.SaveSecureString(StorageKeys.UserEmail, ""); await _storageService.SaveSecureString(StorageKeys.UserEmail, "");
await _storageService.SaveSecureString(StorageKeys.UserTenant, ""); await _storageService.SaveSecureString(StorageKeys.UserTenant, "");
await _storageService.SaveSecureString(StorageKeys.UserRoles, "");
} }
catch catch
{ {
@@ -88,7 +89,8 @@ public class ServerIdentityService : IIdentityService
dto.TenantId, dto.TenantId,
dto.Plan, dto.Plan,
dto.AverageQuizScore, dto.AverageQuizScore,
dto.LastReadBook dto.LastReadBook,
dto.Roles
); );
return Result.Ok(profile); return Result.Ok(profile);