Roles & Permissions (RBAC)
Role-Based Access Control system for managing user access — roles, permissions, direct assignments, and how checks happen
Roles & Permissions (RBAC)
EduShade uses Role-Based Access Control (RBAC) to determine what each user can see and do. Permissions are the atoms; roles are bundles of permissions; and a user can hold any number of roles plus individually-granted "direct" permissions.
Core Concepts
Permissions
A permission is a single, specific action a user can perform. Permissions are organized into groups (e.g. user, rbac).
Examples:
user.create— Create new usersuser.delete— Delete usersrbac.view— View roles and permissionsanalytics.export— Export analytics data
Roles
A role is a named collection of permissions. Instead of assigning individual permissions to each user, you bundle them into roles and assign roles.
Direct Permissions
Permissions can also be assigned directly to a user, bypassing roles. Useful for granting one-off access without creating a new role.
Effective permissions for a user = (permissions from all assigned roles) ∪ (direct permissions).
Permission Groups
Permission seed data lives in auth-service/data/permissions.json and is loaded into the permissions table. The seed defines permissions across many groups — these are the platform-wide permission groups, not all of which are owned by the auth module:
| Group | Owned by | Notes |
|---|---|---|
user | Auth | view, list, create, update, delete, export |
rbac | Auth | view, create, update, delete |
admin | Auth | catch-all admin permissions, e.g. admin.users (required for impersonation) |
tenant / organization / invitation / settings | Tenant / platform | Cross-cut across services |
file | File manager | Upload/download/manage files |
notification | Notification service | Send and manage notifications |
analytics | Analytics | View and export reports |
audit | Audit service | View audit logs |
course / enrollment / certificate / quiz / etc. | Learning, Quiz, etc. | Defined here so RBAC checks for them are uniform across services |
Use the API GET /v1/rbac/permissions/groups to enumerate the groups present in your deployment, and GET /v1/rbac/permissions/groups/{group} to list permissions within a group.
Built-in Roles
Note: EduShade does not seed any roles by default. The
rolestable is empty after a fresh install — administrators must create roles before they can be assigned. There are no out-of-the-box "Super Admin" / "Admin" / "Moderator" roles unless you create them yourself or run a custom seeder.
A common starter setup teams create manually:
| Role | Typical permissions |
|---|---|
| Super Admin | All permissions (effectively bypasses checks via the super_admin role-name shortcut, see below) |
| Admin | user.*, rbac.*, admin.users, audit.view, analytics.view |
| Instructor | Course/lesson management permissions |
| Learner | Basic learning permissions (this is also implied by the is_learner user flag) |
super_admin shortcut
The frontend route guard treats any user holding a role named super_admin as having all permissions — this is a convenience escape hatch and is enforced in src/lib/auth/middlewares/route-permissions.ts. If you want a "god mode" role, name it super_admin exactly.
User Type Flags vs Roles
EduShade has three boolean flags on every user — independent of RBAC roles:
| Flag | Purpose |
|---|---|
is_admin | Grants access to admin-area routes; required for impersonation alongside admin.users |
is_instructor | Marks the user as a course author |
is_learner | Marks the user as a learner (default: true for new registrations) |
A single user can hold any combination of these flags and any number of roles. Flags gate which dashboards (/admin vs /dashboard) are accessible; roles + permissions determine fine-grained capabilities within those dashboards.
Managing Roles (Admin)
Creating a New Role
- Go to Admin → Users → Roles (
/admin/users/roles) - Click Create Role
- Enter a role name (and
guard, defaultweb) - Select the permissions to include
- Click Save
API: POST /v1/rbac/roles (requires rbac.create).
Editing a Role
- From the roles list, click a role to open
/admin/users/roles/{roleID}/edit - Modify the name, status, or toggle permissions on/off
- Click Save — changes take effect immediately for all users with this role (after the cache invalidation event propagates)
API: PUT /v1/rbac/roles/{roleID} (requires rbac.update).
Cloning a Role
Need a role similar to an existing one?
- Find the role in the roles list and click Clone
- A new role is created with the same set of permissions
- Edit the cloned role to customize its name and permissions
API: POST /v1/rbac/roles/{roleID}/clone (requires rbac.create).
Deleting a Role
- Click Delete on the role
- Confirm the action — the role is soft-deleted (
deleted_atset) - Users who held this role lose its permissions on their next permission-cache refresh
API: DELETE /v1/rbac/roles/{roleID} (requires rbac.delete).
Assigning Roles & Permissions
Assign a Role to a User
- API:
POST /v1/rbac/users/{userID}/roles/{roleID}(requiresrbac.update) - UI: edit the user, pick roles in the Roles section.
Remove a Role from a User
- API:
DELETE /v1/rbac/users/{userID}/roles/{roleID}(requiresrbac.update)
Assign a Direct Permission to a User
- API:
POST /v1/rbac/users/{userID}/permissions/{permissionID}(requiresrbac.update)
Remove a Direct Permission from a User
- API:
DELETE /v1/rbac/users/{userID}/permissions/{permissionID}(requiresrbac.update)
How Permissions Are Checked
Backend (per-route):
- The
Can("permission.name", permissionProvider)middleware runs on protected routes. - The provider collects the user's effective permissions — direct + role-derived — from a cache.
- Access is granted if the requested permission is in the set, otherwise the request is rejected with
403.
Frontend:
usePermissions()hook (src/lib/auth/use-permissions.ts) exposeshasPermission(name)andhasRole(name)for inline checks.- Sidebar items, action buttons, and route guards use these to hide UI a user cannot use.
- Route-level guards live in
src/lib/auth/middlewares/route-permissions.ts(canAccessRoute,hasAnyPermission,hasAllPermissions,isSuperAdmin).
Permission Caching
- The backend uses
rbac.NewCachedDBPermissionProviderto cache permissions in memory. - When a role or permission assignment changes, an RBAC event is published to the platform's pub/sub layer (
RBACEventPublisher) so other instances refresh their caches. - Users may need a fresh access token (re-login or refresh) to see permission changes reflected in their JWT
permissionsclaim.
Filtering Roles
When viewing the roles list, you can filter by:
| Filter | Description |
|---|---|
| Search | Search by role name |
| Guard | Filter by guard type (default: web) |
| Status | Active / inactive |
RBAC Events
When roles or permissions change, events are published for:
- Cache invalidation — keeps multi-instance deployments in sync
- Audit logging — tracks who changed what and when (visible to users with
audit.view)
Troubleshooting
| Issue | Solution |
|---|---|
| User can't access a page | Confirm they have the required permission via a role or a direct assignment |
| Permission change not taking effect | Permissions are cached. The user may need to refresh their token (re-login) |
| Can't create or edit roles | You need rbac.create / rbac.update |
| Role deletion didn't remove access | Check the user doesn't have the same permissions via another role or as a direct assignment |
| Admin sidebar shows limited items | The sidebar is filtered by permissions — the user needs the matching role/permissions for items to appear |
| No roles exist after install | This is expected — roles are not seeded by default. Create the roles your team needs |

