Role-Based Access Control (ST-201)
Overview
Implements repository-level role-based access control (RBAC) using CASL for permission management. Three roles are defined: admin, member, and read_only, each with distinct permissions for repository resources.
Roles and Permissions
Admin Role
- Read: All resources (repo, test_case, test_run, config, role, audit)
- Write: Repository resources (repo, test_case, test_run, config)
- Manage: Repository resources (repo, test_case, test_run, config)
- Admin: Role and configuration management
Member Role
- Read: Repository resources (repo, test_case, test_run, config)
- Write: Repository resources (repo, test_case, test_run, config)
- Cannot: Manage roles or perform admin actions
Read-Only Role
- Read: Repository resources (repo, test_case, test_run, config)
- Cannot: Write, manage, or perform admin actions
Data Model
UserRepoRoletable links users to repositories with rolesRoleAuditLogtable tracks all role changes- Unique constraint on
userId+repoIdcombination - Indexed for efficient role lookups
API Endpoints
List Users with Roles
GET /api/repos/[repoId]/roles
Returns all users with their roles for a repository.
Response:
{
"users": [
{
"id": "user-id",
"userId": "user-id",
"repoId": "repo-id",
"role": "admin",
"user": {
"id": "user-id",
"email": "user@example.com",
"name": "User Name"
}
}
]
}
Assign/Update Role
POST /api/repos/[repoId]/roles
Assigns or updates a user's role in a repository.
Body:
{
"userId": "user-uuid",
"role": "admin" | "member" | "read_only"
}
Response:
{
"message": "Role assigned successfully",
"userId": "user-uuid",
"role": "admin"
}
Remove Role
DELETE /api/repos/[repoId]/roles
Removes a user's role from a repository.
Body:
{
"userId": "user-uuid"
}
Response:
{
"message": "Role removed successfully",
"userId": "user-uuid"
}
Get Audit Logs
GET /api/repos/[repoId]/roles/audit?limit=50
Returns role change audit logs for a repository.
Query Parameters:
limit(optional): Number of logs to return (default: 50)
Response:
{
"logs": [
{
"id": "log-id",
"userId": "user-id",
"repoId": "repo-id",
"action": "assigned" | "removed",
"role": "admin",
"actorId": "actor-user-id",
"createdAt": "2025-01-01T00:00:00Z"
}
]
}
Usage
Check Permissions
import { getUserAbility, can } from '@/lib/server/rbac-service';
const ability = await getUserAbility(userId, repoId);
if (can(ability, 'read', 'repo')) {
// User can read repository
}
if (can(ability, 'admin', 'role')) {
// User can manage roles
}
Require Permission
import { requireAuthz } from '@/lib/server/authz';
// In API route handler
await requireAuthz(request, 'admin', 'role', repoId);
// Throws error if permission denied
Assign Default Role
import { assignDefaultRole, RepoRole } from '@/lib/rbac-defaults';
// Assign default member role to new user
await assignDefaultRole(userId, repoId, RepoRole.MEMBER);
Implementation Details
Permission System
- Uses CASL (Isomorphic Authorization) for permission management
- Type-safe permission checks with TypeScript
- Ability instances created per user/repository combination
Service Layer
Location: apps/web/src/lib/server/rbac-service.ts
getUserRepoRole()- Get user's role for a repositorygetUserAbility()- Get CASL ability instanceassignRole()- Assign/update role with audit loggingremoveRole()- Remove role with audit logginggetRepoUsers()- List all users with rolesgetRoleAuditLogs()- Get audit logsuserHasAdminRole()- Check if user is admin
Authorization Middleware
Location: apps/web/src/lib/server/authz.ts
getAuthzContext()- Extract user/repo from sessionrequireAuthz()- Enforce permissions (throws on denial)
Audit Logging
All role changes are logged to audit logs:
role_assigned- When a role is assignedrole_removed- When a role is removed
Audit logs include:
- Actor (who made the change)
- Target user
- Repository
- Role assigned/removed
- Timestamp
Testing
Unit Tests
Location: apps/web/src/lib/__tests__/rbac.test.ts
Tests cover:
- Permission checks for each role
- Ability creation
- Permission denial scenarios
Integration Tests
Location: apps/web/src/lib/server/__tests__/rbac-service.test.ts
Tests cover:
- Role assignment and removal
- Audit log creation
- Permission enforcement
Operations
- Default roles assigned to new repository members (configurable)
- Role changes immediately reflected (no caching)
- Audit logs are immutable (append-only)
- Role removal does not delete user from repository
Future Enhancements
- Custom roles with configurable permissions
- Permission inheritance from organization level
- Role templates for common permission sets
- Time-based role assignments (temporary roles)