feat(dashboard): swap to auto-login handler when DisableLogin is set

This commit is contained in:
Joseph Doherty
2026-06-16 09:14:27 -04:00
parent 1d652b24c6
commit 3690e4c2ca
2 changed files with 94 additions and 7 deletions
@@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using ZB.MOM.WW.Auth.Abstractions.Roles;
using ZB.MOM.WW.Auth.AspNetCore;
@@ -55,9 +56,36 @@ public static class DashboardServiceCollectionExtensions
.AddInteractiveServerComponents();
services.AddSignalR();
services
.AddAuthentication(DashboardAuthenticationDefaults.AuthenticationScheme)
.AddCookie(DashboardAuthenticationDefaults.AuthenticationScheme, cookieOptions =>
// DEV/TEST ONLY. Read directly from configuration here because authentication scheme
// registration runs before options binding. Key mirrors DashboardOptions.DisableLogin.
bool disableLogin = configuration.GetValue<bool>("MxGateway:Dashboard:DisableLogin");
AuthenticationBuilder authentication =
services.AddAuthentication(DashboardAuthenticationDefaults.AuthenticationScheme);
if (disableLogin)
{
// Register an always-authenticating handler UNDER the cookie scheme name, so the
// Viewer/Admin/HubClients policies (which all resolve this scheme) authenticate
// through it as the multi-role dev user — zero policy or page changes.
authentication.AddScheme<AuthenticationSchemeOptions, DashboardAutoLoginAuthenticationHandler>(
DashboardAuthenticationDefaults.AuthenticationScheme,
_ => { });
// Loud, once-at-startup warning (emitted when GatewayOptions is first resolved).
services.AddOptions<GatewayOptions>().PostConfigure<ILoggerFactory>((gatewayOptions, loggerFactory) =>
loggerFactory
.CreateLogger("ZB.MOM.WW.MxGateway.Server.Dashboard.DisableLogin")
.LogWarning(
"DASHBOARD LOGIN DISABLED (MxGateway:Dashboard:DisableLogin=true) — every request is "
+ "authenticated as '{User}' with full permissions ({Roles}). Dev/test only; never "
+ "enable in production.",
gatewayOptions.Dashboard.AutoLoginUser ?? DashboardAutoLoginAuthenticationHandler.DefaultUser,
$"{DashboardRoles.Admin}, {DashboardRoles.Viewer}"));
}
else
{
authentication.AddCookie(DashboardAuthenticationDefaults.AuthenticationScheme, cookieOptions =>
{
// Hardened defaults (HttpOnly, SameSite=Strict, SecurePolicy, SlidingExpiration,
// ExpireTimeSpan) via the shared ZbCookieDefaults.Apply. requireHttps is set to
@@ -73,10 +101,12 @@ public static class DashboardServiceCollectionExtensions
cookieOptions.LoginPath = "/login";
cookieOptions.LogoutPath = "/logout";
cookieOptions.AccessDeniedPath = "/denied";
})
.AddScheme<AuthenticationSchemeOptions, HubTokenAuthenticationHandler>(
DashboardAuthenticationDefaults.HubAuthenticationScheme,
_ => { });
});
}
authentication.AddScheme<AuthenticationSchemeOptions, HubTokenAuthenticationHandler>(
DashboardAuthenticationDefaults.HubAuthenticationScheme,
_ => { });
// Honour DashboardOptions.RequireHttpsCookie (default true / Always; set false for dev
// HTTP deployments → SameAsRequest) and the optional per-environment cookie-name
@@ -0,0 +1,57 @@
using System.Security.Claims;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using ZB.MOM.WW.MxGateway.Server;
using ZB.MOM.WW.MxGateway.Server.Dashboard;
namespace ZB.MOM.WW.MxGateway.Tests.Gateway.Dashboard;
public sealed class DashboardDisableLoginTests
{
[Fact]
public async Task DisableLoginOff_CookieSchemeUsesCookieHandler()
{
await using WebApplication app = GatewayApplication.Build([]);
IAuthenticationSchemeProvider provider =
app.Services.GetRequiredService<IAuthenticationSchemeProvider>();
AuthenticationScheme? scheme = await provider.GetSchemeAsync(
DashboardAuthenticationDefaults.AuthenticationScheme);
Assert.NotNull(scheme);
Assert.Equal(typeof(CookieAuthenticationHandler), scheme!.HandlerType);
}
[Fact]
public async Task DisableLoginOn_CookieSchemeUsesAutoLoginHandler()
{
await using WebApplication app = GatewayApplication.Build(
["--MxGateway:Dashboard:DisableLogin=true"]);
IAuthenticationSchemeProvider provider =
app.Services.GetRequiredService<IAuthenticationSchemeProvider>();
AuthenticationScheme? scheme = await provider.GetSchemeAsync(
DashboardAuthenticationDefaults.AuthenticationScheme);
Assert.NotNull(scheme);
Assert.Equal(typeof(DashboardAutoLoginAuthenticationHandler), scheme!.HandlerType);
}
[Fact]
public async Task DisableLoginOn_AutoLoginPrincipalSatisfiesAdminAndViewerPolicies()
{
await using WebApplication app = GatewayApplication.Build(
["--MxGateway:Dashboard:DisableLogin=true"]);
IAuthorizationService authorization =
app.Services.GetRequiredService<IAuthorizationService>();
ClaimsPrincipal user = DashboardAutoLoginAuthenticationHandler.CreatePrincipal("multi-role");
Assert.True((await authorization.AuthorizeAsync(
user, resource: null, DashboardAuthenticationDefaults.AdminPolicy)).Succeeded);
Assert.True((await authorization.AuthorizeAsync(
user, resource: null, DashboardAuthenticationDefaults.ViewerPolicy)).Succeeded);
}
}