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
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:
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user