using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.EntityFrameworkCore; using Serilog; using ZB.MOM.WW.OtOpcUa.Admin.Components; using ZB.MOM.WW.OtOpcUa.Admin.Hubs; using ZB.MOM.WW.OtOpcUa.Admin.Security; using ZB.MOM.WW.OtOpcUa.Admin.Services; using ZB.MOM.WW.OtOpcUa.Configuration; var builder = WebApplication.CreateBuilder(args); builder.Host.UseSerilog((ctx, cfg) => cfg .MinimumLevel.Information() .WriteTo.Console() .WriteTo.File("logs/otopcua-admin-.log", rollingInterval: RollingInterval.Day)); builder.Services.AddRazorComponents().AddInteractiveServerComponents(); builder.Services.AddHttpContextAccessor(); builder.Services.AddSignalR(); builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(o => { o.Cookie.Name = "OtOpcUa.Admin"; o.LoginPath = "/login"; o.ExpireTimeSpan = TimeSpan.FromHours(8); }); builder.Services.AddAuthorizationBuilder() .AddPolicy("CanEdit", p => p.RequireRole(AdminRoles.ConfigEditor, AdminRoles.FleetAdmin)) .AddPolicy("CanPublish", p => p.RequireRole(AdminRoles.FleetAdmin)); builder.Services.AddCascadingAuthenticationState(); builder.Services.AddDbContext(opt => opt.UseSqlServer(builder.Configuration.GetConnectionString("ConfigDb") ?? throw new InvalidOperationException("ConnectionStrings:ConfigDb not configured"))); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); // Cert-trust management — reads the OPC UA server's PKI store root so rejected client certs // can be promoted to trusted via the Admin UI. Singleton: no per-request state, just // filesystem operations. builder.Services.Configure(builder.Configuration.GetSection(CertTrustOptions.SectionName)); builder.Services.AddSingleton(); // LDAP auth — parity with ScadaLink's LdapAuthService (decision #102). builder.Services.Configure( builder.Configuration.GetSection("Authentication:Ldap")); builder.Services.AddScoped(); // SignalR real-time fleet status + alerts (admin-ui.md §"Real-Time Updates"). builder.Services.AddHostedService(); var app = builder.Build(); app.UseSerilogRequestLogging(); app.UseStaticFiles(); app.UseAuthentication(); app.UseAuthorization(); app.UseAntiforgery(); app.MapPost("/auth/logout", async (HttpContext ctx) => { await ctx.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); ctx.Response.Redirect("/"); }); app.MapHub("/hubs/fleet"); app.MapHub("/hubs/alerts"); app.MapRazorComponents().AddInteractiveServerRenderMode(); await app.RunAsync(); public partial class Program;