98683bbd5e
Role lived in the scoped UserContextAccessor for the circuit's lifetime and was never refreshed, so an admin promoting a user (e.g. N0 to N1) did not reach the affected user's live session. AppInitializer now re-reads the user on each LocationChanged, applying role changes on next navigation. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
77 lines
2.8 KiB
Plaintext
77 lines
2.8 KiB
Plaintext
@inject AuthenticationStateProvider AuthProvider
|
|
@inject IUserService UserService
|
|
@inject IUserContextAccessor UserContext
|
|
@inject NavigationManager Nav
|
|
@inject ILogger<AppInitializer> Logger
|
|
@implements IDisposable
|
|
@using Microsoft.AspNetCore.Components.Authorization
|
|
@using Microsoft.AspNetCore.Components.Routing
|
|
@using SharepointToolbox.Web.Services.Auth
|
|
@using SharepointToolbox.Web.Services.Session
|
|
|
|
@* Invisible component. Seeds IUserContextAccessor on circuit init and re-reads it from the
|
|
store on every navigation, so an admin's role change applies on the user's next page change. *@
|
|
|
|
@code {
|
|
private string? _email;
|
|
|
|
protected override async Task OnInitializedAsync()
|
|
{
|
|
var state = await AuthProvider.GetAuthenticationStateAsync();
|
|
var principal = state.User;
|
|
|
|
if (principal.Identity?.IsAuthenticated != true)
|
|
{
|
|
Logger.LogWarning("AppInitializer: circuit principal NOT authenticated; UserContext left unseeded → page stays on loading.");
|
|
return;
|
|
}
|
|
|
|
_email = principal.FindFirst("preferred_username")?.Value
|
|
?? principal.FindFirst(System.Security.Claims.ClaimTypes.Email)?.Value;
|
|
if (string.IsNullOrEmpty(_email))
|
|
{
|
|
var claims = string.Join(", ", principal.Claims.Select(c => $"{c.Type}={c.Value}"));
|
|
Logger.LogWarning("AppInitializer: authenticated but no preferred_username/email claim. Claims present: [{Claims}]", claims);
|
|
return;
|
|
}
|
|
|
|
await SeedAsync(logSeed: true);
|
|
|
|
// Re-read the user (and current role) from the store on each navigation. The role lives
|
|
// in the scoped UserContextAccessor for the circuit's lifetime, so without this a role
|
|
// change made by an admin would not reach the affected user's live session.
|
|
Nav.LocationChanged += OnLocationChanged;
|
|
}
|
|
|
|
private async Task SeedAsync(bool logSeed)
|
|
{
|
|
if (string.IsNullOrEmpty(_email)) return;
|
|
|
|
var user = await UserService.GetByEmailAsync(_email);
|
|
if (user is null)
|
|
{
|
|
Logger.LogWarning("AppInitializer: no user row for email '{Email}' — provisioning did not persist a matching record.", _email);
|
|
return;
|
|
}
|
|
|
|
if (logSeed)
|
|
Logger.LogInformation("AppInitializer: seeded UserContext for '{Email}' (role {Role}).", user.Email, user.Role);
|
|
|
|
UserContext.Initialize(user);
|
|
}
|
|
|
|
private async void OnLocationChanged(object? sender, LocationChangedEventArgs e)
|
|
{
|
|
try
|
|
{
|
|
await SeedAsync(logSeed: false);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger.LogWarning(ex, "AppInitializer: failed to refresh UserContext on navigation to '{Location}'.", e.Location);
|
|
}
|
|
}
|
|
|
|
public void Dispose() => Nav.LocationChanged -= OnLocationChanged;
|
|
}
|