Skip to content

RBAC (roles & permissions)

App-level roles/permissions and the recommended backend integration patterns.

2 min read

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);

Related Section(s)