diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Pages/Certificates.razor b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Pages/Certificates.razor
index 601aaaa9..b0be8210 100644
--- a/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Pages/Certificates.razor
+++ b/src/Server/ZB.MOM.WW.OtOpcUa.AdminUI/Components/Pages/Certificates.razor
@@ -7,6 +7,8 @@
@using ZB.MOM.WW.OtOpcUa.AdminUI.Certificates
@inject IConfiguration Config
@inject CertificateStoreManager CertManager
+@inject Microsoft.AspNetCore.Authorization.IAuthorizationService AuthorizationService
+@inject AuthenticationStateProvider AuthState
@implements IDisposable
@@ -28,7 +30,7 @@ else
{
@if (_statusMsg is not null)
{
-
+
}
@if (_pending is { } p)
{
@@ -174,15 +176,32 @@ else
private void CancelAction() => _pending = null;
- private void ConfirmAction()
+ private async Task ConfirmAction()
{
if (_pending is not { } p) return;
+
+ // Defense-in-depth: the action buttons are FleetAdmin-gated in markup, but this handler
+ // runs on the server circuit — re-check the policy before mutating the trust store.
+ var authState = await AuthState.GetAuthenticationStateAsync();
+ if (!(await AuthorizationService.AuthorizeAsync(authState.User, null, "FleetAdmin")).Succeeded)
+ {
+ _statusError = true;
+ _statusMsg = "Unauthorized — FleetAdmin required.";
+ _pending = null;
+ return;
+ }
+
var result = p.Verb switch
{
"trust" => CertManager.Trust(p.Thumbprint),
"untrust" => CertManager.Untrust(p.Thumbprint),
- "delete" => CertManager.Delete(p.Kind == StoreKind.Trusted ? "trusted" : "rejected", p.Thumbprint),
- _ => CertActionResult.Fail("unknown action"),
+ "delete" => p.Kind switch
+ {
+ StoreKind.Trusted => CertManager.Delete("trusted", p.Thumbprint),
+ StoreKind.Rejected => CertManager.Delete("rejected", p.Thumbprint),
+ _ => CertActionResult.Fail($"cannot delete from {p.Kind}"),
+ },
+ _ => CertActionResult.Fail("unknown action"),
};
_statusError = !result.Success;
_statusMsg = result.Success