Appearance
App Permissions Module
Overview
The App Permissions system controls access to specific application features (modules) beyond standard data CRUD permissions. While odp_permissions controls which collections/fields a user can read/write, App Permissions control things like: can this user access the MCP interface? Can they view the workflow module? Can they export data?
Permissions are granted per policy (which maps to roles via odp_access). Each permission record specifies:
- Which module (e.g.,
mcp,workflow,data_export) - Which action (e.g.,
access,execute,read) - Optional collection scope (restrict to a specific collection)
Key Concepts
Modules
Modules are registered in appModuleRegistry. Each module defines:
- A module ID and name
- A set of allowed actions
- Whether actions support collection-level scoping
Policies
App permissions link to odp_policies, not directly to roles. A role can have multiple policies via odp_access. Effective permissions = union of all permissions across all policies linked to the user's roles.
Endpoints
All endpoints require Admin access unless noted.
GET /app-permissions/modules
List all registered app modules.
Response:
json
{
"data": [
{
"id": "mcp",
"name": "MCP Server",
"actions": [
{ "id": "access", "name": "Access", "collectionScopeSupport": false }
]
},
{
"id": "workflow",
"name": "Workflow",
"actions": [
{ "id": "access", "name": "Access", "collectionScopeSupport": false },
{ "id": "manage", "name": "Manage", "collectionScopeSupport": true }
]
}
]
}GET /app-permissions/modules/:moduleId
Get a specific module definition.
URL Parameters:
moduleId— Module identifier
Response: Single module object (same structure as list item).
GET /app-permissions
List all app permission records.
Query Parameters:
| Parameter | Description |
|---|---|
policy | Filter by policy UUID |
module | Filter by module ID |
action | Filter by action ID |
limit | Page size (default: 100) |
offset | Page offset (default: 0) |
Response:
json
{
"data": [
{
"id": "perm-uuid",
"policy": "policy-uuid",
"module": "mcp",
"action": "access",
"collectionScope": "__global__",
"createdAt": "2024-01-01T00:00:00.000Z",
"createdBy": "user-uuid"
}
]
}POST /app-permissions
Create a new app permission.
Request Body:
json
{
"policy": "policy-uuid",
"module": "workflow",
"action": "manage",
"collection_scope": "my_collection"
}| Field | Type | Required | Description |
|---|---|---|---|
policy | UUID | Yes | Policy to grant permission to |
module | string | Yes | Registered module ID |
action | string | Yes | Action within the module, or * for all |
collection_scope | string | No | __global__ (default) or collection name |
Validation:
- Module must be registered in
appModuleRegistry - Action must exist in the module (unless
*) collection_scopeonly allowed if action hascollectionScopeSupport = true- Policy must exist in
odp_policies - Unique constraint:
(policy, module, action, collection_scope)
Response: 201 Created
json
{
"data": {
"id": "new-perm-uuid",
"policy": "policy-uuid",
"module": "workflow",
"action": "manage",
"collectionScope": "my_collection"
}
}DELETE /app-permissions/:id
Delete an app permission record.
URL Parameters:
id— Permission UUID
Response: 204 No Content
Side Effect: Invalidates app permission cache for all users linked to the permission's policy.
GET /app-permissions/user/:userId/effective
Get the effective (resolved) app permissions for a specific user.
Auth required: Admin
URL Parameters:
userId— User UUID
Response:
json
{
"data": {
"mcp": {
"access": true
},
"workflow": {
"access": true,
"manage": ["my_collection", "__global__"]
}
}
}Business Logic:
- Fetches all policies linked to the user via roles →
odp_access - Unions all
odp_app_permissionsrecords across those policies - Returns a map of
{ module: { action: true | string[] } }
GET /app-permissions/me/effective
Get effective app permissions for the current authenticated user.
Auth required: Yes
Response: Same structure as /user/:userId/effective.
Audit Logs
GET /app-access-logs
Query app permission access log entries.
Auth required: Admin
Query Parameters:
| Parameter | Description |
|---|---|
user_id | Filter by user ID |
module | Filter by module |
action | Filter by action |
result | pass or fail |
from | Start datetime (ISO 8601) |
to | End datetime (ISO 8601) |
limit | Page size |
offset | Page offset |
Response:
json
{
"data": [
{
"id": "log-uuid",
"user_id": "user-uuid",
"initiated_by": "user-uuid",
"module": "mcp",
"action": "access",
"collection_scope": null,
"result": "pass",
"policy_id": "policy-uuid",
"fail_reason": null,
"ip": "192.168.1.1",
"user_agent": "Mozilla/5.0...",
"created_at": "2024-01-01T12:00:00.000Z"
}
]
}Data Model
odp_app_permissions
| Column | Type | Description |
|---|---|---|
id | UUID PK | |
policy | UUID FK → odp_policies | Policy granting permission (CASCADE delete) |
module | text | Module identifier |
action | text | Action identifier (or *) |
collection_scope | text | __global__ or collection name |
created_at | timestamp | |
created_by | UUID FK → odp_users | Admin who created the permission |
Unique constraint: (policy, module, action, collection_scope)
Indexes:
idx_app_perm_policyonpolicyidx_app_perm_module_actionon(module, action)
odp_app_access_logs
| Column | Type | Description |
|---|---|---|
id | UUID PK | |
user_id | text | User making the request |
initiated_by | text | Actual user (differs if impersonating) |
module | text | Module being accessed |
action | text | Action being performed |
collection_scope | text | Collection scope (if applicable) |
result | text | pass or fail |
policy_id | text | Policy that granted access (on pass) |
fail_reason | text | Reason for failure (on fail) |
ip | text | Client IP |
user_agent | text | Browser/client |
created_at | timestamp |
Indexes:
idx_app_log_useronuser_ididx_app_log_createdoncreated_atidx_app_log_module_actionon(module, action)idx_app_log_resultonresult