drop users.role column: full RBAC through group membership
All checks were successful
CI/CD / detect-changes (pull_request) Successful in 4s
CI/CD / test-frontend (pull_request) Successful in 5s
CI/CD / test-go-pg (pull_request) Successful in 2m22s
CI/CD / test-sqlite (pull_request) Successful in 2m35s
CI/CD / build-and-deploy (pull_request) Successful in 1m18s

The role column was a pre-RBAC artifact. All authorization now flows
through explicit group membership and permission grants:

- Everyone group: all users added on creation (no implicit membership)
- Admins group: grants surface.admin.access + all platform permissions
- JWT claims, login response, profile: role field removed
- OIDC: isIdPAdmin() maps IdP claims → Admins group (no role writes)
- Admin UI: role dropdown removed, admin managed through groups
- Middleware cache simplified to isActive only

28 files changed, -79 lines net. Zero magic roles.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-26 17:23:12 +00:00
parent e8e45184f7
commit b10f5bee05
28 changed files with 206 additions and 285 deletions

View File

@@ -6,40 +6,51 @@ All notable changes to Switchboard Core are documented here.
### Added
- **Admin → RBAC group migration**: `surface.admin.access` permission replaces
hardcoded `role == "admin"` checks. Seeded "Admins" system group carries all
platform permissions. Admin middleware now resolves grants through the standard
permission system — no special-casing. Any group can grant `surface.admin.access`.
- `AdminsGroupID` constant (`00000000-0000-0000-0000-000000000002`)
- `SyncAdminsGroupMembership()` — shared helper used by bootstrap, seed, OIDC,
and admin handlers to keep group membership in sync with role changes
- `SeedAdminsGroupMember()` test helper
- **Full RBAC**: all authorization flows through group membership and permission
grants. No magic roles, no implicit group membership, no special-casing.
- `surface.admin.access` permission — any group can grant admin panel access
- Admins system group seeded with all platform permissions
- Everyone system group — all users explicitly added on creation
- `EnsureEveryoneGroup()`, `AddToAdminsGroup()`, `RemoveFromAdminsGroup()` helpers
- `SeedAdminsGroupMember()`, `SeedEveryoneGroupMember()` test helpers
- System groups re-seeded after `TruncateAll` in test helper
- OIDC `isIdPAdmin()` — maps IdP role claims to Admins group membership
### Changed
- `RequireAdmin()` / `RequireAdminPage()` now accept `store.Stores` and check
`surface.admin.access` grant instead of `role == "admin"`
- `RequirePermission()` no longer bypasses checks for admin role — admins get
permissions through group membership like everyone else
- Bootstrap/seed/OIDC login all add admin users to Admins group
- Admin create/update/delete handlers sync Admins group membership on role change
- Demotion/deletion safeguards count Admins group members instead of `CountByRole`
- `RequireAdmin()` / `RequireAdminPage()` check `surface.admin.access` grant
- `RequirePermission()` no longer bypasses for admin role
- `ResolvePermissions()` unions explicit group memberships only (no implicit Everyone)
- All user creation paths (builtin, OIDC, mTLS, admin, bootstrap, seed) add to
Everyone group. Admin users added to Admins group.
- JWT claims no longer include `role` field
- Login response no longer includes `role` in user object
- Profile endpoint no longer returns `role`
- Profile bootstrap resolves permissions from groups (no admin shortcut)
- Middleware auth cache tracks `isActive` only (no role)
- Admin create user accepts `is_admin` bool (not role string)
- Admin update role endpoint accepts `is_admin` bool, manages Admins group directly
- Demotion/deletion safeguards check Admins group member count
- Notifications `RoleFallbackHandler` queries Admins group members
- OIDC syncs Admins group on login (no role column writes)
- Kernel permissions: 6 → 7 (added `surface.admin.access`)
- Admin users UI: role dropdown removed, admin managed through groups
### Removed
- **Token budgets** from groups: `token_budget_daily`, `token_budget_monthly`
columns, `ResolveTokenBudget()`, and all store/handler/UI code. Provider-era
cruft — token budgets belong in a future provider extension, not the kernel.
- **Allowed models** from groups: `allowed_models` column,
`ResolveModelAllowlist()`, `toggleModel` UI. Same rationale.
- `GroupPatch` fields: `ClearBudgetDaily`, `ClearBudgetMonthly`,
`ClearAllowedModels` — no longer needed
- Admin groups UI: Token Budgets section, Allowed Models section, models API call
- **`users.role` column** — dropped from schema, model, JWT, all handlers
- `UserRoleAdmin`, `UserRoleUser` constants
- `CountByRole()` store method
- `DefaultRole` config for OIDC and mTLS providers
- `SyncAdminsGroupMembership()` (replaced by `AddToAdminsGroup`/`RemoveFromAdminsGroup`)
- OIDC `resolveRole()` (replaced by `isIdPAdmin()`)
- **Token budgets** from groups: columns, `ResolveTokenBudget()`, all store/handler/UI
- **Allowed models** from groups: column, `ResolveModelAllowlist()`, UI
- Admin groups UI: Token Budgets section, Allowed Models section
### Migration notes
- 001_core.sql (both dialects): removed `role` column from users table
- 002_teams.sql (both dialects): added Admins group seed, removed
`token_budget_daily`, `token_budget_monthly`, `allowed_models` columns
- No new migration files — edited in place per pre-MVP policy