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>
public LastReadBookDto? LastReadBook { get; init; }
public string[] Roles { get; init; } = Array.Empty<string>();
}
public record LastReadBookDto
@@ -48,7 +48,11 @@ public class GetUserProfileQueryHandler : IRequestHandler<GetUserProfileQuery, R
Progress = e.Progress,
LastChapter = e.LastChapter ?? "Rozpoczynanie...",
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);
@@ -6,4 +6,5 @@ public static class StorageKeys
public const string RefreshToken = "nexus_refresh_token";
public const string UserEmail = "nexus_user_email";
public const string UserTenant = "nexus_user_tenant";
public const string UserRoles = "nexus_user_roles";
}
@@ -23,7 +23,8 @@ public record UserProfile(
Guid TenantId,
SubscriptionPlanDto Plan,
int AverageQuizScore,
LastReadBookDto? LastReadBook)
LastReadBookDto? LastReadBook,
string[] Roles)
{
// Helper properties for UI compatibility
public string CurrentPlan => Plan?.Name ?? PlanConstants.DefaultPlanName;
@@ -104,11 +105,15 @@ public class IdentityService : IIdentityService
var profile = profileResult.Value;
await _storageService.SaveSecureString(StorageKeys.UserEmail, profile.Email);
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
{
(_authStateProvider as NexusAuthenticationStateProvider)?.NotifyUserAuthentication(email, "unknown");
(_authStateProvider as NexusAuthenticationStateProvider)?.NotifyUserAuthentication(email, "unknown", "");
}
return Result.Ok();
@@ -132,6 +137,7 @@ public class IdentityService : IIdentityService
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();
@@ -197,7 +203,11 @@ public class IdentityService : IIdentityService
_cachedProfile = profile;
await _storageService.SaveSecureString(StorageKeys.UserEmail, profile.Email);
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;
}
@@ -246,7 +256,11 @@ public class IdentityService : IIdentityService
var profile = profileResult.Value;
await _storageService.SaveSecureString(StorageKeys.UserEmail, profile.Email);
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();
@@ -38,10 +38,15 @@ public class NexusAuthenticationStateProvider : AuthenticationStateProvider
{
var emailResult = await _storageService.GetSecureString(StorageKeys.UserEmail);
var tenantIdResult = await _storageService.GetSecureString(StorageKeys.UserTenant);
var rolesResult = await _storageService.GetSecureString(StorageKeys.UserRoles);
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;
}
}
@@ -51,7 +56,12 @@ public class NexusAuthenticationStateProvider : AuthenticationStateProvider
if (storedEmailResult.IsSuccess && !string.IsNullOrEmpty(storedEmailResult.Value))
{
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;
}
@@ -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>
{
@@ -75,13 +85,23 @@ public class NexusAuthenticationStateProvider : AuthenticationStateProvider
new Claim(ClaimTypes.Email, email),
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);
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));
}
@@ -48,6 +48,7 @@ public class ServerIdentityService : IIdentityService
await _storageService.SaveSecureString(StorageKeys.RefreshToken, "");
await _storageService.SaveSecureString(StorageKeys.UserEmail, "");
await _storageService.SaveSecureString(StorageKeys.UserTenant, "");
await _storageService.SaveSecureString(StorageKeys.UserRoles, "");
}
catch
{
@@ -88,7 +89,8 @@ public class ServerIdentityService : IIdentityService
dto.TenantId,
dto.Plan,
dto.AverageQuizScore,
dto.LastReadBook
dto.LastReadBook,
dto.Roles
);
return Result.Ok(profile);