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
USERrole - Role Changes: Only
ADMINusers can modify user roles - Database Field: Stored in the
rolecolumn of theusertable
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
Always use permission checks: Use
createPermissionProcedure()instead ofprotectedProcedure()when an action requires specific permissionsGraceful degradation: In the UI, hide or disable features that users don't have permission to use
Clear error messages: Provide users with clear feedback when they lack permissions
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)