fix: guarantee root admin and Administrator role are non-deletable#119
Merged
Conversation
The first-run setup produced a *deletable* root admin and a *deletable* Administrator role, so an administrator could delete the main admin account and/or the Administrator role and orphan all admin access. Two layers, one defect: - Library: CreateAsync now forces Deletable=false for root users (mirroring the existing UpdateAsync guard) so the default User.Deletable=true can no longer mint a deletable root. EnsureAdministratorRoleAsync now hardens a pre-existing deletable Administrator role to non-deletable via a RoleChanged event, so the guarantee holds regardless of how the role first came to exist. - Sample: Setup.razor creates the first admin with RootUser=true and lets the library own the "un-deletable root administrator" guarantee (protected role creation + assignment + non-deletability) in one transaction. The manual role creation and AddToRole that left both deletable are removed. Tests: integration coverage for both invariants (root created non-deletable; existing deletable Administrator role hardened) and an E2E assertion that the /Setup-created admin surfaces as non-deletable. Closes #117 Closes #118
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The first-run setup produced a deletable root administrator and a deletable Administrator role. An administrator could therefore delete the main admin account and/or the Administrator role from the admin UI, orphaning all administrative access and leaving the app in an inconsistent, unrecoverable state. The setup UI even promises an "un-deletable root administrator" — the implementation didn't deliver it.
One defect, two layers. Closes #117 (sample) and #118 (library hardening).
Root cause
Setup.razorbypassed the library's root-user mechanism:RootUser = true. So none of the library's root guards applied, andUser.Deletable(defaulttrue) was propagated verbatim byCreateAsync— it did not forcefalsefor root the wayUpdateAsyncdoes.Role.Deletabledefaults totrue). BecauseRootUserwas never set, the library path that creates the role as non-deletable never ran.Fix
Library (defence in depth — the guarantee no longer depends on host code):
UserStore.CreateAsyncnow forcesDeletable = falsefor root users, mirroring the existingUpdateAsyncguard.EnsureAdministratorRoleAsyncnow hardens a pre-existing deletable Administrator role to non-deletable via aRoleChangedevent, so the guarantee holds regardless of how the role first came to exist.Sample (canonical library usage):
Setup.razorcreates the first admin withRootUser = trueand lets the library own the whole "un-deletable root administrator" guarantee (protected role creation + assignment + non-deletability) in one transaction. The manual role creation andAddToRolethat left both deletable are removed.Tests
UserStoreTests):CreateAsync_RootUser_IsNonDeletableandCreateAsync_RootUser_HardensExistingDeletableAdministratorRole. Verified locally against Postgres (Podman) — green, with the full user/role store suite (60/60) passing, no regressions.AdministrationTests.Admin_RootAccount_IsNonDeletable): asserts the/Setup-created admin surfaces asDeletable = "No"in the Users grid, covering the sample fix end-to-end.ProvisionAdminAsyncdrives the real/Setuppage, so every admin E2E test also regression-guards that setup still works withRootUser = true. Built locally; runs in CI (local E2E is blocked by the Aspire HTTPS dev cert).Verification
dotnet build(library, sample, both test projects): 0 errors, 0 warnings.