docs(plan): dashboard disable-login implementation plan + tasks
This commit is contained in:
@@ -0,0 +1,447 @@
|
||||
# Dashboard "Disable Login" Dev Flag — Implementation Plan
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers-extended-cc:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Add a `MxGateway:Dashboard:DisableLogin` config flag that, when on, auto-authenticates every dashboard request as a fixed dev user (default `multi-role`) holding both dashboard roles — no login form, cookie, or LDAP bind.
|
||||
|
||||
**Architecture:** When the flag is on, the dashboard's `AddCookie(...)` registration is replaced by a custom `AuthenticationHandler` registered **under the same scheme name** (`MxGateway.Dashboard`) whose `HandleAuthenticateAsync` always succeeds with a multi-role principal. `UseAuthentication()` stamps that principal on `HttpContext.User` for every request, so every policy (Viewer/Admin/HubClients), the Blazor circuit, and the SignalR hubs see a signed-in admin with **zero policy or page changes**. Mirrors the sister project OtOpcUa's `Security:Auth:DisableLogin`.
|
||||
|
||||
**Tech Stack:** .NET 10 (x64) gateway server; ASP.NET Core authentication/authorization; xUnit. Server-side only — no worker, no `.proto`, no clients, no gRPC API-key changes. Builds and tests entirely on macOS.
|
||||
|
||||
**Design doc:** `docs/plans/2026-06-16-dashboard-disable-login-design.md`
|
||||
|
||||
**Key existing files (verified):**
|
||||
- `src/ZB.MOM.WW.MxGateway.Server/Configuration/DashboardOptions.cs` — options bound from `MxGateway:Dashboard`.
|
||||
- `src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardServiceCollectionExtensions.cs::AddGatewayDashboard` — auth scheme + policy wiring.
|
||||
- `src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardAuthenticationDefaults.cs` — scheme/policy name constants (`AuthenticationScheme = "MxGateway.Dashboard"`, `AdminPolicy`, `ViewerPolicy`).
|
||||
- `src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardRoles.cs` — `Admin = "Administrator"`, `Viewer = "Viewer"`.
|
||||
- `src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardAuthenticator.cs::CreatePrincipal` — the claim shape to mirror (`ZbClaimTypes.Name/Username/DisplayName` + `ZbClaimTypes.Role` per role; identity authType = scheme, nameType = `ZbClaimTypes.Name`, roleType = `ZbClaimTypes.Role`).
|
||||
- `ZbClaimTypes` (from `ZB.MOM.WW.Auth.AspNetCore`): `Name` (= `ClaimTypes.Name`), `Role` (= `ClaimTypes.Role`), `Username` (`"zb:username"`), `DisplayName` (`"zb:displayname"`).
|
||||
- `src/ZB.MOM.WW.MxGateway.Server/Properties/AssemblyInfo.cs` — `InternalsVisibleTo("ZB.MOM.WW.MxGateway.Tests")` (so `internal` members are test-visible).
|
||||
|
||||
**Test conventions (verified):** no Moq/NSubstitute — hand-written stubs only. Integration-style tests build the real app with `GatewayApplication.Build(["--MxGateway:Dashboard:Key=value"])` and resolve services from `app.Services` (see `DashboardCookieOptionsTests`, `DashboardHubsRegistrationTests`). Run filtered tests only (per standing guidance), with `MSBUILDDISABLENODEREUSE=1`.
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Config fields on `DashboardOptions`
|
||||
|
||||
**Classification:** small
|
||||
**Estimated implement time:** ~3 min
|
||||
**Parallelizable with:** none (Tasks 2/3 depend on these fields)
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/ZB.MOM.WW.MxGateway.Server/Configuration/DashboardOptions.cs`
|
||||
- Test: `src/ZB.MOM.WW.MxGateway.Tests/Configuration/GatewayOptionsTests.cs`
|
||||
|
||||
**Step 1: Write the failing test** — add to `GatewayOptionsTests.cs`:
|
||||
|
||||
```csharp
|
||||
[Fact]
|
||||
public void DashboardOptions_DisableLogin_DefaultsToFalse()
|
||||
{
|
||||
Assert.False(new DashboardOptions().DisableLogin);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DashboardOptions_AutoLoginUser_DefaultsToNull()
|
||||
{
|
||||
Assert.Null(new DashboardOptions().AutoLoginUser);
|
||||
}
|
||||
```
|
||||
|
||||
(If `GatewayOptionsTests` lacks `using ZB.MOM.WW.MxGateway.Server.Configuration;`, add it.)
|
||||
|
||||
**Step 2: Run it, expect FAIL** (compile error: no such members)
|
||||
|
||||
Run: `MSBUILDDISABLENODEREUSE=1 dotnet test src/ZB.MOM.WW.MxGateway.Tests/ZB.MOM.WW.MxGateway.Tests.csproj --filter "FullyQualifiedName~GatewayOptionsTests.DashboardOptions"`
|
||||
|
||||
**Step 3: Add the two `init` properties** to `DashboardOptions.cs` (place near `AllowAnonymousLocalhost`):
|
||||
|
||||
```csharp
|
||||
/// <summary>
|
||||
/// DEV/TEST ONLY. When true, the dashboard bypasses the login form entirely and
|
||||
/// auto-authenticates EVERY request as <see cref="AutoLoginUser"/> holding both
|
||||
/// dashboard roles (Administrator + Viewer). No cookie, no LDAP bind. Default false.
|
||||
/// Unlike <see cref="AllowAnonymousLocalhost"/> (which only succeeds the authorization
|
||||
/// requirement without authenticating), this mints a real principal, so the UI behaves
|
||||
/// as a signed-in admin and applies to all clients (not just loopback). Never enable in
|
||||
/// production. See docs/plans/2026-06-16-dashboard-disable-login-design.md.
|
||||
/// </summary>
|
||||
public bool DisableLogin { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Username minted for the auto-login principal when <see cref="DisableLogin"/> is true.
|
||||
/// Null/blank falls back to the GLAuth Administrator test user <c>multi-role</c>.
|
||||
/// </summary>
|
||||
public string? AutoLoginUser { get; init; }
|
||||
```
|
||||
|
||||
**Step 4: Run the test, expect PASS.**
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add src/ZB.MOM.WW.MxGateway.Server/Configuration/DashboardOptions.cs src/ZB.MOM.WW.MxGateway.Tests/Configuration/GatewayOptionsTests.cs
|
||||
git commit -m "feat(dashboard): add DisableLogin + AutoLoginUser options (default off)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: `DashboardAutoLoginAuthenticationHandler` + unit tests
|
||||
|
||||
**Classification:** high-risk (security/auth code)
|
||||
**Estimated implement time:** ~5 min
|
||||
**Parallelizable with:** Task 4
|
||||
|
||||
**Files:**
|
||||
- Create: `src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardAutoLoginAuthenticationHandler.cs`
|
||||
- Test: `src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/DashboardAutoLoginAuthenticationHandlerTests.cs`
|
||||
|
||||
**Step 1: Write the failing test** (`DashboardAutoLoginAuthenticationHandlerTests.cs`):
|
||||
|
||||
```csharp
|
||||
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);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run it, expect FAIL** (type does not exist).
|
||||
|
||||
Run: `MSBUILDDISABLENODEREUSE=1 dotnet test src/ZB.MOM.WW.MxGateway.Tests/ZB.MOM.WW.MxGateway.Tests.csproj --filter "FullyQualifiedName~DashboardAutoLoginAuthenticationHandlerTests"`
|
||||
|
||||
**Step 3: Implement** `DashboardAutoLoginAuthenticationHandler.cs`:
|
||||
|
||||
```csharp
|
||||
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);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 4: Run the test, expect PASS.**
|
||||
|
||||
**Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardAutoLoginAuthenticationHandler.cs src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/DashboardAutoLoginAuthenticationHandlerTests.cs
|
||||
git commit -m "feat(dashboard): add auto-login auth handler for DisableLogin mode"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Wire the scheme swap + startup warning + wiring/authorization tests
|
||||
|
||||
**Classification:** high-risk (security wiring)
|
||||
**Estimated implement time:** ~5 min
|
||||
**Parallelizable with:** none (depends on Task 2's handler)
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardServiceCollectionExtensions.cs`
|
||||
- Test: `src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/DashboardDisableLoginTests.cs` (create)
|
||||
|
||||
**Step 1: Write the failing tests** (`DashboardDisableLoginTests.cs`):
|
||||
|
||||
```csharp
|
||||
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);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> Note: `AuthorizeAsync` invokes the real `DashboardAuthorizationHandler` against the minted
|
||||
> principal — its role-check branch succeeds independent of `HttpContext` (loopback check
|
||||
> returns false with no request, and `Authentication.Mode` defaults to `ApiKey`), so this
|
||||
> proves the policies pass purely on the minted roles.
|
||||
|
||||
**Step 2: Run them, expect FAIL** (the `DisableLoginOn_*` tests fail — handler not yet wired; cookie handler still registered).
|
||||
|
||||
Run: `MSBUILDDISABLENODEREUSE=1 dotnet test src/ZB.MOM.WW.MxGateway.Tests/ZB.MOM.WW.MxGateway.Tests.csproj --filter "FullyQualifiedName~DashboardDisableLoginTests"`
|
||||
|
||||
**Step 3: Rewire `AddGatewayDashboard`.** In `DashboardServiceCollectionExtensions.cs`, replace the current authentication-builder block:
|
||||
|
||||
```csharp
|
||||
services
|
||||
.AddAuthentication(DashboardAuthenticationDefaults.AuthenticationScheme)
|
||||
.AddCookie(DashboardAuthenticationDefaults.AuthenticationScheme, cookieOptions =>
|
||||
{
|
||||
// ... existing cookie config ...
|
||||
})
|
||||
.AddScheme<AuthenticationSchemeOptions, HubTokenAuthenticationHandler>(
|
||||
DashboardAuthenticationDefaults.HubAuthenticationScheme,
|
||||
_ => { });
|
||||
```
|
||||
|
||||
with:
|
||||
|
||||
```csharp
|
||||
// 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 =>
|
||||
{
|
||||
// ... MOVE the existing cookie config body here unchanged ...
|
||||
});
|
||||
}
|
||||
|
||||
authentication.AddScheme<AuthenticationSchemeOptions, HubTokenAuthenticationHandler>(
|
||||
DashboardAuthenticationDefaults.HubAuthenticationScheme,
|
||||
_ => { });
|
||||
```
|
||||
|
||||
Notes for the implementer:
|
||||
- Keep the existing `services.AddOptions<CookieAuthenticationOptions>(scheme).Configure(...)` block (RequireHttpsCookie / cookie-name) as-is. When `disableLogin` is on it configures an options object no handler reads — harmless dead config; not worth guarding.
|
||||
- Required usings should already be present (`Microsoft.AspNetCore.Authentication`, `Microsoft.Extensions.Configuration`, `Microsoft.Extensions.Logging`, the `Configuration` namespace for `GatewayOptions`). Add any that are missing.
|
||||
- `configuration.GetValue<bool>` defaults to `false` when the key is absent — preserves default-off.
|
||||
|
||||
**Step 4: Run the tests, expect PASS** (all three).
|
||||
|
||||
**Step 5: Run the broader dashboard auth tests to confirm no regression:**
|
||||
|
||||
Run: `MSBUILDDISABLENODEREUSE=1 dotnet test src/ZB.MOM.WW.MxGateway.Tests/ZB.MOM.WW.MxGateway.Tests.csproj --filter "FullyQualifiedName~Dashboard"`
|
||||
Expected: all pass (existing `DashboardCookieOptionsTests`, `DashboardHubsRegistrationTests`, etc., still green — they build with the flag off).
|
||||
|
||||
> The startup warning is verified by inspection / manual run (`dotnet run … --MxGateway:Dashboard:DisableLogin=true` logs the warning once). It is not asserted automatically — capturing a startup log line would require injecting a log provider the `Build` harness does not expose, and the warning is a safety nicety, not core behavior.
|
||||
|
||||
**Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add src/ZB.MOM.WW.MxGateway.Server/Dashboard/DashboardServiceCollectionExtensions.cs src/ZB.MOM.WW.MxGateway.Tests/Gateway/Dashboard/DashboardDisableLoginTests.cs
|
||||
git commit -m "feat(dashboard): swap to auto-login handler when DisableLogin is set"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: Documentation
|
||||
|
||||
**Classification:** small
|
||||
**Estimated implement time:** ~3 min
|
||||
**Parallelizable with:** Task 2 (disjoint files — docs vs src/test)
|
||||
|
||||
**Files:**
|
||||
- Modify: `docs/GatewayConfiguration.md`
|
||||
- Modify: `docs/GatewayDashboardDesign.md`
|
||||
- Modify: `CLAUDE.md`
|
||||
|
||||
**Step 1:** In `docs/GatewayConfiguration.md`, add `MxGateway:Dashboard:DisableLogin` (bool, default `false`) and `MxGateway:Dashboard:AutoLoginUser` (string, default `multi-role`) to the dashboard options section. Describe: dev/test only; auto-authenticates every request as `AutoLoginUser` with both roles; applies to all clients (not just loopback); never enable in production. Note it differs from `AllowAnonymousLocalhost` (which only bypasses authorization without minting a principal).
|
||||
|
||||
**Step 2:** In `docs/GatewayDashboardDesign.md`, document the auth-scheme swap: when the flag is on, the cookie handler is replaced by `DashboardAutoLoginAuthenticationHandler` under the same scheme name; explain *why* (every policy resolves that scheme, so no policy/page changes), and that it is dev/test only with a loud startup warning.
|
||||
|
||||
**Step 3:** In `CLAUDE.md`, in the Authentication section near the `Dashboard:AllowAnonymousLocalhost` sentence, add one sentence: `MxGateway:Dashboard:DisableLogin` (default off) auto-authenticates every dashboard request as `AutoLoginUser` (default `multi-role`) with all roles — dev/test only.
|
||||
|
||||
**Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add docs/GatewayConfiguration.md docs/GatewayDashboardDesign.md CLAUDE.md
|
||||
git commit -m "docs: document dashboard DisableLogin / AutoLoginUser dev flag"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Verification (after all tasks)
|
||||
|
||||
```bash
|
||||
MSBUILDDISABLENODEREUSE=1 dotnet test src/ZB.MOM.WW.MxGateway.Tests/ZB.MOM.WW.MxGateway.Tests.csproj \
|
||||
--filter "FullyQualifiedName~Dashboard|FullyQualifiedName~GatewayOptions"
|
||||
```
|
||||
|
||||
Expected: all dashboard + options tests pass. (Known macOS-only failures `OrphanWorkerTerminatorTests` ×2 and the parallel-load `SqliteAuthStoreTests` TLS temp-file test are unrelated and out of this filter.)
|
||||
|
||||
Then `superpowers-extended-cc:finishing-a-development-branch` to merge/push.
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"planPath": "docs/plans/2026-06-16-dashboard-disable-login.md",
|
||||
"tasks": [
|
||||
{"id": 136, "subject": "Task 1: Config fields on DashboardOptions", "status": "pending"},
|
||||
{"id": 137, "subject": "Task 2: DashboardAutoLoginAuthenticationHandler + unit tests", "status": "pending", "blockedBy": [136]},
|
||||
{"id": 138, "subject": "Task 3: Wire scheme swap + startup warning + wiring/authorization tests", "status": "pending", "blockedBy": [137]},
|
||||
{"id": 139, "subject": "Task 4: Documentation", "status": "pending", "blockedBy": [136]}
|
||||
],
|
||||
"lastUpdated": "2026-06-16"
|
||||
}
|
||||
Reference in New Issue
Block a user