Docs /features/user-roles-permissions

User Roles and Permissions

Overview

The application implements a role-based access control (RBAC) system at the user level to manage permissions for various actions throughout the platform.

User Roles

Available Roles

Role Description Default for New Users
USER Standard user with full access to create and manage their own resources ✅ Yes
ADMIN Administrative user with elevated permissions including user management ❌ No
SUSPENDED Suspended user with read-only access, cannot create or modify resources ❌ No

Role Assignment

  • Default Role: All newly created users are automatically assigned the USER role
  • Role Changes: Only ADMIN users can modify user roles
  • Database Field: Stored in the role column of the user table

Permissions

USER Role Permissions

Users with the USER role can:

  • ✅ Create organizations
  • ✅ Create projects
  • ✅ Manage their own resources (projects, organizations they own)
  • ✅ Invite members to organizations they own/admin
  • ✅ Generate and manage API keys
  • ✅ Execute test runs

ADMIN Role Permissions

Admins have all USER permissions plus:

  • ✅ Manage all resources (across all users)
  • ✅ Manage user accounts
  • ✅ Change user roles
  • ✅ View system-wide analytics

SUSPENDED Role Permissions

Suspended users have restricted access:

  • ❌ Cannot create organizations
  • ❌ Cannot create projects
  • ❌ Cannot manage resources
  • ✅ Can view their existing resources (read-only)

Implementation

Checking Permissions in Code

import { hasPermission, type UserRole } from '@izri/shared'

// Check if user can create organization
const userRole: UserRole = 'USER'
const canCreate = hasPermission(userRole, 'canCreateOrganization')

Using Permission-Based Procedures

The tRPC layer provides permission-based procedures:

import { createPermissionProcedure } from '../trpc'

// Require specific permission
const myProcedure = createPermissionProcedure('canCreateOrganization')
  .input(z.object({ /* ... */ }))
  .mutation(async ({ input, ctx }) => {
    // User's role has been verified
    // ctx.user is guaranteed to have the required permission
  })

Admin-Only Procedures

For admin-only operations:

import { adminProcedure } from '../trpc'

const adminOnlyProcedure = adminProcedure
  .input(z.object({ /* ... */ }))
  .mutation(async ({ input, ctx }) => {
    // Only ADMIN users can reach this code
  })

Database Schema

The user table includes a role field:

CREATE TABLE "user" (
  "id" VARCHAR(191) PRIMARY KEY,
  "email" VARCHAR(255) NOT NULL UNIQUE,
  "name" VARCHAR(255),
  "role" VARCHAR(32) NOT NULL DEFAULT 'USER',
  "emailVerified" BOOLEAN NOT NULL DEFAULT false,
  "image" TEXT,
  "createdAt" TIMESTAMP NOT NULL DEFAULT now(),
  "updatedAt" TIMESTAMP NOT NULL DEFAULT now()
);

Error Handling

Permission Denied Errors

When a user attempts an action they don't have permission for:

{
  code: 'FORBIDDEN',
  message: 'You do not have permission to perform this action. Required permission: canCreateOrganization'
}

Role-Based Errors

When a non-admin attempts an admin-only action:

{
  code: 'FORBIDDEN',
  message: 'Admin access required'
}

Best Practices

  1. Always use permission checks: Use createPermissionProcedure() instead of protectedProcedure() when an action requires specific permissions

  2. Graceful degradation: In the UI, hide or disable features that users don't have permission to use

  3. Clear error messages: Provide users with clear feedback when they lack permissions

  4. Audit logging: Log permission-related actions for security auditing (especially role changes)

Future Enhancements

Potential improvements to the permission system:

  • Custom roles with configurable permissions
  • Organization-level role inheritance
  • Time-based role assignments (temporary admin access)
  • Permission groups for easier management
  • More granular permissions (e.g., canDeleteProject, canViewAnalytics)

Related Documentation

Reading this with an agent? /docs/features/user-roles-permissions.md serves the raw markdown.

All docs