feat(dashboard): add auto-login auth handler for DisableLogin mode

This commit is contained in:
Joseph Doherty
2026-06-16 08:14:51 -04:00
parent 073252d7a6
commit 4993057ed5
2 changed files with 126 additions and 0 deletions
@@ -0,0 +1,89 @@
using System.Security.Claims;
using System.Text.Encodings.Web;
using Microsoft.AspNetCore.Authentication;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using ZB.MOM.WW.Auth.AspNetCore;
using ZB.MOM.WW.MxGateway.Server.Configuration;
namespace ZB.MOM.WW.MxGateway.Server.Dashboard;
/// <summary>
/// Authentication handler used ONLY when <c>MxGateway:Dashboard:DisableLogin</c> is true.
/// Registered under the dashboard cookie scheme name
/// (<see cref="DashboardAuthenticationDefaults.AuthenticationScheme"/>), it authenticates
/// EVERY request as the configured dev user with both dashboard roles — no credential check,
/// no cookie, no LDAP bind. The minted principal mirrors the shape the real login
/// (<see cref="DashboardAuthenticator"/>) produces, so policies and the UI cannot tell it
/// apart. DEV/TEST ONLY; never enable in production.
/// </summary>
public sealed class DashboardAutoLoginAuthenticationHandler
: AuthenticationHandler<AuthenticationSchemeOptions>, IAuthenticationSignInHandler
{
/// <summary>Username used when <c>AutoLoginUser</c> is null or blank.</summary>
public const string DefaultUser = "multi-role";
private readonly string _user;
/// <summary>Initializes the handler with scheme plumbing and the dashboard options.</summary>
/// <param name="options">The per-scheme authentication options monitor.</param>
/// <param name="logger">The logger factory the base handler uses.</param>
/// <param name="encoder">The URL encoder the base handler uses.</param>
/// <param name="gatewayOptions">Gateway options carrying the dashboard auto-login user.</param>
public DashboardAutoLoginAuthenticationHandler(
IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
IOptions<GatewayOptions> gatewayOptions)
: base(options, logger, encoder)
=> _user = gatewayOptions.Value.Dashboard.AutoLoginUser ?? DefaultUser;
/// <summary>No-op: auto-login writes no cookie, so a sign-in has nothing to persist.</summary>
/// <param name="user">Ignored.</param>
/// <param name="properties">Ignored.</param>
/// <returns>A completed task.</returns>
public Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties? properties) => Task.CompletedTask;
/// <summary>No-op: there is no auth cookie to clear; the next request re-authenticates.</summary>
/// <param name="properties">Ignored.</param>
/// <returns>A completed task.</returns>
public Task SignOutAsync(AuthenticationProperties? properties) => Task.CompletedTask;
/// <inheritdoc />
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
ClaimsPrincipal principal = CreatePrincipal(_user);
AuthenticationTicket ticket = new(principal, Scheme.Name);
return Task.FromResult(AuthenticateResult.Success(ticket));
}
/// <summary>
/// Builds the multi-role dev principal. Null/blank <paramref name="user"/> falls back to
/// <see cref="DefaultUser"/>. Claim shape mirrors <see cref="DashboardAuthenticator"/>.
/// </summary>
/// <param name="user">The configured auto-login username (may be null/blank).</param>
/// <returns>An authenticated principal holding both dashboard roles.</returns>
internal static ClaimsPrincipal CreatePrincipal(string? user)
{
string name = string.IsNullOrWhiteSpace(user) ? DefaultUser : user.Trim();
Claim[] claims =
[
new Claim(ClaimTypes.NameIdentifier, name),
new Claim(ZbClaimTypes.Username, name),
new Claim(ZbClaimTypes.Name, name),
new Claim(ZbClaimTypes.DisplayName, name),
new Claim(ZbClaimTypes.Role, DashboardRoles.Admin),
new Claim(ZbClaimTypes.Role, DashboardRoles.Viewer),
];
ClaimsIdentity identity = new(
claims,
DashboardAuthenticationDefaults.AuthenticationScheme,
ZbClaimTypes.Name,
ZbClaimTypes.Role);
return new ClaimsPrincipal(identity);
}
}
@@ -0,0 +1,37 @@
using System.Security.Claims;
using ZB.MOM.WW.MxGateway.Server.Dashboard;
namespace ZB.MOM.WW.MxGateway.Tests.Gateway.Dashboard;
public sealed class DashboardAutoLoginAuthenticationHandlerTests
{
[Fact]
public void CreatePrincipal_MintsAuthenticatedMultiRoleUser()
{
ClaimsPrincipal principal = DashboardAutoLoginAuthenticationHandler.CreatePrincipal("multi-role");
Assert.True(principal.Identity!.IsAuthenticated);
Assert.Equal("multi-role", principal.Identity!.Name);
Assert.True(principal.IsInRole(DashboardRoles.Admin));
Assert.True(principal.IsInRole(DashboardRoles.Viewer));
}
[Theory]
[InlineData(null)]
[InlineData("")]
[InlineData(" ")]
public void CreatePrincipal_BlankUser_FallsBackToDefault(string? user)
{
ClaimsPrincipal principal = DashboardAutoLoginAuthenticationHandler.CreatePrincipal(user);
Assert.Equal(DashboardAutoLoginAuthenticationHandler.DefaultUser, principal.Identity!.Name);
}
[Fact]
public void CreatePrincipal_TrimsUser()
{
ClaimsPrincipal principal = DashboardAutoLoginAuthenticationHandler.CreatePrincipal(" multi-role ");
Assert.Equal("multi-role", principal.Identity!.Name);
}
}