using Microsoft.AspNetCore.Components.Authorization; using Microsoft.Extensions.DependencyInjection; using ZB.MOM.WW.ScadaBridge.CentralUI.Auth; using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services; using ZB.MOM.WW.ScadaBridge.Commons.Messages.Management; using ZB.MOM.WW.ScadaBridge.ManagementService; using ZB.MOM.WW.ScadaBridge.Security; namespace ZB.MOM.WW.ScadaBridge.CentralUI.Services; /// Fresh caller authorization and audited configuration commands for page event handlers. public sealed class ConfigurationCommandService(IServiceProvider services, AuthenticationStateProvider auth) { /// Runs an existing audited instance operation with fresh scope and an atomic audit commit. public async Task ExecuteInstanceAsync(int? instanceId, int? createSiteId, Func> operation) { var username = await auth.RequireRoleAsync(Roles.Deployer); var sites = new SiteScopeService(auth); try { return (T)(await services.GetRequiredService().ExecuteAsync(async sp => { var siteId = createSiteId; if (instanceId.HasValue) { var instance = await sp.GetRequiredService().GetInstanceByIdAsync(instanceId.Value); siteId = instance?.SiteId; } if (!siteId.HasValue || !await sites.IsSiteAllowedAsync(siteId.Value)) throw new UnauthorizedAccessException("Instance site access was revoked."); return await operation(sp.GetRequiredService(), username); }))!; } finally { services.GetService()?.Reset(); } } /// Executes one command through the same guarded boundary as the management actor. public async Task ExecuteAsync(object command) { var principal = (await auth.GetAuthoritativeStateAsync()).User; if (principal.Identity?.IsAuthenticated != true) throw new UnauthorizedAccessException("Session expired."); var user = new AuthenticatedUser(principal.GetUsername(), principal.GetDisplayName() ?? principal.GetUsername(), principal.FindAll(JwtTokenService.RoleClaimType).Select(c => c.Value).ToArray(), principal.FindAll(JwtTokenService.SiteIdClaimType).Select(c => c.Value).ToArray()); try { return await ManagementActor.ExecuteConfigurationCommandAsync(services, command, user, services.GetService()?.ClientAddress); } finally { services.GetService()?.Reset(); } } } /// Resolves the mutation facade only when a page acts, keeping read-only rendering independent. public static class ConfigurationCommandExtensions { /// Submits a database-only configuration mutation with current server-side authority. public static Task ExecuteConfigurationAsync(this IServiceProvider services, object command) => services.GetRequiredService().ExecuteAsync(command); }