fix(adminui): capture audit username at click time, not at panel init
v2-ci / build (push) Failing after 48s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped

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.
This commit is contained in:
Joseph Doherty
2026-05-28 11:58:12 -04:00
parent 662f3f9f5c
commit 0d3ec46c14
@@ -149,7 +149,6 @@
// Authorization // Authorization
private bool _canOperate; private bool _canOperate;
private string? _currentUserName;
// Action state // Action state
private bool _busyReconnect; private bool _busyReconnect;
@@ -162,8 +161,9 @@
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
// Check DriverOperator authorization so buttons only render for permitted users. // 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(); 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"); var authResult = await AuthorizationService.AuthorizeAsync(auth.User, null, "DriverOperator");
_canOperate = authResult.Succeeded; _canOperate = authResult.Succeeded;
@@ -213,8 +213,9 @@
StateHasChanged(); StateHasChanged();
try try
{ {
var userName = await GetCurrentUserNameAsync();
var result = await AdminOps.AskAsync<ReconnectDriverResult>( var result = await AdminOps.AskAsync<ReconnectDriverResult>(
new ReconnectDriver(ClusterId, DriverInstanceId, _currentUserName ?? "unknown", Guid.NewGuid()), new ReconnectDriver(ClusterId, DriverInstanceId, userName, Guid.NewGuid()),
new System.Threading.CancellationTokenSource(TimeSpan.FromSeconds(15)).Token); new System.Threading.CancellationTokenSource(TimeSpan.FromSeconds(15)).Token);
ShowOpResult(result.Ok, result.Ok ? "Reconnect dispatched" : (result.Message ?? "Failed")); ShowOpResult(result.Ok, result.Ok ? "Reconnect dispatched" : (result.Message ?? "Failed"));
} }
@@ -237,8 +238,9 @@
StateHasChanged(); StateHasChanged();
try try
{ {
var userName = await GetCurrentUserNameAsync();
var result = await AdminOps.AskAsync<RestartDriverResult>( var result = await AdminOps.AskAsync<RestartDriverResult>(
new RestartDriver(ClusterId, DriverInstanceId, _currentUserName ?? "unknown", Guid.NewGuid()), new RestartDriver(ClusterId, DriverInstanceId, userName, Guid.NewGuid()),
new System.Threading.CancellationTokenSource(TimeSpan.FromSeconds(15)).Token); new System.Threading.CancellationTokenSource(TimeSpan.FromSeconds(15)).Token);
ShowOpResult(result.Ok, result.Ok ? "Restart dispatched" : (result.Message ?? "Failed")); ShowOpResult(result.Ok, result.Ok ? "Restart dispatched" : (result.Message ?? "Failed"));
} }
@@ -253,6 +255,20 @@
} }
} }
/// <summary>
/// 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).
/// </summary>
private async Task<string> 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) private void ShowOpResult(bool ok, string message)
{ {
_opResultOk = ok; _opResultOk = ok;