Resolve all 46 low-severity architecture review findings

This commit is contained in:
Joseph Doherty
2026-09-09 22:56:36 -04:00
parent fef3e7675f
commit 9ee402bbcd
139 changed files with 2621 additions and 1082 deletions
@@ -0,0 +1,17 @@
using Microsoft.AspNetCore.Components.Authorization;
using ZB.MOM.WW.ScadaBridge.CentralUI.Auth;
using ZB.MOM.WW.ScadaBridge.Security;
namespace ZB.MOM.WW.ScadaBridge.CentralUI.Services;
/// <summary>Scoped application boundary that checks current circuit authority before host failover.</summary>
public sealed class AuthorizedManualFailoverService(AuthenticationStateProvider auth, IManualFailoverService backend)
{
/// <summary>Fails over central only for a currently authorized administrator.</summary>
public async Task<string?> FailOverCentralAsync()
=> await backend.FailOverCentralAsync(await auth.RequireRoleAsync(Roles.Administrator));
/// <summary>Fails over a site only for a currently authorized administrator.</summary>
public async Task<SiteFailoverOutcome> FailOverSiteAsync(string siteId)
=> await backend.FailOverSiteAsync(siteId, await auth.RequireRoleAsync(Roles.Administrator));
}
@@ -84,6 +84,7 @@ public sealed class FlowDebugService : IFlowDebugService
private readonly ManagementActorHolder _holder;
private readonly AuthenticationStateProvider _auth;
private readonly IInboundApiRepository _inboundApi;
private readonly ISiteRepository _sites;
private readonly ITemplateEngineRepository _templates;
private readonly IFlatteningPipeline _pipeline;
private readonly CommunicationService _comms;
@@ -116,8 +117,10 @@ public sealed class FlowDebugService : IFlowDebugService
ITemplateEngineRepository templates,
IFlatteningPipeline pipeline,
CommunicationService comms,
ILogger<FlowDebugService> logger)
ILogger<FlowDebugService> logger,
ISiteRepository sites)
{
_sites = sites ?? throw new ArgumentNullException(nameof(sites));
_holder = holder ?? throw new ArgumentNullException(nameof(holder));
_auth = auth ?? throw new ArgumentNullException(nameof(auth));
_inboundApi = inboundApi ?? throw new ArgumentNullException(nameof(inboundApi));
@@ -322,6 +325,13 @@ public sealed class FlowDebugService : IFlowDebugService
}
var site = step.SiteId!;
var scope = new SiteScopeService(_auth);
if (!await scope.IsSystemWideAsync())
{
var owner = (await _sites.GetAllSitesAsync(ct)).FirstOrDefault(s => s.SiteIdentifier == site);
if (owner is null || !await scope.IsSiteAllowedAsync(owner.Id))
return new(false, "You are not permitted to act on cached calls for this site.");
}
SiteCallRelayOutcome outcome;
bool siteReachable;
string? errorMessage;
@@ -12,6 +12,15 @@ public sealed class PollGate
/// may proceed; <see langword="false"/> if a previous refresh is still running.</returns>
public bool TryEnter() => Interlocked.CompareExchange(ref _inFlight, 1, 0) == 0;
/// <summary>Runs and observes a posted refresh, releasing the gate even if dispatch is rejected.</summary>
public async Task RunAsync(Func<Task> dispatch, Action<Exception> onError)
{
if (!TryEnter()) return;
try { await dispatch().ConfigureAwait(false); }
catch (Exception error) { onError(error); }
finally { Exit(); }
}
/// <summary>Releases the gate after a poll tick's refresh has completed.</summary>
public void Exit() => Volatile.Write(ref _inFlight, 0);
}