// 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"}, {"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 }