Talent-Acquisition-Platform

RBAC: Roles and Permissions

System Roles

Six roles are seeded at database migration time. They are system roles (is_system_role = true) and apply across all organizations. Org admins can also create custom org-specific roles.

Role Description
super_admin Platform-level admin. Full access to all orgs, all resources, all actions.
org_admin Organization admin. Full access within their org. Can manage users and roles.
hiring_manager Manages job requisitions, reviews candidate pipeline, views analytics.
recruiter Sources candidates, manages pipeline, uses AI features.
interviewer Read-only access to candidates + jobs. Can submit interview feedback.
viewer Read-only access to candidates, jobs, and analytics.

Permissions Catalogue

All 20 permissions in the system:

Permission Key Description
candidates:read View candidate profiles and lists
candidates:create Add new candidates
candidates:update Edit candidate information
candidates:delete Permanently delete candidates
jobs:read View job requisitions
jobs:create Create new job requisitions
jobs:update Edit job requisitions
jobs:delete Delete job requisitions
pipeline:read View pipeline entries and interview rounds
pipeline:update Move candidates through stages, schedule rounds, manage offers
sourcing:run Start new candidate sourcing jobs
sourcing:view View sourcing job history and results
analytics:read View recruitment analytics and funnel data
users:read View team member list
users:manage Create, edit, deactivate users and assign roles
roles:manage Create and edit custom roles, assign permissions
ai:use Use Claude AI features (scoring, JD parsing, emails, etc.)
settings:view View organization settings
settings:manage Edit organization settings and API keys
integrations:manage Configure third-party integrations (Keka, Gmail, etc.)

Role–Permission Matrix

Permission super_admin org_admin hiring_manager recruiter interviewer viewer
candidates:read ✅ ✅ ✅ ✅ ✅ ✅
candidates:create ✅ ✅   ✅    
candidates:update ✅ ✅   ✅    
candidates:delete ✅ ✅        
jobs:read ✅ ✅ ✅ ✅ ✅ ✅
jobs:create ✅ ✅ ✅      
jobs:update ✅ ✅ ✅      
jobs:delete ✅ ✅        
pipeline:read ✅ ✅ ✅ ✅ ✅  
pipeline:update ✅ ✅ ✅ ✅    
sourcing:run ✅ ✅   ✅    
sourcing:view ✅ ✅   ✅    
analytics:read ✅ ✅ ✅ ✅   ✅
users:read ✅ ✅        
users:manage ✅ ✅        
roles:manage ✅ ✅        
ai:use ✅ ✅ ✅ ✅    
settings:view ✅ ✅ ✅ ✅ ✅ ✅
settings:manage ✅ ✅        
integrations:manage ✅ ✅        

Backend Enforcement

Dependency factory

# app/core/deps.py
def require_permission(permission: str) -> Callable:
    async def _check(user: User = Depends(get_current_user)) -> User:
        perms = get_user_permissions(user)
        if permission not in perms:
            raise HTTPException(403, f"Permission denied: '{permission}' required")
        return user
    return _check

Usage in routes

@router.delete("/{candidate_id}")
async def delete_candidate(
    candidate_id: uuid.UUID,
    user = Depends(require_permission("candidates:delete"))  # ← enforced
):
    ...
@router.get("/admin/users")
async def list_all_users(
    user = Depends(require_role("super_admin"))  # ← role check
):
    ...

Frontend Enforcement

Component-level gating

// Hide delete button from viewers
<PermissionGate permission="candidates:delete">
  <Button variant="danger" onClick={handleDelete}>Delete</Button>
</PermissionGate>

// Show admin panel only to admins
<PermissionGate role={['org_admin', 'super_admin']}>
  <AdminPanel />
</PermissionGate>

Route-level protection

// In App.tsx
<Route path="/admin" element={
  <RoleGuard role={['super_admin', 'org_admin']}>
    <Admin />
  </RoleGuard>
} />

In custom hooks

const { hasPermission, hasRole, isAdmin } = usePermission()

if (hasPermission('sourcing:run')) {
  // Show sourcing button
}

Creating Custom Roles

Org admins can create custom roles via the Admin panel or API:

curl -X POST /api/v1/roles \
  -H "Authorization: Bearer TOKEN" \
  -d '{
    "name": "Senior Recruiter",
    "description": "Can do everything a recruiter does + manage settings",
    "permissions": [
      "candidates:read", "candidates:create", "candidates:update",
      "jobs:read", "pipeline:read", "pipeline:update",
      "sourcing:run", "sourcing:view", "ai:use",
      "analytics:read", "settings:view", "settings:manage"
    ]
  }'

Assigning Roles to Users

Via API

# Create user with role
curl -X POST /api/v1/users \
  -d '{"email": "john@acme.com", "name": "John", "password": "...", "role": "recruiter"}'

# Change user role
curl -X POST /api/v1/roles/assign \
  -d '{"user_id": "uuid-here", "role_name": "hiring_manager"}'

Via Admin UI

/admin → Team tab → Edit user → Change Role


Audit Trail

Every mutating API call (POST, PUT, PATCH, DELETE) that succeeds (HTTP < 400) is automatically logged to audit_logs:

{
  "org_id": "...",
  "user_id": "...",
  "action": "DELETE",
  "resource": "candidates",
  "resource_id": "cand-uuid",
  "ip_address": "1.2.3.4",
  "created_at": "2025-01-01T00:00:00"
}

The audit log is append-only and cannot be deleted through the API.