Skip to content

Role Based Access Control

arminrad edited this page Mar 16, 2026 · 2 revisions

Role-Based Access Control (RBAC)

Granular permissions and role management


Overview

Multi-tier permission system:

  • Roles: Admin, User, Developer, Support
  • Permissions: Read, Write, Delete, Admin
  • Resources: Users, API Keys, Payments, Models
  • Scope-based: Fine-grained access control

Default Roles

User (Default)

  • Manage own account
  • Create API keys
  • Make API requests
  • View own usage
  • Purchase credits

Developer

  • All User permissions
  • Access API documentation
  • View system status
  • Test endpoints

Support

  • View user accounts
  • Read user activity
  • Assist with issues
  • Cannot modify payments

Admin

  • Full system access
  • User management
  • Payment operations
  • System configuration
  • Audit log access

Database Schema

roles Table

CREATE TABLE roles (
  id INTEGER PRIMARY KEY,
  name TEXT UNIQUE NOT NULL,
  description TEXT,
  permissions JSONB NOT NULL,
  is_system BOOLEAN DEFAULT false,
  created_at TIMESTAMP DEFAULT NOW()
);

user_roles Table

CREATE TABLE user_roles (
  user_id INTEGER NOT NULL REFERENCES users(id),
  role_id INTEGER NOT NULL REFERENCES roles(id),
  assigned_at TIMESTAMP DEFAULT NOW(),
  assigned_by INTEGER REFERENCES users(id),
  PRIMARY KEY (user_id, role_id)
);

Permissions Structure

{
  "users": {
    "read": true,
    "write": false,
    "delete": false
  },
  "api_keys": {
    "read": true,
    "write": true,
    "delete": true
  },
  "payments": {
    "read": true,
    "write": false,
    "delete": false
  },
  "admin": {
    "read": false,
    "write": false,
    "delete": false
  }
}

Checking Permissions

In Code

from src.services.roles import check_permission

@app.get("/admin/users")
async def list_users(user: User = Depends(get_current_user)):
    if not check_permission(user, "users", "read"):
        raise HTTPException(403, "Permission denied")

    return get_all_users()

Decorator

from src.security.deps import require_permission

@app.delete("/users/{user_id}")
@require_permission("users", "delete")
async def delete_user(user_id: int):
    return delete_user_account(user_id)

API Endpoints

Get User Roles

GET /user/roles

Response:

{
  "roles": [
    {
      "id": 1,
      "name": "user",
      "permissions": {...}
    }
  ]
}

Assign Role (Admin)

POST /admin/users/{user_id}/roles

Request:

{
  "role_id": 2
}

Remove Role (Admin)

DELETE /admin/users/{user_id}/roles/{role_id}

Create Custom Role (Admin)

POST /admin/roles

Request:

{
  "name": "content_moderator",
  "description": "Can moderate user content",
  "permissions": {
    "users": {"read": true},
    "content": {"read": true, "write": true}
  }
}

Permission Scopes

Resource-Level

# Can read any user
check_permission(user, "users", "read")

# Can write own data only
check_permission(user, "users", "write", resource_id=user.id)

API Key Scopes

{
  "scope_permissions": {
    "read": ["models", "balance"],
    "write": ["chat"],
    "admin": []
  }
}

Role Hierarchy

Admin
  ├── Support
  │     └── Developer
  │           └── User

Higher roles inherit lower role permissions.


Use Cases

Support Team Access

# Create support role
support_role = create_role(
    name="support",
    permissions={
        "users": {"read": true, "write": false},
        "tickets": {"read": true, "write": true},
        "payments": {"read": true, "write": false}
    }
)

# Assign to support team member
assign_role(user_id=support_user.id, role_id=support_role.id)

API Key Restrictions

# Limited scope key
api_key = create_api_key(
    user_id=user.id,
    scope_permissions={
        "read": ["models"],  # Can only read models
        "write": [],         # Cannot write anything
        "admin": []          # No admin access
    }
)

Implementation

Check Permission Function

def check_permission(
    user: User,
    resource: str,
    action: str,
    resource_id: Optional[int] = None
) -> bool:
    # Get user roles
    roles = get_user_roles(user.id)

    # Check each role
    for role in roles:
        perms = role.permissions.get(resource, {})

        # Check permission
        if perms.get(action):
            # Check resource ownership if needed
            if resource_id and not is_owner(user.id, resource_id):
                continue
            return True

    return False

Middleware

@app.middleware("http")
async def rbac_middleware(request: Request, call_next):
    # Get user and required permission
    user = get_user_from_request(request)
    required = get_required_permission(request.url.path, request.method)

    # Check permission
    if required and not check_permission(user, *required):
        return JSONResponse(
            status_code=403,
            content={"error": "Permission denied"}
        )

    return await call_next(request)

Audit Logging

All role changes are logged:

await log_audit_event(
    event_type="role_assigned",
    event_category="authorization",
    user_id=target_user.id,
    admin_id=admin.id,
    details={
        "role_id": role.id,
        "role_name": role.name
    },
    severity="high"
)

Best Practices

  1. Principle of least privilege: Grant minimum needed
  2. Regular audits: Review role assignments
  3. Custom roles: Create specific roles as needed
  4. Temporary access: Use expiration dates
  5. Audit all changes: Log role modifications
  6. Test permissions: Verify access controls
  7. Document roles: Clear role descriptions

Security Considerations

Privilege Escalation Prevention

# Users cannot grant roles they don't have
def can_assign_role(admin: User, role: Role) -> bool:
    admin_roles = get_user_roles(admin.id)

    # Check if admin has all permissions in role
    for resource, perms in role.permissions.items():
        for action, allowed in perms.items():
            if allowed and not check_permission(admin, resource, action):
                return False

    return True

Resource Ownership

# Users can only modify their own resources
def is_owner(user_id: int, resource_id: int) -> bool:
    resource = get_resource(resource_id)
    return resource.user_id == user_id

Migration Guide

From Simple to RBAC

# Old: Simple admin check
if not user.is_admin:
    raise PermissionDenied()

# New: RBAC check
if not check_permission(user, "users", "write"):
    raise PermissionDenied()

Related Documentation


Last Updated: December 2024 Status: Production Ready


Related

Clone this wiki locally