docs(adminui): implementation plan for Security:Auth:DisableLogin dev flag
6-task plan (T0 branch -> T1 options/roles -> T2 handler -> T3 wiring -> T5 verify; T4 config+docker-dev parallel). AutoLoginAuthenticationHandler registered under the cookie scheme name so existing policies keep working; enabled in docker-dev.
This commit is contained in:
@@ -0,0 +1,417 @@
|
||||
# AdminUI "Disable Login" Dev Flag — Implementation Plan
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers-extended-cc:subagent-driven-development
|
||||
> (or executing-plans) to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Add a `Security:Auth:DisableLogin` config flag that disables AdminUI login —
|
||||
when on, every request is auto-authenticated as `multi-role-test` with all roles — and
|
||||
enable it in docker-dev.
|
||||
|
||||
**Architecture:** A custom `AuthenticationHandler` registered **under the cookie scheme
|
||||
name** when the flag is on (replacing `AddCookie`); it always returns
|
||||
`AuthenticateResult.Success` with an all-roles principal. Because the `FallbackPolicy` +
|
||||
`FleetAdmin` + `DriverOperator` policies all *name the cookie scheme*, they keep working
|
||||
unchanged. `HttpContext.User` is the single source feeding both the HTTP pipeline and the
|
||||
Blazor circuit. Design: `docs/plans/2026-06-11-adminui-disable-login-design.md` (master
|
||||
`78917673`).
|
||||
|
||||
**Tech Stack:** .NET 10, ASP.NET Core cookie/authentication, Blazor Server, xUnit +
|
||||
Shouldly. No bUnit.
|
||||
|
||||
**Hard rules (every task):** stage by explicit path — never `git add .`; never stage
|
||||
`sql_login.txt` or `src/Server/ZB.MOM.WW.OtOpcUa.Host/pki/`; never echo the gateway API
|
||||
key into a **new** tracked file (the compose edit touches an existing file that already
|
||||
has it); no force-push, no `--no-verify`. **No Configuration entity / EF migration
|
||||
change.** Agent does **not** sign in to the AdminUI — when the flag is on, no sign-in is
|
||||
needed (that's the point).
|
||||
|
||||
**Branch:** `feat/adminui-disable-login` off `master @ 78917673`.
|
||||
|
||||
**Verified context (from the brainstorming exploration — do not re-discover):**
|
||||
- Auth is wired in `src/Server/ZB.MOM.WW.OtOpcUa.Security/ServiceCollectionExtensions.cs`
|
||||
→ `AddOtOpcUaAuth` (the **only** `AddAuthentication`/`AddAuthorization` site). Cookie
|
||||
registration is at lines **72–82**; the options binds are at **36–38**; the policy block
|
||||
(`FallbackPolicy`, `DriverOperator`, `FleetAdmin`) is **113–131** and **must stay
|
||||
unchanged**.
|
||||
- Claim type helpers: **`ZbClaimTypes`** (`ZB.MOM.WW.Auth.Abstractions` — same import
|
||||
`AuthEndpoints.cs` uses) — `Name` (== `ClaimTypes.Name`), `Username`, `DisplayName`,
|
||||
`Role` (== `ClaimTypes.Role`). Mirror the principal shape `AuthEndpoints.cs:118-132`
|
||||
builds.
|
||||
- Roles: enum **`AdminRole`** (`src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Enums/AdminRole.cs`)
|
||||
= `Viewer`, `Designer`, `Administrator`; plus the appsettings-only control-plane string
|
||||
**`Operator`** (the `DriverOperator` policy accepts `Operator` or `Administrator`).
|
||||
- Tests live in `tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests/` (xUnit + Shouldly), e.g.
|
||||
`OtOpcUaLdapAuthServiceTests.cs`, `CanonicalAdminRolesTests.cs`.
|
||||
|
||||
---
|
||||
|
||||
### Task 0: Branch + baseline
|
||||
|
||||
**Classification:** small · **~2 min** · **Parallelizable with:** none
|
||||
|
||||
**Files:** none (branch + verify only)
|
||||
|
||||
**Steps:**
|
||||
1. `git switch -c feat/adminui-disable-login` (off `master @ 78917673`).
|
||||
2. `dotnet build ZB.MOM.WW.OtOpcUa.slnx` — green baseline. Confirm
|
||||
`tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests/` is in the `.slnx`.
|
||||
3. Commit nothing.
|
||||
|
||||
---
|
||||
|
||||
### Task 1: `AuthDisableLoginOptions` + centralized role list
|
||||
|
||||
**Classification:** small · **~4 min** · **Parallelizable with:** none
|
||||
|
||||
**Files:**
|
||||
- Create: `src/Server/ZB.MOM.WW.OtOpcUa.Security/Auth/AuthDisableLoginOptions.cs`
|
||||
- Create: `src/Server/ZB.MOM.WW.OtOpcUa.Security/Auth/DevAuthRoles.cs`
|
||||
- Test: `tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests/DevAuthRolesTests.cs`
|
||||
|
||||
**Step 1 — failing test** (`DevAuthRolesTests`):
|
||||
- `DevAuthRoles.All` contains **every** `AdminRole` enum name (`Viewer`, `Designer`,
|
||||
`Administrator`) **and** `"Operator"`; count == 4; no duplicates.
|
||||
- (Guards the "grant all roles" contract against a future `AdminRole` addition.)
|
||||
|
||||
```csharp
|
||||
using Shouldly;
|
||||
using Xunit;
|
||||
using ZB.MOM.WW.OtOpcUa.Configuration.Enums;
|
||||
using ZB.MOM.WW.OtOpcUa.Security.Auth;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Security.Tests;
|
||||
|
||||
public class DevAuthRolesTests
|
||||
{
|
||||
[Fact]
|
||||
public void All_covers_every_AdminRole_plus_Operator()
|
||||
{
|
||||
foreach (var name in Enum.GetNames<AdminRole>())
|
||||
DevAuthRoles.All.ShouldContain(name);
|
||||
DevAuthRoles.All.ShouldContain("Operator");
|
||||
DevAuthRoles.All.Length.ShouldBe(Enum.GetNames<AdminRole>().Length + 1);
|
||||
DevAuthRoles.All.Distinct().Count().ShouldBe(DevAuthRoles.All.Length);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2 — run, expect fail** (types don't exist).
|
||||
|
||||
**Step 3 — implement:**
|
||||
```csharp
|
||||
// AuthDisableLoginOptions.cs
|
||||
namespace ZB.MOM.WW.OtOpcUa.Security.Auth;
|
||||
|
||||
/// <summary>
|
||||
/// Dev/test flag: when <see cref="DisableLogin"/> is true the AdminUI bypasses the login
|
||||
/// form entirely and auto-authenticates every request as <see cref="User"/> with all roles.
|
||||
/// Default OFF. Never enable in production.
|
||||
/// </summary>
|
||||
public sealed class AuthDisableLoginOptions
|
||||
{
|
||||
/// <summary>Configuration section name (<c>Security:Auth</c>).</summary>
|
||||
public const string SectionName = "Security:Auth";
|
||||
|
||||
/// <summary>When true, disable login and auto-authenticate every request. Default false.</summary>
|
||||
public bool DisableLogin { get; set; }
|
||||
|
||||
/// <summary>The username the auto-login principal is minted with. Default "multi-role-test".</summary>
|
||||
public string User { get; set; } = "multi-role-test";
|
||||
}
|
||||
```
|
||||
```csharp
|
||||
// DevAuthRoles.cs
|
||||
using ZB.MOM.WW.OtOpcUa.Configuration.Enums;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Security.Auth;
|
||||
|
||||
/// <summary>
|
||||
/// The full canonical role set granted to the auto-login dev principal: every
|
||||
/// <see cref="AdminRole"/> plus the appsettings-only control-plane role "Operator"
|
||||
/// (required by the DriverOperator policy). Centralised so adding an AdminRole
|
||||
/// automatically widens the grant.
|
||||
/// </summary>
|
||||
public static class DevAuthRoles
|
||||
{
|
||||
/// <summary>Operator role string — not an <see cref="AdminRole"/> enum member; used by the DriverOperator policy.</summary>
|
||||
public const string Operator = "Operator";
|
||||
|
||||
/// <summary>All roles granted to the auto-login principal.</summary>
|
||||
public static readonly string[] All =
|
||||
[.. Enum.GetNames<AdminRole>(), Operator];
|
||||
}
|
||||
```
|
||||
|
||||
**Step 4 — run, expect pass. Step 5 — commit** the 3 files by path.
|
||||
|
||||
> Verify the `ZB.MOM.WW.OtOpcUa.Security` csproj already references the `Configuration`
|
||||
> project (it imports `ZB.MOM.WW.OtOpcUa.Configuration` in `ServiceCollectionExtensions.cs`,
|
||||
> so it does). If `AdminRole` isn't resolvable, that's a plan defect — surface it.
|
||||
|
||||
---
|
||||
|
||||
### Task 2: `AutoLoginAuthenticationHandler`
|
||||
|
||||
**Classification:** high-risk · **~5 min** · **Parallelizable with:** none (depends T1)
|
||||
|
||||
**Files:**
|
||||
- Create: `src/Server/ZB.MOM.WW.OtOpcUa.Security/Auth/AutoLoginAuthenticationHandler.cs`
|
||||
- Test: `tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests/AutoLoginAuthenticationHandlerTests.cs`
|
||||
|
||||
**Step 1 — failing tests** (`AutoLoginAuthenticationHandlerTests`). Drive the handler
|
||||
directly (`InitializeAsync` with a scheme + `DefaultHttpContext`, then
|
||||
`AuthenticateAsync()`), then assert on the resulting principal:
|
||||
- Success: `result.Succeeded == true`; `result.Principal!.Identity!.Name == "multi-role-test"`
|
||||
(default) — and `== "custom"` when `AuthDisableLoginOptions.User = "custom"`.
|
||||
- Principal carries a role claim for **every** `DevAuthRoles.All` value;
|
||||
`principal.IsInRole("Administrator")`, `IsInRole("Operator")`, `IsInRole("Viewer")`,
|
||||
`IsInRole("Designer")` all true.
|
||||
- **Policy satisfaction:** build the real policies and assert the principal passes both —
|
||||
`RequireRole("Administrator")` (FleetAdmin) and `RequireRole("Operator","Administrator")`
|
||||
(DriverOperator) via `IAuthorizationService.AuthorizeAsync`. (Construct an
|
||||
`AuthorizationService` through `new ServiceCollection().AddAuthorization(...)` mirroring
|
||||
the policy block, or assert `ClaimsPrincipal.IsInRole` for each required role — the
|
||||
IsInRole assertion is sufficient and simpler; prefer it.)
|
||||
|
||||
Handler-construction helper (modern ASP.NET ctor — **no `ISystemClock`**):
|
||||
```csharp
|
||||
private static AutoLoginAuthenticationHandler CreateHandler(string user = "multi-role-test")
|
||||
{
|
||||
var schemeOpts = new Mock<IOptionsMonitor<AuthenticationSchemeOptions>>();
|
||||
schemeOpts.Setup(o => o.Get(It.IsAny<string>())).Returns(new AuthenticationSchemeOptions());
|
||||
var disableOpts = Options.Create(new AuthDisableLoginOptions { DisableLogin = true, User = user });
|
||||
var handler = new AutoLoginAuthenticationHandler(
|
||||
schemeOpts.Object, NullLoggerFactory.Instance, UrlEncoder.Default, disableOpts);
|
||||
return handler;
|
||||
}
|
||||
// In each test:
|
||||
// await handler.InitializeAsync(
|
||||
// new AuthenticationScheme(CookieAuthenticationDefaults.AuthenticationScheme, null, typeof(AutoLoginAuthenticationHandler)),
|
||||
// new DefaultHttpContext());
|
||||
// var result = await handler.AuthenticateAsync();
|
||||
```
|
||||
|
||||
**Step 2 — run, expect fail** (handler doesn't exist).
|
||||
|
||||
**Step 3 — implement:**
|
||||
```csharp
|
||||
using System.Security.Claims;
|
||||
using System.Text.Encodings.Web;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using ZB.MOM.WW.Auth.Abstractions; // ZbClaimTypes (same import AuthEndpoints uses)
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Security.Auth;
|
||||
|
||||
/// <summary>
|
||||
/// Auth handler used ONLY when <see cref="AuthDisableLoginOptions.DisableLogin"/> is true.
|
||||
/// Registered under the cookie scheme name, it authenticates EVERY request as the configured
|
||||
/// dev user with all <see cref="DevAuthRoles.All"/> roles — no credential check, no cookie.
|
||||
/// The minted principal mirrors the shape the real login (AuthEndpoints) produces.
|
||||
/// </summary>
|
||||
public sealed class AutoLoginAuthenticationHandler
|
||||
: AuthenticationHandler<AuthenticationSchemeOptions>
|
||||
{
|
||||
private readonly AuthDisableLoginOptions _opts;
|
||||
|
||||
/// <summary>Initializes the handler with the scheme plumbing and the disable-login options.</summary>
|
||||
public AutoLoginAuthenticationHandler(
|
||||
IOptionsMonitor<AuthenticationSchemeOptions> options,
|
||||
ILoggerFactory logger,
|
||||
UrlEncoder encoder,
|
||||
IOptions<AuthDisableLoginOptions> disableLoginOptions)
|
||||
: base(options, logger, encoder)
|
||||
=> _opts = disableLoginOptions.Value;
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||
{
|
||||
var user = string.IsNullOrWhiteSpace(_opts.User) ? "multi-role-test" : _opts.User;
|
||||
|
||||
var claims = new List<Claim>
|
||||
{
|
||||
new(ZbClaimTypes.Name, user),
|
||||
new(ZbClaimTypes.Username, user),
|
||||
new(ZbClaimTypes.DisplayName, user),
|
||||
};
|
||||
foreach (var role in DevAuthRoles.All)
|
||||
claims.Add(new Claim(ZbClaimTypes.Role, role));
|
||||
|
||||
var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
var principal = new ClaimsPrincipal(identity);
|
||||
var ticket = new AuthenticationTicket(principal, Scheme.Name);
|
||||
return Task.FromResult(AuthenticateResult.Success(ticket));
|
||||
}
|
||||
}
|
||||
```
|
||||
> `ClaimsIdentity(claims, authenticationType)` — passing the auth-type string makes
|
||||
> `Identity.IsAuthenticated == true` and `Identity.Name` resolve from the `ClaimTypes.Name`
|
||||
> claim. Confirm `ZbClaimTypes.Name == ClaimTypes.Name` (it is — that's why `Identity.Name`
|
||||
> works on the real login path).
|
||||
|
||||
**Step 4 — run targeted tests** (`dotnet test tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests
|
||||
--filter AutoLoginAuthenticationHandler`), expect pass. **Step 5 — commit** by path.
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Branch `AddOtOpcUaAuth` on the flag (+ loud warning)
|
||||
|
||||
**Classification:** high-risk · **~5 min** · **Parallelizable with:** none (depends T1, T2)
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/Server/ZB.MOM.WW.OtOpcUa.Security/ServiceCollectionExtensions.cs`
|
||||
(bind options near :36-38; replace the `AddAuthentication().AddCookie(...)` at :72-82 with
|
||||
the flag branch; **leave :88-111 cookie PostConfigure and :113-131 policy block
|
||||
untouched**).
|
||||
- Test: `tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests/AddOtOpcUaAuthWiringTests.cs`
|
||||
|
||||
**Step 1 — failing wiring tests** (`AddOtOpcUaAuthWiringTests`). Build a `ServiceCollection`,
|
||||
add minimal config, call `AddOtOpcUaAuth`, resolve `IAuthenticationSchemeProvider`, and
|
||||
assert the **handler type** registered for the cookie scheme:
|
||||
- `DisableLogin=true` → the cookie scheme's `HandlerType == typeof(AutoLoginAuthenticationHandler)`.
|
||||
- `DisableLogin=false` (or absent) → `HandlerType == typeof(CookieAuthenticationHandler)`.
|
||||
|
||||
```csharp
|
||||
private static async Task<Type> CookieHandlerTypeAsync(bool disableLogin)
|
||||
{
|
||||
var config = new ConfigurationBuilder().AddInMemoryCollection(new Dictionary<string, string?>
|
||||
{
|
||||
["Security:Auth:DisableLogin"] = disableLogin ? "true" : "false",
|
||||
}).Build();
|
||||
|
||||
var services = new ServiceCollection();
|
||||
services.AddLogging();
|
||||
services.AddSingleton<IConfiguration>(config);
|
||||
services.AddDbContext<OtOpcUaConfigDbContext>(o => o.UseInMemoryDatabase("wiring")); // DataProtection PersistKeysToDbContext needs it
|
||||
services.AddOtOpcUaAuth(config);
|
||||
|
||||
var sp = services.BuildServiceProvider();
|
||||
var provider = sp.GetRequiredService<IAuthenticationSchemeProvider>();
|
||||
var scheme = await provider.GetSchemeAsync(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
return scheme!.HandlerType;
|
||||
}
|
||||
```
|
||||
(If `AddDbContext`/InMemory isn't already available to the test project, prefer asserting
|
||||
via the registered `AuthenticationSchemeOptions`/`IConfigureOptions` instead — but the
|
||||
`Security.Tests` project already constructs `OtOpcUaConfigDbContext` in-memory elsewhere,
|
||||
so reuse that. If a DI dependency makes `BuildServiceProvider` throw, that's a plan defect
|
||||
— surface it rather than mocking half the graph.)
|
||||
|
||||
**Step 2 — run, expect fail.**
|
||||
|
||||
**Step 3 — implement.** Add the bind alongside the existing `AddOptions` calls:
|
||||
```csharp
|
||||
services.AddOptions<AuthDisableLoginOptions>().Bind(configuration.GetSection(AuthDisableLoginOptions.SectionName));
|
||||
```
|
||||
Replace lines 72-82 with:
|
||||
```csharp
|
||||
var disableLogin = configuration
|
||||
.GetSection(AuthDisableLoginOptions.SectionName)
|
||||
.GetValue<bool>(nameof(AuthDisableLoginOptions.DisableLogin));
|
||||
|
||||
var authBuilder = services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme);
|
||||
if (disableLogin)
|
||||
{
|
||||
// DEV/TEST ONLY: replace the cookie handler with an always-succeeding handler registered
|
||||
// UNDER the cookie scheme name, so FallbackPolicy + FleetAdmin + DriverOperator (which all
|
||||
// name this scheme) authenticate through it and pass with all roles — zero policy changes.
|
||||
authBuilder.AddScheme<AuthenticationSchemeOptions, AutoLoginAuthenticationHandler>(
|
||||
CookieAuthenticationDefaults.AuthenticationScheme, _ => { });
|
||||
|
||||
// Loud, once-at-first-resolve warning (mirrors the cookie RequireHttps warning idiom).
|
||||
services.AddOptions<AuthDisableLoginOptions>()
|
||||
.PostConfigure<ILoggerFactory>((opts, lf) =>
|
||||
lf.CreateLogger("ZB.MOM.WW.OtOpcUa.Security").LogWarning(
|
||||
"AdminUI LOGIN DISABLED (Security:Auth:DisableLogin=true) — every request is " +
|
||||
"authenticated as '{User}' with FULL permissions ({Roles}). Dev/test only; never " +
|
||||
"enable in production.", opts.User, string.Join(",", DevAuthRoles.All)));
|
||||
}
|
||||
else
|
||||
{
|
||||
authBuilder.AddCookie(o =>
|
||||
{
|
||||
o.LoginPath = "/login";
|
||||
o.LogoutPath = "/auth/logout";
|
||||
});
|
||||
}
|
||||
```
|
||||
Add `using ZB.MOM.WW.OtOpcUa.Security.Auth;` and
|
||||
`using Microsoft.AspNetCore.Authentication;`.
|
||||
|
||||
> The cookie-options `PostConfigure` (`AddOptions<CookieAuthenticationOptions>(...)`, :88-111)
|
||||
> stays — it's keyed to the cookie scheme name and is harmless/ignored when the auto-login
|
||||
> handler backs that scheme. Do NOT delete it. DataProtection, LDAP, JWT, and the
|
||||
> `AddAuthorization` policy block all stay exactly as-is.
|
||||
|
||||
**Step 4 — run** (`dotnet test tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests`), expect pass +
|
||||
no regressions in the existing auth tests. **Step 5 — commit** by path.
|
||||
|
||||
---
|
||||
|
||||
### Task 4: appsettings default + docker-dev enablement
|
||||
|
||||
**Classification:** small · **~3 min** · **Parallelizable with:** Task 2, Task 3 (disjoint files; depends T1 for the section name)
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/Server/ZB.MOM.WW.OtOpcUa.Host/appsettings.json` (add the default-off key under
|
||||
`Security`).
|
||||
- Modify: `docker-dev/docker-compose.yml` (enable on `central-1` ~:139 and `central-2` ~:179).
|
||||
|
||||
**Steps:**
|
||||
1. **appsettings.json** — add `"Auth": { "DisableLogin": false }` inside the existing
|
||||
`"Security"` object (verify the section exists; merge, don't duplicate). This documents
|
||||
the key and keeps prod default OFF.
|
||||
2. **docker-compose.yml** — `git status docker-dev/docker-compose.yml` first (it may carry
|
||||
unrelated working-tree edits — leave those alone). With a **targeted `Edit`** (NOT
|
||||
`git add .`), add to the `environment:` block of **both** AdminUI nodes, next to the
|
||||
other `Security__*` keys:
|
||||
```yaml
|
||||
Security__Auth__DisableLogin: "true"
|
||||
```
|
||||
- `central-1` block (~:139, the `&otopcua-host` anchor).
|
||||
- `central-2` block (~:179, it has its own full `environment:` list — add there too).
|
||||
- Do **not** touch the site-* nodes (driver-only, no UI). Do **not** alter the
|
||||
`GALAXY_MXGW_API_KEY` line.
|
||||
3. **Commit** `src/Server/ZB.MOM.WW.OtOpcUa.Host/appsettings.json` and
|
||||
`docker-dev/docker-compose.yml` **by explicit path**. If the compose file had unrelated
|
||||
pre-existing edits you did not make, stage **only** your hunk is not possible per-line
|
||||
here — instead surface the pre-existing diff to the user and ask before committing the
|
||||
whole file (do not silently bundle someone else's change).
|
||||
|
||||
> No test (config only). Proven by Task 5's live `/run`.
|
||||
|
||||
---
|
||||
|
||||
### Task 5: Live-verify (docker-dev `/run`)
|
||||
|
||||
**Classification:** verification · **Parallelizable with:** none (depends T3, T4)
|
||||
|
||||
**Steps:**
|
||||
1. Rebuild docker-dev so central-1/central-2 pick up the new image + the
|
||||
`Security__Auth__DisableLogin=true` env:
|
||||
`docker compose -f docker-dev/docker-compose.yml up -d --build` (user drives if a sign-in
|
||||
to anything external is needed — but the AdminUI itself needs none now).
|
||||
2. Browse `http://localhost:9200/` — confirm it loads **straight into the app** (no
|
||||
redirect to `/login`) as **`multi-role-test`**: the Account page shows that user with all
|
||||
roles; a `FleetAdmin`-gated page (e.g. **RoleGrants**) renders; a `DriverOperator` action
|
||||
(DriverStatusPanel Reconnect/Restart) is enabled.
|
||||
3. Confirm the **loud warning** is in the central node logs at startup.
|
||||
4. **Agent does not sign in** (none required). Record outcome. Any defect → new fix task.
|
||||
5. After green: run superpowers-extended-cc:finishing-a-development-branch (full
|
||||
`dotnet test`, then merge to master). Note: leave `Security__Auth__DisableLogin=true` in
|
||||
docker-dev (that's the requested dev state).
|
||||
|
||||
---
|
||||
|
||||
## Execution notes
|
||||
|
||||
- **Serial spine:** T0 → T1 → T2 → T3 → T5. **T4 ∥ T2/T3** (disjoint files; only needs the
|
||||
section name from T1).
|
||||
- **One writer:** only T3 touches `ServiceCollectionExtensions.cs`; T1/T2 add new files;
|
||||
T4 touches config files. No shared-file contention.
|
||||
- **Security classification:** T2 + T3 are `high-risk` (they mint an auth principal and
|
||||
rewire the scheme registration) → serial spec→code review. T1/T4 are `small`.
|
||||
- **Checkpoint:** after T3 the flag works in-process (proven by unit tests); T4+T5 land it
|
||||
in docker-dev. Natural pause after T3 before the live run.
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"planPath": "docs/plans/2026-06-11-adminui-disable-login.md",
|
||||
"designPath": "docs/plans/2026-06-11-adminui-disable-login-design.md",
|
||||
"branch": "feat/adminui-disable-login",
|
||||
"baseBranch": "master",
|
||||
"baseSha": "78917673",
|
||||
"status": "pending",
|
||||
"note": "Security:Auth:DisableLogin flag — auto-authenticate AdminUI as multi-role-test with all roles via an AutoLoginAuthenticationHandler registered UNDER the cookie scheme name (so FallbackPolicy + FleetAdmin + DriverOperator keep working unchanged). Enabled in docker-dev central-1/central-2. AdminUI cookie surface only.",
|
||||
"tasks": [
|
||||
{"id": 229, "planTask": 0, "subject": "DL-T0: Branch + baseline", "classification": "small", "status": "pending", "blockedBy": []},
|
||||
{"id": 230, "planTask": 1, "subject": "DL-T1: AuthDisableLoginOptions + centralized role list", "classification": "small", "status": "pending", "blockedBy": [229]},
|
||||
{"id": 231, "planTask": 2, "subject": "DL-T2: AutoLoginAuthenticationHandler", "classification": "high-risk", "status": "pending", "blockedBy": [230]},
|
||||
{"id": 232, "planTask": 3, "subject": "DL-T3: Branch AddOtOpcUaAuth on the flag (+ loud warning)", "classification": "high-risk", "status": "pending", "blockedBy": [230, 231]},
|
||||
{"id": 233, "planTask": 4, "subject": "DL-T4: appsettings default + docker-dev enablement", "classification": "small", "status": "pending", "blockedBy": [230], "parallelizableWith": [231, 232]},
|
||||
{"id": 234, "planTask": 5, "subject": "DL-T5: Live-verify (docker-dev /run)", "classification": "verification", "status": "pending", "blockedBy": [232, 233]}
|
||||
],
|
||||
"lastUpdated": "2026-06-11"
|
||||
}
|
||||
Reference in New Issue
Block a user