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

@@ -42,19 +42,10 @@ var AllPermissions = []string{
// ── Resolution ──────────────────────────────
// ResolvePermissions returns the effective permission set for a user.
// Always includes the Everyone group regardless of membership.
// Includes both implicit Everyone group and explicit group memberships.
// Unions permissions from all groups the user is a member of (including Everyone).
func ResolvePermissions(ctx context.Context, stores store.Stores, userID string) (map[string]bool, error) {
perms := make(map[string]bool)
// Always apply Everyone group — no membership row required.
if everyone, err := stores.Groups.GetByID(ctx, EveryoneGroupID); err == nil {
for _, p := range everyone.Permissions {
perms[p] = true
}
}
// Union all explicit group memberships.
groups, err := stores.Groups.ListForUser(ctx, userID)
if err != nil {
return perms, err
@@ -68,14 +59,19 @@ func ResolvePermissions(ctx context.Context, stores store.Stores, userID string)
return perms, nil
}
// SyncAdminsGroupMembership adds or removes a user from the Admins group
// based on their role. Called by auth providers and admin handlers when
// a user's role changes so that permissions stay in sync.
func SyncAdminsGroupMembership(ctx context.Context, stores store.Stores, userID, role string) {
if role == "admin" {
_ = stores.Groups.AddMember(ctx, AdminsGroupID, userID, userID)
} else {
_ = stores.Groups.RemoveMember(ctx, AdminsGroupID, userID)
}
// EnsureEveryoneGroup adds a user to the Everyone group (idempotent).
// Called on every user creation path so that Everyone membership is explicit.
func EnsureEveryoneGroup(ctx context.Context, stores store.Stores, userID string) {
_ = stores.Groups.AddMember(ctx, EveryoneGroupID, userID, userID)
}
// AddToAdminsGroup adds a user to the Admins group (idempotent).
func AddToAdminsGroup(ctx context.Context, stores store.Stores, userID string) {
_ = stores.Groups.AddMember(ctx, AdminsGroupID, userID, userID)
}
// RemoveFromAdminsGroup removes a user from the Admins group.
func RemoveFromAdminsGroup(ctx context.Context, stores store.Stores, userID string) {
_ = stores.Groups.RemoveMember(ctx, AdminsGroupID, userID)
}