Role Levels

This page defines the shared Octopus role-level policy.

Role Matrix

Role

Level

Viewer

1

User

2

Operator

3

Engineer

4

Admin

5

Default for setting-level gates:

  • If a setting does not define level, it is treated as level: 0 (visible to all logged-in users).

Semantics

  • A user can access a feature/setting only when: userRoleLevel >= requiredLevel.

  • Role names are canonicalized case-insensitively (admin, ADMIN -> Admin).

  • Unknown/invalid role names are treated as Viewer.

  • Role level must be enforced server-side for security. Frontend checks are UX only.

Backend

  • Source-of-truth mapping lives in auth/roles.py.

  • Auth session/token claims include role and role_level.

  • GraphQL authorization enforces minimum levels for sensitive operations.

  • Dynamic endpoint-defined GraphQL mutations require Operator (level 3) or higher.

  • Preference access is user-scoped by default:

    • A user can read/update only their own preferences (userId must match the authenticated user).

    • If userId belongs to someone else, the request is denied.

    • Only Admin (level 5) can read/update another user’s preferences.

Frontend

  • Frontend reads JWT claims (role, optional role_level) for UI behavior.

  • Frontend fetches canonical role levels from backend /auth/roles and uses them as the primary mapping.

  • If backend role levels are temporarily unavailable, frontend uses cached values (or built-in defaults) as fallback.

  • Widget settings UI hides fields where field.level > userRoleLevel.

  • Frontend filtering is not a security boundary.

SDK

  • Widget config fields support optional level?: number.

  • JSON-schema style widget settings can define level per property.

  • SDK schema conversion preserves level so host apps can enforce visibility.

Example

const schema = {
  type: 'object',
  properties: {
    useLiveData: {
      type: 'boolean',
      title: 'Use Live Data',
      level: 5,
      default: true
    },
    title: {
      type: 'string',
      title: 'Title',
      default: '' // implicit level 0
    }
  }
};