Appearance
Roles, Policies & Permissions (RBAC)
Overview
ODP uses a Role-Based Access Control (RBAC) model:
- Roles — Group users and define system-level access flags.
- Policies — Define granular read/write/delete permissions on specific collections and fields.
- Permissions — Individual permission rules within a policy (collection + action + field restrictions + filter conditions).
Roles
Data Model
Table: odp_roles
| Column | Type | Description |
|---|---|---|
id | UUID | Primary key |
name | varchar | Display name |
icon | varchar | UI icon name |
description | text | Role description |
admin_access | boolean | Bypasses all permission checks |
tech_access | boolean | System/debug access (implies admin_access) |
app_access | boolean | Can access the app panel |
impersonate_access | boolean | Can use impersonation endpoints |
Access Flag Hierarchy:
tech_access → admin_access (implied)
admin_access → bypasses all RBAC checks
app_access → can log into the app panel
impersonate_access → can impersonate other usersRole Endpoints
POST /roles
Create a role. Admin only.
Request Body
json
{
"name": "Editor",
"icon": "edit",
"description": "Can create and edit content",
"app_access": true,
"admin_access": false,
"tech_access": false
}Response 200
json
{ "data": "role-uuid" }GET /roles
List all roles.
Auth required: Yes
Supports standard query system.
GET /roles/:id
Read a single role.
PATCH /roles/:id
Update a role. Admin only.
DELETE /roles/:id
Delete a role. Admin only.
Policies
Policies are named permission sets that can be attached to roles.
Data Model
Table: odp_policies
| Column | Type | Description |
|---|---|---|
id | UUID | Primary key |
name | varchar | Policy name |
icon | varchar | UI icon |
description | text | Description |
admin_access | boolean | Policy grants admin access |
app_access | boolean | Policy grants app access |
enforce_tfa | boolean | Require TFA for users with this policy |
Policy Endpoints
POST /policies
Create a policy. Admin only.
json
{
"name": "Content Manager",
"description": "Full access to content collections",
"app_access": true
}GET /policies
List all policies. Supports standard query system.
GET /policies/:id
Read a single policy (with its permissions nested).
PATCH /policies/:id
Update a policy. Admin only.
DELETE /policies/:id
Delete a policy. Admin only.
Permissions
Permissions define what a policy can do on a specific collection.
Data Model
Table: odp_permissions
| Column | Type | Description |
|---|---|---|
id | integer | Primary key |
policy | UUID | FK to odp_policies.id |
collection | varchar | Collection name (e.g., articles) |
action | varchar | create, read, update, delete, share |
fields | json | Array of allowed field names (["*"] = all) |
permissions | json | Filter condition — limits which items are accessible |
validation | json | Zod-compatible validation rules for write operations |
presets | json | Default field values applied on create |
Permission Actions
| Action | Description |
|---|---|
create | Insert new items |
read | Read items (with optional row-level filter) |
update | Modify existing items |
delete | Remove items |
share | Share items publicly |
Field-Level Permissions
The fields array controls which columns are returned/writable:
json
{ "fields": ["title", "status", "published_at"] }Use ["*"] to allow all fields.
Row-Level Permissions (Filter Conditions)
The permissions JSON uses the same filter syntax as query filters:
json
{
"permissions": {
"author_id": { "_eq": "$CURRENT_USER" }
}
}Dynamic variables:
$CURRENT_USER— Current user's UUID$CURRENT_ROLES— Array of current user's role UUIDs$NOW— Current timestamp
Permission Endpoints
Table prefix: /permissions
POST /permissions
json
{
"policy": "policy-uuid",
"collection": "articles",
"action": "read",
"fields": ["*"],
"permissions": {
"status": { "_eq": "published" }
}
}GET /permissions
List all permissions. Admin access.
GET /permissions/me
Get all permissions applicable to the current user (resolves from roles and policies). Any authenticated user.
GET /permissions/:id
Read a single permission. Admin access.
PATCH /permissions/:id
Update a permission. Admin access.
DELETE /permissions/:id
Delete a permission. Admin access.
App-Level Permissions
Beyond collection-level RBAC, certain modules (Workflow, etc.) use app-level permissions stored in odp_app_permissions. These control access to specific module actions:
| Module | Actions |
|---|---|
workflow | view, start, participate, manage |
App permissions are checked via validateAppAccess(accountability, module, action, collection, knex) in src/permissions/index.ts.
How Permissions Are Evaluated
- Admin users (
admin_access: true) bypass all RBAC. - Tech users (
tech_access: true) implyadmin_access. - For non-admins, the service loads all policies attached to the user's roles.
- Policies are merged — if any policy grants access, the action is allowed.
- Row-level filters from all matching permissions are
OR-combined. - Field restrictions are
AND-intersected (most restrictive wins).
Role-Policy Assignment
Roles are linked to policies via the odp_access junction table.
Assign a policy to a role:
bash
PATCH /roles/:roleId
{
"policies": ["policy-uuid-1", "policy-uuid-2"]
}Example: Setting Up an Editor Role
bash
# 1. Create a policy
POST /policies
{
"name": "Article Editor",
"app_access": true
}
# Returns: { "data": "policy-abc" }
# 2. Add permissions to the policy
POST /permissions
{
"policy": "policy-abc",
"collection": "articles",
"action": "read",
"fields": ["*"]
}
POST /permissions
{
"policy": "policy-abc",
"collection": "articles",
"action": "update",
"fields": ["title", "content", "status"],
"permissions": { "author_id": { "_eq": "$CURRENT_USER" } }
}
# 3. Create the role and assign the policy
POST /roles
{
"name": "Editor",
"app_access": true,
"policies": ["policy-abc"]
}
# 4. Assign role to user
PATCH /users/:userId
{
"role": "role-uuid"
}