You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Frontend: replace role selector with required group multi-select + permission-derived gating (follow-up to #907) #913
Follow-up to #907 / PR #912 (backend group-membership-only authorization).
The backend now derives all authorization from group membership and no longer returns user.role (the users.role / sessions.role columns are dropped by migration 000057). The frontend still gates on currentUser.role (see frontend/src/auth.tsisAdmin() ~L20/L921/L934, frontend/src/permissions.tsgetRolePermissions() / isAdmin()), so admin UI gating and permission derivation will break against the new API.
Scope
Create/edit-user form: replace the role selector with a required group multi-select (>= 1 group, sensible default preselected; cannot submit empty). Submit groups: string[] instead of role.
Permission-derived UI gating: switch isAdmin() and viewer/operator gating to derive from the user's effective group permissions (membership in the Administrators group, fixed UUID 00000000-0000-5000-8000-000000000001, or the {admin, *} capability) instead of user.role.
Types: drop role from the user/session TS types; add groups: string[] (the API field is groups, no omitempty).
Tests: update frontend/src/__tests__/users.test.ts, auth.test.ts, permissions-related and recommendations-permissions tests to the group/permission model; keep coverage flat or better.
Deployment ordering (important)
PR #912 must not be deployed to an environment whose frontend still reads user.role. Ship this follow-up together with #912 (or merge #912 then this, and deploy only after both land).
Backend reference for the effective-permission contract: internal/auth/service_group.go (HasPermission, UserHasAdminCapability), internal/api/types.go (User.Groups), internal/auth/service_api.go (APIUser.Groups).
Verification sweep against main (101f099fb) on 2026-07-27 finds this already resolved.
The role selector has been replaced by a required groups multi-select, and isAdmin() now derives from group membership.
Evidence: commit fa1603f (#914).
Recommending close.
Verified resolved at 3c0f8ac: the create/edit-user form uses a required multi-select of groups, isAdmin() and permission gating derive from Administrators-group membership or the admin capability rather than user.role, and the user/session TS types no longer carry a role field. Evidence: frontend/src/permissions.ts:144-204, frontend/src/auth.ts:21, frontend/src/index.html:1060 (#914). Residual axes checked: remaining user.role reads in frontend/src (none outside comments), role field in TS types (none), getRolePermissions callers (test-only). Closing as completed; reopen if the behaviour recurs.
Activity
cristim commentedon Jul 27, 2026
Verification sweep against
main(101f099fb) on 2026-07-27 finds this already resolved.The role selector has been replaced by a required groups multi-select, and isAdmin() now derives from group membership.
Evidence: commit fa1603f (#914).
Recommending close.
cristim commentedon Sep 2, 2026
Verified resolved at 3c0f8ac: the create/edit-user form uses a required multi-select of groups, isAdmin() and permission gating derive from Administrators-group membership or the admin capability rather than user.role, and the user/session TS types no longer carry a role field. Evidence: frontend/src/permissions.ts:144-204, frontend/src/auth.ts:21, frontend/src/index.html:1060 (#914). Residual axes checked: remaining user.role reads in frontend/src (none outside comments), role field in TS types (none), getRolePermissions callers (test-only). Closing as completed; reopen if the behaviour recurs.