Fix auth, Bootstrap, Blazor nav, LDAP, and deployment pipeline for working Central UI

Bootstrap served locally with absolute paths and <base href="/">.
LDAP auth uses search-then-bind with service account for GLAuth compatibility.
CookieAuthenticationStateProvider reads HttpContext.User instead of parsing JWT.
Login/logout forms opt out of Blazor enhanced nav (data-enhance="false").
Nav links use absolute paths; seed data includes Design/Deployment group mappings.
DataConnections page loads all connections (not just site-assigned).
Site appsettings configured for Test Plant A; Site registers with Central on startup.
DeploymentService resolves string site identifier for Akka routing.
Instances page gains Create Instance form.
This commit is contained in:
Joseph Doherty
2026-03-17 10:03:06 -04:00
parent 6fa4c101ab
commit 4879c4e01e
21 changed files with 265 additions and 92 deletions

View File

@@ -2,55 +2,28 @@ using System.Security.Claims;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Components.Server;
using Microsoft.AspNetCore.Http;
using ScadaLink.Security;
namespace ScadaLink.CentralUI.Auth;
/// <summary>
/// Reads the JWT from an HTTP-only cookie and creates a ClaimsPrincipal for Blazor Server.
/// This bridges cookie-based auth (set by the login endpoint) with Blazor's auth state.
/// Bridges ASP.NET Core cookie authentication with Blazor Server's auth state.
/// The cookie middleware has already validated and decrypted the cookie by the time
/// the Blazor circuit is established, so we just read HttpContext.User.
/// </summary>
public class CookieAuthenticationStateProvider : ServerAuthenticationStateProvider
{
public const string AuthCookieName = "ScadaLink.Auth";
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly JwtTokenService _jwtTokenService;
public CookieAuthenticationStateProvider(
IHttpContextAccessor httpContextAccessor,
JwtTokenService jwtTokenService)
public CookieAuthenticationStateProvider(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
_jwtTokenService = jwtTokenService;
}
public override Task<AuthenticationState> GetAuthenticationStateAsync()
{
var httpContext = _httpContextAccessor.HttpContext;
if (httpContext == null)
{
return Task.FromResult(new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity())));
}
var user = _httpContextAccessor.HttpContext?.User
?? new ClaimsPrincipal(new ClaimsIdentity());
var token = httpContext.Request.Cookies[AuthCookieName];
if (string.IsNullOrEmpty(token))
{
return Task.FromResult(new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity())));
}
var principal = _jwtTokenService.ValidateToken(token);
if (principal == null)
{
return Task.FromResult(new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity())));
}
// Check idle timeout
if (_jwtTokenService.IsIdleTimedOut(principal))
{
return Task.FromResult(new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity())));
}
return Task.FromResult(new AuthenticationState(principal));
return Task.FromResult(new AuthenticationState(user));
}
}