fix(central-ui): resolve CentralUI-002/003/004 — site-scope enforcement, per-circuit console capture, cached auth state

This commit is contained in:
Joseph Doherty
2026-05-16 19:33:09 -04:00
parent 5a08b04535
commit 87f14c190a
17 changed files with 693 additions and 40 deletions

View File

@@ -5,6 +5,7 @@
@using ScadaLink.Commons.Messages.RemoteQuery
@using ScadaLink.Communication
@inject ISiteRepository SiteRepository
@inject ScadaLink.CentralUI.Auth.SiteScopeService SiteScope
@inject CommunicationService CommunicationService
<div class="container-fluid mt-3">
@@ -212,9 +213,16 @@
protected override async Task OnInitializedAsync()
{
_sites = (await SiteRepository.GetAllSitesAsync()).ToList();
// Site scoping (CentralUI-002): a scoped Deployment user may only query
// event logs for the sites they are permitted on.
_sites = await SiteScope.FilterSitesAsync(await SiteRepository.GetAllSitesAsync());
}
// _sites is already filtered, so membership IS the scope check.
private bool SelectedSiteIsPermitted =>
!string.IsNullOrEmpty(_selectedSiteId)
&& _sites.Any(s => s.SiteIdentifier == _selectedSiteId);
private async Task Search()
{
_entries = new();
@@ -237,6 +245,14 @@
{
_searching = true;
_errorMessage = null;
// Site scoping (CentralUI-002): re-check before querying — the dropdown is
// filtered, but the selection must not be trusted on its own.
if (!SelectedSiteIsPermitted)
{
_errorMessage = "You are not permitted to view event logs for that site.";
_searching = false;
return;
}
try
{
var request = new EventLogQueryRequest(

View File

@@ -6,6 +6,7 @@
@using ScadaLink.Commons.Types.Enums
@using ScadaLink.Communication
@inject ISiteRepository SiteRepository
@inject ScadaLink.CentralUI.Auth.SiteScopeService SiteScope
@inject CommunicationService CommunicationService
@inject IJSRuntime JS
@inject IDialogService Dialog
@@ -360,9 +361,17 @@
protected override async Task OnInitializedAsync()
{
_sites = (await SiteRepository.GetAllSitesAsync()).ToList();
// Site scoping (CentralUI-002): a scoped Deployment user may only inspect
// and act on parked messages for the sites they are permitted on.
_sites = await SiteScope.FilterSitesAsync(await SiteRepository.GetAllSitesAsync());
}
// True only when the currently selected SiteIdentifier is one this user is
// permitted on. _sites is already filtered, so membership IS the scope check.
private bool SelectedSiteIsPermitted =>
!string.IsNullOrEmpty(_selectedSiteId)
&& _sites.Any(s => s.SiteIdentifier == _selectedSiteId);
private async Task OnSiteChanged(ChangeEventArgs e)
{
_selectedSiteId = e.Value?.ToString() ?? string.Empty;
@@ -393,6 +402,15 @@
{
_searching = true;
_errorMessage = null;
// Site scoping (CentralUI-002): re-check before querying — the dropdown is
// filtered, but the selection must not be trusted on its own.
if (!SelectedSiteIsPermitted)
{
_errorMessage = "You are not permitted to view parked messages for that site.";
_messages = null;
_searching = false;
return;
}
try
{
var request = new ParkedMessageQueryRequest(
@@ -557,6 +575,7 @@
{
var ids = _selectedIds.ToList();
if (ids.Count == 0) return;
if (!SelectedSiteIsPermitted) { _toast.ShowError("Not permitted for this site."); return; }
var confirmed = await Dialog.ConfirmAsync(
"Retry parked messages",
@@ -587,6 +606,7 @@
{
var ids = _selectedIds.ToList();
if (ids.Count == 0) return;
if (!SelectedSiteIsPermitted) { _toast.ShowError("Not permitted for this site."); return; }
var confirmed = await Dialog.ConfirmAsync(
"Discard parked messages",
@@ -618,6 +638,7 @@
private async Task RetrySingle(ParkedMessageEntry msg)
{
if (!SelectedSiteIsPermitted) { _toast.ShowError("Not permitted for this site."); return; }
_actionInProgress = true;
_activeAction = "Retry";
try
@@ -638,6 +659,7 @@
private async Task<bool> DiscardSingle(ParkedMessageEntry msg)
{
if (!SelectedSiteIsPermitted) { _toast.ShowError("Not permitted for this site."); return false; }
var confirmed = await Dialog.ConfirmAsync(
"Discard parked message",
$"Permanently discard message {ShortId(msg.MessageId)}? This cannot be undone.",