diff --git a/Components/Shared/AppInitializer.razor b/Components/Shared/AppInitializer.razor index 6161c77..bde1a91 100644 --- a/Components/Shared/AppInitializer.razor +++ b/Components/Shared/AppInitializer.razor @@ -1,14 +1,20 @@ @inject AuthenticationStateProvider AuthProvider @inject IUserService UserService @inject IUserContextAccessor UserContext +@inject NavigationManager Nav @inject ILogger 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. Run once per circuit to seed IUserContextAccessor. *@ +@* 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(); @@ -20,23 +26,51 @@ return; } - var email = principal.FindFirst("preferred_username")?.Value - ?? principal.FindFirst(System.Security.Claims.ClaimTypes.Email)?.Value; - if (string.IsNullOrEmpty(email)) + _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; } - var user = await UserService.GetByEmailAsync(email); + 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); + Logger.LogWarning("AppInitializer: no user row for email '{Email}' — provisioning did not persist a matching record.", _email); return; } - Logger.LogInformation("AppInitializer: seeded UserContext for '{Email}' (role {Role}).", user.Email, user.Role); + 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; }