RBAC (roles & permissions)
App-level roles/permissions and the recommended backend integration patterns.
Overview
App-level RBAC is distinct from workspace membership. Roles and permissions are stored in the database and loaded by the backend when using requireFullAuthWithRoles.
There is no JWT auth hook; the app resolves public.users.id from the token and fetches roles/permissions from user_roles and role_permissions.
Roles
- Editor — Can view and manage feedback; can view role/permission data. Use case: content moderators or support.
- Admin — All editor permissions plus users.manage_roles (assign/remove roles). Only super admins can assign/remove the admin role; admins can assign/remove only the editor role (enforced in RbacService and DB RPCs). Use case: platform admins.
- Super Admin — Not a role; a flag on public.users (is_super_admin=true). Bypasses permission checks and can assign/remove any role.
Backend integration
Types
The role/permission types live in backend/data/types/rbacTypes.ts (for example AppRole and AppPermission).
Authenticate with roles
Use requireFullAuthWithRoles(supabase, userRepository, rbacRepository) so req.user includes:
- roles
- permissions
- publicId
- isSuperAdmin
Use requireFullAuth only when the route does not need role/permission resolution.
Route guards
Use role/permission middlewares after requireFullAuthWithRoles:
- requireEditor — editor, admin, or super_admin
- requireAdmin — admin or super_admin
- requireSuperAdmin — super_admin only
- requireRole('editor') — that role or super_admin
- requirePermission('feedback.view') — that permission (super_admin bypasses)
- requireAnyPermission([...]) — at least one permission
Example: protect by permission
const authWithRoles = requireFullAuthWithRoles(supabase, userRepository, rbacRepository);
const requireManageRoles = requirePermission('users.manage_roles');
rbacRouter.post('/users/:userId/roles/:role', authWithRoles, requireManageRoles, rbacController.assignRole);
rbacRouter.delete('/users/:userId/roles/:role', authWithRoles, requireManageRoles, rbacController.removeRole); Example: protect by editor or higher
feedbackRouter.get('/', authWithRoles, requireEditor, feedbackController.getAllFeedbacks);
feedbackRouter.patch('/:feedbackId', authWithRoles, requireEditor, feedbackController.handleFeedback);