EduShade
Auth Module

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 users
  • user.delete — Delete users
  • rbac.view — View roles and permissions
  • analytics.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:

GroupOwned byNotes
userAuthview, list, create, update, delete, export
rbacAuthview, create, update, delete
adminAuthcatch-all admin permissions, e.g. admin.users (required for impersonation)
tenant / organization / invitation / settingsTenant / platformCross-cut across services
fileFile managerUpload/download/manage files
notificationNotification serviceSend and manage notifications
analyticsAnalyticsView and export reports
auditAudit serviceView 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 roles table 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:

RoleTypical permissions
Super AdminAll permissions (effectively bypasses checks via the super_admin role-name shortcut, see below)
Adminuser.*, rbac.*, admin.users, audit.view, analytics.view
InstructorCourse/lesson management permissions
LearnerBasic 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:

FlagPurpose
is_adminGrants access to admin-area routes; required for impersonation alongside admin.users
is_instructorMarks the user as a course author
is_learnerMarks 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

  1. Go to Admin → Users → Roles (/admin/users/roles)
  2. Click Create Role
  3. Enter a role name (and guard, default web)
  4. Select the permissions to include
  5. Click Save

API: POST /v1/rbac/roles (requires rbac.create).

Editing a Role

  1. From the roles list, click a role to open /admin/users/roles/{roleID}/edit
  2. Modify the name, status, or toggle permissions on/off
  3. 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?

  1. Find the role in the roles list and click Clone
  2. A new role is created with the same set of permissions
  3. Edit the cloned role to customize its name and permissions

API: POST /v1/rbac/roles/{roleID}/clone (requires rbac.create).

Deleting a Role

  1. Click Delete on the role
  2. Confirm the action — the role is soft-deleted (deleted_at set)
  3. 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} (requires rbac.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} (requires rbac.update)

Assign a Direct Permission to a User

  • API: POST /v1/rbac/users/{userID}/permissions/{permissionID} (requires rbac.update)

Remove a Direct Permission from a User

  • API: DELETE /v1/rbac/users/{userID}/permissions/{permissionID} (requires rbac.update)

How Permissions Are Checked

Backend (per-route):

  1. The Can("permission.name", permissionProvider) middleware runs on protected routes.
  2. The provider collects the user's effective permissions — direct + role-derived — from a cache.
  3. 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) exposes hasPermission(name) and hasRole(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.NewCachedDBPermissionProvider to 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 permissions claim.

Filtering Roles

When viewing the roles list, you can filter by:

FilterDescription
SearchSearch by role name
GuardFilter by guard type (default: web)
StatusActive / 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

IssueSolution
User can't access a pageConfirm they have the required permission via a role or a direct assignment
Permission change not taking effectPermissions are cached. The user may need to refresh their token (re-login)
Can't create or edit rolesYou need rbac.create / rbac.update
Role deletion didn't remove accessCheck the user doesn't have the same permissions via another role or as a direct assignment
Admin sidebar shows limited itemsThe sidebar is filtered by permissions — the user needs the matching role/permissions for items to appear
No roles exist after installThis is expected — roles are not seeded by default. Create the roles your team needs

On this page