feat(rbac): user assignment, audit trail UI and e2e coverage - #137
Merged
Conversation
The form rendered behind `editing || name || perms.size > 0`, but creating a role starts with no name and no permissions and startEdit(null) sets editing to null, so every term was falsy and the button did nothing. Only Edit on an existing role could open the form. Track the open state explicitly instead, and give the escape hatch its real name: the button cleared the fields but left the form open, so Cancel is what it does now that closing is possible. Caught by the e2e added in the next commit.
Two specs against the real /access page. A Viewer opens it, sees the roles, and is refused a direct POST /api/roles. An admin creates a role through the management UI. Two deviations from the plan, both needed to make the tests mean something. The viewer check uses page.request rather than the isolated request fixture, which carries no browser cookies and would have hit the 401 branch instead of the 403 the test is about. The created role takes a unique name and is deleted afterwards, since a fixed name survives in a dev database and would let a rerun pass on the leftover row while the create silently returned 409. Seeds a Security Manager (users:manage, no roles:manage) and a Viewer in the shared company.
The section exists only to reassign roles, but it rendered behind users:read. The Viewer preset takes READ_ONLY, which is every ":read" permission in the catalog, so a read-only user saw the roster and a role dropdown that the server refused on every change. The server was never at risk; this is the client catching up with it.
The auth specs built state they never undid. Enrolling a second factor left a TwoFactor row, so the next run got 401 from enable on an already-enrolled user. Registering a passkey left a credential bound to a CDP virtual authenticator that dies with the browser, so passkey sign-in hung waiting on something no live authenticator could satisfy. CI never saw either, its database is created fresh per run, but locally the second run always failed. Clear both in beforeAll rather than a teardown: an interrupted run never reaches its teardown, so cleaning up front is what actually makes a rerun deterministic. The serial chains still build their own state afterwards. Verified by running the full suite twice back to back from a dirty database with no manual reset in between: 9/9 both times.
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.
Closes RBAC Plan 2 (
docs/superpowers/plans/2026-07-26-rbac-management-audit.md), tasks 12 to 14. Tasks 1 to 11 landed earlier ondevelop.Task 12: user assignment and audit trail UI
UserRoleAssignment.tsx: searchable user list with a role dropdown, replaying the call throughStepUpDialogonSTEP_UP_REQUIRED.AuditTrail.tsx: paginated read ofGET /api/audit./accessbehindusers:readandaudit:read, plus anAccessentry in the sidebar.Bug found by the new tests
The
New rolebutton never opened anything. The form rendered behindediting || name || perms.size > 0, but a new role starts with no name and no permissions andstartEdit(null)setseditingto null, so every term was falsy. OnlyEditon an existing role could open it. The open state is now explicit, andClearbecameCancelsince closing is finally possible.Task 13: e2e
Two specs against the real page. A Viewer opens
/access, sees the roles, and is refused a directPOST /api/roles. An admin creates a role through the UI.Two deliberate deviations from the plan, both needed for the tests to mean anything:
page.request, not the isolatedrequestfixture. That fixture carries no browser cookies, so the call would have hit the401branch instead of the403the test is actually about.409.Task 14: verification
Note for local runs: the e2e suite is not idempotent against a persistent database.
two-factor.specenrols 2FA without cleaning up andpasskey.specleaves a credential bound to a virtual authenticator that no longer exists, so a second local run fails on both. CI is unaffected, its database is fresh each time. Left alone here as pre-existing and out of this plan scope.