AdminRoles & Permissions

Roles & Permissions

The admin system uses a role-based access control (RBAC) model with granular permissions. Roles are stored in the ADMINDB D1 database and cached at the edge...

Roles & Permissions

The admin system uses a role-based access control (RBAC) model with granular permissions. Roles are stored in the ADMIN_DB D1 database and cached at the edge via KV.

Built-in Roles

Three roles are seeded by the migration and cover the most common access patterns:

RoleDisplay NameDescriptionPermissions
viewerViewerRead-only access to admin dashboards and logs6 permissions
editorEditorRead and write access to configuration, flags, and tiers16 permissions
super-adminSuper AdminFull administrative access including user management and role assignment27 permissions (all)

Super-admin bypass: Users with the super-admin role skip all permission checks — similar to Unix root. This is enforced in the middleware before any permission evaluation occurs.

Viewer Permissions

admin:read, audit:read, metrics:read, config:read, users:read, flags:read

Editor Permissions

admin:read, audit:read, metrics:read, config:read, config:write,
users:read, flags:read, flags:write, tiers:read, tiers:write,
scopes:read, scopes:write, endpoints:read, endpoints:write,
announcements:read, announcements:write

Super-Admin Permissions

All 27 permissions (see full list below). Super-admins also bypass the permission check entirely.

All 27 Permissions

Defined in AdminPermissionSchema (worker/schemas.ts):

PermissionCategoryDescription
admin:readAdminView admin dashboard and system state
admin:writeAdminModify admin system configuration
audit:readAuditQuery audit log entries
metrics:readMetricsView system metrics and analytics
config:readConfigRead tier, scope, and endpoint configuration
config:writeConfigModify tier, scope, and endpoint configuration
users:readUsersView user list and user details
users:writeUsersUpdate user records
users:manageUsersSuspend, activate, and manage user lifecycle
flags:readFlagsView feature flags
flags:writeFlagsCreate, update, and delete feature flags
tiers:readTiersView tier configuration
tiers:writeTiersCreate, update, and delete tier configuration
scopes:readScopesView scope configuration
scopes:writeScopesCreate, update, and delete scope configuration
endpoints:readEndpointsView endpoint auth overrides
endpoints:writeEndpointsCreate, update, and delete endpoint auth overrides
announcements:readAnnouncementsView system announcements
announcements:writeAnnouncementsCreate, update, and delete announcements
roles:readRolesView role definitions
roles:writeRolesCreate and modify role definitions
roles:assignRolesAssign and revoke roles for users
keys:readAPI KeysView API key metadata
keys:writeAPI KeysCreate API keys
keys:revokeAPI KeysRevoke API keys
storage:readStorageView storage statistics and data
storage:writeStorageClear cache, run vacuum, and manage storage

Role Resolution Flow

Every admin API request resolves the caller's role and permissions before evaluating access. The flow prioritizes speed via KV caching:

Cache Details

  • Key format: admin:role:{clerkUserId}
  • Namespace: RATE_LIMIT (shared KV namespace, uses admin: prefix)
  • TTL: 300 seconds (5 minutes)
  • Invalidation: Explicit delete on role assign/revoke via invalidateRoleCache()
  • Stored value: JSON { role_name, permissions[], expires_at }

Middleware Pipeline

Admin routes are protected by middleware functions defined in worker/middleware/admin-role-middleware.ts. The pipeline runs in order:

Guard Functions

Three middleware variants are exported for different use cases:

// Require a single permission
requireAdminPermission('config:write')

// Require at least one of several permissions (OR logic)
requireAnyAdminPermission(['config:write', 'tiers:write'])

// Require all listed permissions (AND logic)
requireAllAdminPermissions(['audit:read', 'metrics:read'])

There is also extractAdminContext() which resolves the caller's role and permissions without enforcing a specific permission check. This is used by the "my context" endpoint.

AdminGuardResult

Every guard returns a discriminated union:

type AdminGuardResult =
    | { authorized: true; adminContext: ResolvedAdminContext }
    | { authorized: false; error: string; status: 401 | 403 };

Handlers check .authorized and return the appropriate HTTP status if access is denied.

Role Management API

Roles are managed via the admin API. Only users with the roles:write or roles:assign permission can modify roles.

OperationEndpointPermission
List rolesGET /admin/system/rolesadmin:read
Create rolePOST /admin/system/rolesroles:write
Update rolePATCH /admin/system/roles/:idroles:write
List assignmentsGET /admin/system/roles/assignmentsadmin:read
Assign rolePOST /admin/system/roles/assignroles:assign
Revoke roleDELETE /admin/system/roles/revokeroles:assign

Example: Assign a role

curl -X POST https://your-worker.workers.dev/admin/system/roles/assign \
  -H "Authorization: Bearer $CLERK_JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "clerk_user_id": "user_2abc123",
    "role_name": "editor",
    "expires_at": "2025-12-31T23:59:59Z"
  }'

Example: Revoke a role

curl -X DELETE https://your-worker.workers.dev/admin/system/roles/revoke \
  -H "Authorization: Bearer $CLERK_JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "clerk_user_id": "user_2abc123",
    "role_name": "editor"
  }'

Custom Roles

Beyond the three built-in roles, you can create custom roles with any combination of the 27 permissions:

curl -X POST https://your-worker.workers.dev/admin/system/roles \
  -H "Authorization: Bearer $CLERK_JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "role_name": "flag-manager",
    "display_name": "Flag Manager",
    "description": "Can manage feature flags but nothing else",
    "permissions": ["admin:read", "flags:read", "flags:write"]
  }'

Custom roles follow the same resolution flow and caching behavior as built-in roles.