From 0d3ec46c14a2fdff20f52492b07f3e91cea045ce Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Thu, 28 May 2026 11:58:12 -0400 Subject: [PATCH] fix(adminui): capture audit username at click time, not at panel init DriverStatusPanel previously cached the username in a field at OnInitializedAsync and forwarded the cached value into RestartDriver / ReconnectDriver messages. A token refresh or claim change mid- circuit would land the stale name in the audit ConfigEdit row. Re-reads AuthenticationStateProvider at button-click time so the audit entry reflects the current principal. --- .../Shared/Drivers/DriverStatusPanel.razor | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Shared/Drivers/DriverStatusPanel.razor b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Shared/Drivers/DriverStatusPanel.razor index 709582f7..02441c01 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Shared/Drivers/DriverStatusPanel.razor +++ b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Shared/Drivers/DriverStatusPanel.razor @@ -149,7 +149,6 @@ // Authorization private bool _canOperate; - private string? _currentUserName; // Action state private bool _busyReconnect; @@ -162,8 +161,9 @@ protected override async Task OnInitializedAsync() { // Check DriverOperator authorization so buttons only render for permitted users. + // The username for audit logging is re-read at button-click time (not captured here) + // so token-refreshes mid-session land in audit entries accurately. var auth = await AuthState.GetAuthenticationStateAsync(); - _currentUserName = auth.User.Identity?.Name ?? auth.User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value ?? "unknown"; var authResult = await AuthorizationService.AuthorizeAsync(auth.User, null, "DriverOperator"); _canOperate = authResult.Succeeded; @@ -213,8 +213,9 @@ StateHasChanged(); try { + var userName = await GetCurrentUserNameAsync(); var result = await AdminOps.AskAsync( - new ReconnectDriver(ClusterId, DriverInstanceId, _currentUserName ?? "unknown", Guid.NewGuid()), + new ReconnectDriver(ClusterId, DriverInstanceId, userName, Guid.NewGuid()), new System.Threading.CancellationTokenSource(TimeSpan.FromSeconds(15)).Token); ShowOpResult(result.Ok, result.Ok ? "Reconnect dispatched" : (result.Message ?? "Failed")); } @@ -237,8 +238,9 @@ StateHasChanged(); try { + var userName = await GetCurrentUserNameAsync(); var result = await AdminOps.AskAsync( - new RestartDriver(ClusterId, DriverInstanceId, _currentUserName ?? "unknown", Guid.NewGuid()), + new RestartDriver(ClusterId, DriverInstanceId, userName, Guid.NewGuid()), new System.Threading.CancellationTokenSource(TimeSpan.FromSeconds(15)).Token); ShowOpResult(result.Ok, result.Ok ? "Restart dispatched" : (result.Message ?? "Failed")); } @@ -253,6 +255,20 @@ } } + /// + /// Re-reads the AuthenticationState at call time so the username forwarded to the + /// audit log reflects the current claims-principal — survives token refresh / role + /// change during a long-lived Blazor circuit. Returns "unknown" if no Name claim is + /// present (auth requirements upstream should normally prevent this). + /// + private async Task GetCurrentUserNameAsync() + { + var auth = await AuthState.GetAuthenticationStateAsync(); + return auth.User.Identity?.Name + ?? auth.User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value + ?? "unknown"; + } + private void ShowOpResult(bool ok, string message) { _opResultOk = ok;