Skip to content

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

ColumnTypeDescription
idUUIDPrimary key
namevarcharDisplay name
iconvarcharUI icon name
descriptiontextRole description
admin_accessbooleanBypasses all permission checks
tech_accessbooleanSystem/debug access (implies admin_access)
app_accessbooleanCan access the app panel
impersonate_accessbooleanCan 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 users

Role 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

ColumnTypeDescription
idUUIDPrimary key
namevarcharPolicy name
iconvarcharUI icon
descriptiontextDescription
admin_accessbooleanPolicy grants admin access
app_accessbooleanPolicy grants app access
enforce_tfabooleanRequire 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

ColumnTypeDescription
idintegerPrimary key
policyUUIDFK to odp_policies.id
collectionvarcharCollection name (e.g., articles)
actionvarcharcreate, read, update, delete, share
fieldsjsonArray of allowed field names (["*"] = all)
permissionsjsonFilter condition — limits which items are accessible
validationjsonZod-compatible validation rules for write operations
presetsjsonDefault field values applied on create

Permission Actions

ActionDescription
createInsert new items
readRead items (with optional row-level filter)
updateModify existing items
deleteRemove items
shareShare 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:

ModuleActions
workflowview, start, participate, manage

App permissions are checked via validateAppAccess(accountability, module, action, collection, knex) in src/permissions/index.ts.


How Permissions Are Evaluated

  1. Admin users (admin_access: true) bypass all RBAC.
  2. Tech users (tech_access: true) imply admin_access.
  3. For non-admins, the service loads all policies attached to the user's roles.
  4. Policies are merged — if any policy grants access, the action is allowed.
  5. Row-level filters from all matching permissions are OR-combined.
  6. 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"
}

ODP Internal API Documentation