Skip to main content

Role Hierarchy

CORTEX supports hierarchical roles where child roles inherit permissions from their parent roles.

How Inheritance Works

TENANT_ADMIN
├── users:create ✓
├── users:read ✓
├── users:update ✓
├── users:delete ✓
└── inherits from: ORG_ADMIN
├── organizations:read ✓
├── organizations:update ✓
└── inherits from: MEMBER
├── profile:read ✓
├── profile:update ✓
└── inherits from: VIEWER
└── dashboard:read ✓

A user with TENANT_ADMIN role has all permissions from TENANT_ADMIN, ORG_ADMIN, MEMBER, and VIEWER.

Get Role Hierarchy

Endpoint

GET /roles/:id/hierarchy

Response (200 OK)

{
"role": {
"id": "tenant-admin-id",
"name": "TENANT_ADMIN",
"scopeLevel": "TENANT"
},
"depth": 0,
"children": [
{
"role": {
"id": "org-admin-id",
"name": "ORG_ADMIN",
"scopeLevel": "ORGANIZATION"
},
"depth": 1,
"children": [
{
"role": {
"id": "member-id",
"name": "MEMBER",
"scopeLevel": "ORGANIZATION"
},
"depth": 2,
"children": [
{
"role": {
"id": "viewer-id",
"name": "VIEWER",
"scopeLevel": "ORGANIZATION"
},
"depth": 3,
"children": []
}
]
}
]
}
]
}

Setting Up Inheritance

When creating a role, specify the parentId to inherit from:

Request

{
"name": "SENIOR_DEVELOPER",
"description": "Senior developer with additional permissions",
"scopeLevel": "ORGANIZATION",
"parentId": "member-role-id"
}

The new role inherits all permissions from MEMBER and its ancestors.

Effective Permissions

To get all permissions a user has (including inherited):

Endpoint

GET /users/:id/effective-permissions

Response (200 OK)

{
"permissions": [
{ "resource": "users", "action": "create", "source": "TENANT_ADMIN" },
{ "resource": "users", "action": "read", "source": "TENANT_ADMIN" },
{ "resource": "organizations", "action": "read", "source": "ORG_ADMIN" },
{ "resource": "profile", "action": "read", "source": "MEMBER" },
{ "resource": "dashboard", "action": "read", "source": "VIEWER" }
]
}

The source field indicates which role granted each permission.

Hierarchy Rules

1. No Circular References

A role cannot be its own ancestor:

ROLE_A → ROLE_B → ROLE_A  ❌ Not allowed

2. Maximum Depth

Hierarchy is limited to 5 levels by default:

Level 0: TENANT_ADMIN
Level 1: ORG_ADMIN
Level 2: MANAGER
Level 3: MEMBER
Level 4: VIEWER ← Maximum depth

3. Scope Level Consistency

Child roles must have the same or narrower scope:

TENANT scope → ORGANIZATION scope  ✓ Valid
ORGANIZATION scope → TENANT scope ❌ Invalid

Modifying Hierarchy

Change Parent Role

PATCH /roles/:id
{
"parentId": "new-parent-role-id"
}
caution

Changing a parent role affects all users with that role. Their effective permissions will change immediately.

Remove Inheritance

Set parentId to null:

{
"parentId": null
}

Visualization

Building a Role Tree

interface RoleNode {
role: {
id: string;
name: string;
scopeLevel: string;
};
depth: number;
children: RoleNode[];
}

function renderRoleTree(node: RoleNode, indent = 0): string {
const prefix = ' '.repeat(indent) + (indent > 0 ? '└── ' : '');
let output = `${prefix}${node.role.name}\n`;

node.children.forEach(child => {
output += renderRoleTree(child, indent + 1);
});

return output;
}

// Output:
// TENANT_ADMIN
// └── ORG_ADMIN
// └── MEMBER
// └── VIEWER

Code Examples

TypeScript

interface RoleHierarchy {
role: {
id: string;
name: string;
scopeLevel: string;
};
depth: number;
children: RoleHierarchy[];
}

async function getRoleHierarchy(
accessToken: string,
roleId: string
): Promise<RoleHierarchy> {
const response = await fetch(
`http://localhost:8091/roles/${roleId}/hierarchy`,
{
headers: { 'Authorization': `Bearer ${accessToken}` },
}
);
return response.json();
}

// Flatten hierarchy to list all descendants
function flattenHierarchy(node: RoleHierarchy): string[] {
const roles = [node.role.name];
for (const child of node.children) {
roles.push(...flattenHierarchy(child));
}
return roles;
}

cURL

# Get role hierarchy
curl http://localhost:8091/roles/tenant-admin-id/hierarchy \
-H "Authorization: Bearer <access-token>"

# Create role with parent
curl -X POST http://localhost:8091/roles \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <access-token>" \
-d '{
"name": "SENIOR_DEV",
"description": "Senior developer",
"scopeLevel": "ORGANIZATION",
"parentId": "member-role-id"
}'