AETHERLABS

Roles & Permissions

How RBAC works in Aether Labs SSO — roles, permissions, application scoping, and the seeded roles.

Roles & Permissions

Aether Labs SSO uses role-based access control (RBAC). A user holds one or more roles; each role carries a list of permissions. Permissions are the thing your app actually checks — roles are just bundles of them.

The model

user ──(user_roles)──▶ role ──(permissions[])──▶ "resource:action"
                          └─ application_id ─▶ scopes the role to one OAuth client
  • Permissions are resource:action strings, e.g. users:read, oauth_clients:write, finance:view_sensitive.
  • Roles have a name, a description, a permission list, and an optional application_id. A role with application_id = null is global; a scoped role only applies to that OAuth client.
  • user_roles links a user to a role, with assigned_by, expires_at, and is_active.

Permissions are resolved live from the database on each request. They are not embedded in the JWT — the token carries only roles. Read permissions from GET /api/auth/me (or /api/rbac/users/:userId/permissions).

Permission catalog

Permissions are grouped by resource:

ResourceExamples
usersusers:read, users:write, users:delete, users:manage_roles
oauth_clientsoauth_clients:read, oauth_clients:write, oauth_clients:rotate_secret, oauth_clients:delete
teamread/write on team members
projectsread/write on projects
clientsread/write on clients
financefinance:view, finance:view_sensitive
accountsread/write on financial accounts
blogsread/write/delete on CMS content
leadsread/write on leads
systemsystem:manage_users, system:manage_roles
serverserver-level administration

Fetch the authoritative list at runtime from GET /api/rbac/permissions.

Seeded roles

On every boot the API ensures a default set of roles exists, each scoped to the admin-dashboard OAuth client:

RoleIntended for
super_adminEverything, including creating and editing roles.
adminAlmost everything, excluding sensitive finance data.
managerProjects and clients, team, finance, and leads.
supportRead-mostly access plus lead updates.
content_writerBlog/CMS read, write, and delete.

Admin-tier checks

Route guards accept admin, super_admin, and manager as admin-tier. Managing roles themselves requires super_admin.

Assigning roles

Roles are assigned through the SSO admin console (admin-web → Users) or the API:

POST https://sso.aetherlabs.agency/api/rbac/users/:userId/roles
Authorization: Bearer <accessToken>
Content-Type: application/json

{ "roleId": "role-uuid" }

Requires the users:manage_roles permission.

Bootstrapping the first admin

New users register with no roles, so there is always a chicken-and-egg problem for the first administrator. Register a user, then grant super_admin:

pnpm --dir api bootstrap:admin --email you@example.com

The script builds the API, ensures the default roles exist, and assigns super_admin (scoped to the admin-dashboard client) to that existing user, with an audit log entry. In production, run it inside the api container:

node dist/scripts/bootstrapAdmin.js --email you@example.com

Enforcing permissions

Frontend checks are for UX only. Always enforce on the backend:

function requirePermission(permission: string) {
  return (req, res, next) => {
    if (!req.user?.permissions?.includes(permission)) {
      return res.status(403).json({ error: 'Forbidden' });
    }
    next();
  };
}

See also

On this page