Files
spsays-bff/internal/rbac/rbac.go
T
2026-10-15 20:59:53 +05:30

110 lines
3.6 KiB
Go

// Package rbac is the admin panel's permission catalog.
//
// Permissions are "resource:action" strings. The catalog lives in code, not in
// the database, so a permission can't exist without a route enforcing it, and
// adding one is a reviewed change. Roles (in admin_roles) are just named sets
// of these strings.
package rbac
import "slices"
// Wildcard grants everything. Only the seeded superadmin role carries it.
const Wildcard = "*"
// ManageStaff is held by superadmins alone: granting, changing, revoking and
// blocking other staff. It is deliberately absent from Catalog, so no custom
// role can ever be given it.
const ManageStaff = "admins:manage"
// Permission is one grantable capability.
type Permission struct {
Key string `json:"key"`
Group string `json:"group"`
Label string `json:"label"`
}
// Catalog is every permission a custom role may hold, grouped for the editor.
var Catalog = []Permission{
{"stats:read", "Overview", "View dashboard stats"},
{"users:read", "Users", "View users"},
{"users:notify", "Users", "Send users a message"},
{"users:ban", "Users", "Ban and unban users"},
{"users:limits", "Users", "Change bookmark and collection limits"},
{"disciples:manage", "Users", "Link and unlink direct disciples"},
{"posts:moderate", "Moderation", "Hide, delete and restore posts"},
{"comments:moderate", "Moderation", "Delete and restore comments"},
{"collections:read", "Moderation", "View collections"},
{"reports:read", "Moderation", "View reports"},
{"reports:resolve", "Moderation", "Resolve reports"},
{"feedback:read", "Feedback", "View feedback"},
{"feedback:reply", "Feedback", "Reply to feedback"},
{"feedback:resolve", "Feedback", "Acknowledge and resolve feedback"},
{"vani:read", "Vāṇī of the Day", "View the schedule"},
{"vani:write", "Vāṇī of the Day", "Pin and clear days"},
{"rewards:read", "Rewards", "View the reward catalog"},
{"rewards:write", "Rewards", "Add custom gifts"},
{"rewards:send", "Rewards", "Send gifts to everyone"},
{"rewards:delete", "Rewards", "Delete custom gifts"},
{"broadcasts:read", "Announcements", "View announcements"},
{"broadcasts:write", "Announcements", "Create and delete drafts"},
{"broadcasts:publish", "Announcements", "Publish to everyone"},
{"quizzes:read", "Quizzes", "View quizzes"},
{"quizzes:write", "Quizzes", "Create, edit and publish quizzes"},
{"quizzes:delete", "Quizzes", "Delete quizzes"},
{"quizzes:results", "Quizzes", "View participants and responses"},
{"quizzes:reset", "Quizzes", "Reset a participant's attempt"},
{"short_urls:read", "Short URLs", "View short links and stats"},
}
// Valid reports whether p is a grantable permission.
func Valid(p string) bool {
return slices.ContainsFunc(Catalog, func(c Permission) bool { return c.Key == p })
}
// Set is a resolved permission set for one staff member.
type Set struct {
all bool
keys map[string]bool
}
// NewSet resolves a role's stored permissions.
func NewSet(perms []string) Set {
s := Set{keys: map[string]bool{}}
for _, p := range perms {
if p == Wildcard {
s.all = true
}
s.keys[p] = true
}
return s
}
// Has reports whether the set grants p.
func (s Set) Has(p string) bool { return s.all || s.keys[p] }
// List is the effective permissions, for the admin UI to decide what to show.
// A wildcard expands to the full catalog plus ManageStaff.
func (s Set) List() []string {
if s.all {
out := make([]string, 0, len(Catalog)+1)
for _, c := range Catalog {
out = append(out, c.Key)
}
return append(out, ManageStaff)
}
out := make([]string, 0, len(s.keys))
for k := range s.keys {
out = append(out, k)
}
slices.Sort(out)
return out
}