Skip to content

feat(rbac): user assignment, audit trail UI and e2e coverage - #137

Merged
WhiteMuush merged 5 commits into
developfrom
feat/rbac-management-ui
Aug 4, 2026
Merged

feat(rbac): user assignment, audit trail UI and e2e coverage#137
WhiteMuush merged 5 commits into
developfrom
feat/rbac-management-ui

Conversation

@WhiteMuush

Copy link
Copy Markdown
Owner

Closes RBAC Plan 2 (docs/superpowers/plans/2026-07-26-rbac-management-audit.md), tasks 12 to 14. Tasks 1 to 11 landed earlier on develop.

Task 12: user assignment and audit trail UI

  • UserRoleAssignment.tsx: searchable user list with a role dropdown, replaying the call through StepUpDialog on STEP_UP_REQUIRED.
  • AuditTrail.tsx: paginated read of GET /api/audit.
  • Both mounted on /access behind users:read and audit:read, plus an Access entry in the sidebar.

Bug found by the new tests

The New role button never opened anything. The form rendered behind editing || name || perms.size > 0, but a new role starts with no name and no permissions and startEdit(null) sets editing to null, so every term was falsy. Only Edit on an existing role could open it. The open state is now explicit, and Clear became Cancel since closing is finally possible.

Task 13: e2e

Two specs against the real page. A Viewer opens /access, sees the roles, and is refused a direct POST /api/roles. An admin creates a role through the UI.

Two deliberate deviations from the plan, both needed for the tests to mean anything:

  • The viewer check uses page.request, not the isolated request fixture. That fixture carries no browser cookies, so the call would have hit the 401 branch instead of the 403 the test is actually about.
  • The created role takes a unique name and is deleted afterwards. A fixed name survives in a dev database, and a rerun would pass on the leftover row while the create silently returned 409.

Task 14: verification

tsc                  0 error
lint                 0 error
vitest unit          182 tests, 36 files
vitest integration   12 tests, 10 files
playwright e2e       9/9

Note for local runs: the e2e suite is not idempotent against a persistent database. two-factor.spec enrols 2FA without cleaning up and passkey.spec leaves 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.

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.
@WhiteMuush
WhiteMuush merged commit fcaadfd into develop Aug 4, 2026
13 checks passed
@WhiteMuush
WhiteMuush deleted the feat/rbac-management-ui branch August 4, 2026 19:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant