Status: ✅ Implemented Version: 1.0 Last Updated: 2025-11-14
CoreStack implements a comprehensive Role-Based Access Control (RBAC) system that provides fine-grained access control for users across projects, groups, and system resources. The system supports Django-style permissions with full TypeScript type safety.
- ✅ Flexible User Management: Single user accounts with multiple project memberships
- ✅ Dynamic Role Management: Create and modify roles without code changes
- ✅ Granular Permissions: Fine-grained control over API endpoints and resources (CRUD)
- ✅ Multi-Level Access Control: Project-specific, cross-project, and global system roles
- ✅ Audit Trail: Complete logging of permission changes and access attempts
- ✅ Performance Optimized: Permission caching with 5-minute TTL
- ✅ User Impersonation: Super admin group can impersonate users for support
- ✅ TypeScript Support: Full type safety throughout the system
The RBAC system uses 13 tables:
- groups - User groups (project, cross-project, functional)
- group_members - User-group memberships
- group_projects - Project associations for cross-project groups
- roles - Role definitions (system, project, cross-project)
- permissions - Permission definitions (resource.action format)
- role_permissions - Role-permission mappings
- user_system_roles - System-level role assignments
- user_project_roles - Project-level role assignments
- user_group_roles - Group-level role assignments
- external_accounts - External system accounts (NIS, LDAP)
- rbac_audit_log - Complete audit trail
- permission_cache - Performance cache
- impersonation_sessions - Active impersonation sessions
Format: <resource>.<action>[.<scope>]
Examples:
user.create- Create usersuser.read- Read user dataproject.update- Update projectsrbac.role.create- Create RBAC rolesssh.execute- Execute SSH commands
| Role | Description | Use Case |
|---|---|---|
| system_admin | Full system access | System administrators |
| system_moderator | User & content moderation | Community managers |
| system_auditor | Read-only access | Compliance officers |
| Role | Description | Use Case |
|---|---|---|
| project_owner | Full project control | Project creators |
| project_admin | Administrative access | Project managers |
| project_member | Regular member access | Team members |
| project_viewer | Read-only access | Stakeholders |
- Run Database Migration:
npm run db:generate
npm run db:push- Seed RBAC System:
npm run db:seed-rbacThis creates:
- 40+ built-in permissions
- 7 built-in roles
- Super admin group
import { assignRole } from '@/lib/rbac';
// Assign system role
await assignRole({
userId: 1,
roleId: 1, // system_admin
});
// Assign project role
await assignRole({
userId: 2,
roleId: 4, // project_member
projectId: 10,
});
// Assign role with expiration
await assignRole({
userId: 3,
roleId: 5,
expiresAt: new Date('2025-12-31'),
});import { checkPermission } from '@/lib/rbac';
// Check system permission
const canCreateUsers = await checkPermission({
userId: 1,
permission: 'user.create',
});
// Check project-scoped permission
const canUpdateProject = await checkPermission({
userId: 2,
permission: 'project.update',
projectId: 10,
});import { router, permissionProcedure } from '@/lib/trpc/trpc';
export const userRouter = router({
// Requires 'user.create' permission
create: permissionProcedure('user.create')
.input(createUserSchema)
.mutation(async ({ ctx, input }) => {
// Permission already checked
return await createUser(input);
}),
// Requires 'user.delete' permission
delete: permissionProcedure('user.delete')
.input(z.object({ id: z.number() }))
.mutation(async ({ ctx, input }) => {
return await deleteUser(input.id);
}),
});import {
requireAnyPermission,
requireAllPermissions,
checkResourcePermission
} from '@/lib/rbac';
// Require ONE of multiple permissions
await requireAnyPermission(
userId,
['user.update', 'user.admin']
);
// Require ALL permissions
await requireAllPermissions(
userId,
['user.read', 'user.admin']
);
// Check with resource ownership
const canUpdate = await checkResourcePermission(
userId,
'post.update',
post.authorId // Owner ID
);Super admins can impersonate users for support and debugging.
import { addUserToGroup } from '@/lib/rbac';
// Add user to super_admins group
const groups = await listGroups({});
const superAdminGroup = groups.find(g => g.name === 'super_admins');
await addUserToGroup(superAdminGroup.id, adminUserId);// Start impersonation session
const session = await trpc.rbac.impersonation.start.mutate({
targetUserId: 123,
reason: 'Support ticket #456',
durationMs: 3600000, // 1 hour
});
// Use session token in API calls
const client = createTRPCClient({
headers: {
'Authorization': `Bearer ${adminJWT}`,
'X-Impersonation-Token': session.sessionToken,
},
});
// All requests now made as user #123
const data = await client.user.getProfile.query();
// End impersonation
await trpc.rbac.impersonation.end.mutate({
sessionToken: session.sessionToken,
});- ✅ Only super_admins group members can impersonate
- ✅ Cannot impersonate yourself
- ✅ Sessions expire automatically (default 1 hour)
- ✅ Complete audit trail with reason, IP, user agent
- ✅ All actions logged with admin and impersonated user IDs
trpc.rbac.roles.list- List all rolestrpc.rbac.roles.create- Create new roletrpc.rbac.roles.update- Update roletrpc.rbac.roles.delete- Delete role (non-built-in only)trpc.rbac.roles.assignPermissions- Assign permissions to roletrpc.rbac.roles.removePermissions- Remove permissions from role
trpc.rbac.permissions.list- List all permissionstrpc.rbac.permissions.create- Create new permissiontrpc.rbac.permissions.update- Update permissiontrpc.rbac.permissions.delete- Delete permission
trpc.rbac.userRoles.assign- Assign role to usertrpc.rbac.userRoles.revoke- Revoke role from usertrpc.rbac.userRoles.getUserPermissions- Get all user permissionstrpc.rbac.userRoles.getMyPermissions- Get current user's permissions
trpc.rbac.groups.list- List groupstrpc.rbac.groups.create- Create grouptrpc.rbac.groups.addMember- Add user to grouptrpc.rbac.groups.removeMember- Remove user from grouptrpc.rbac.groups.getMembers- Get group members
trpc.rbac.impersonation.start- Start impersonation sessiontrpc.rbac.impersonation.end- End impersonation sessiontrpc.rbac.impersonation.getStatus- Get current impersonation statustrpc.rbac.impersonation.getHistory- Get impersonation audit history
- Permission checks are cached for 5 minutes
- Cache key format:
user:{userId}:perm:{permission}:project:{projectId} - Automatic cache invalidation on role changes
- Use
useCache: falseto bypass cache
- Batch Permission Checks:
const results = await checkMultiplePermissions(
userId,
['user.read', 'user.update', 'user.delete']
);- Use Permission Caching:
// Enable caching (default)
await checkPermission({ userId, permission, useCache: true });- Minimize Role Changes: Role changes invalidate cache
- Principle of Least Privilege: Grant minimum necessary permissions
- Regular Audits: Review audit logs regularly
- Time-Limited Roles: Use
expiresAtfor temporary access - Impersonation Logging: Always provide reason for impersonation
- Permission Granularity: Use specific permissions, not wildcards
All RBAC operations are logged:
- Role assignments/revocations
- Permission grants/denials
- Impersonation sessions
- Access attempts
Query audit logs:
const logs = await db
.select()
.from(rbacAuditLog)
.where(eq(rbacAuditLog.userId, userId))
.orderBy(desc(rbacAuditLog.createdAt))
.limit(100);The old project_members table is preserved for backward compatibility.
// Migrate project members to RBAC
import { assignRole, getRoleByName } from '@/lib/rbac';
const roleMapping = {
'owner': 'project_owner',
'admin': 'project_admin',
'member': 'project_member',
'viewer': 'project_viewer',
};
for (const member of oldMembers) {
const roleName = roleMapping[member.role];
const role = await getRoleByName(roleName);
await assignRole({
userId: member.userId,
roleId: role.id,
projectId: member.projectId,
});
}- Check user's roles:
const perms = await getUserPermissions(userId, projectId);
console.log('Roles:', perms.systemRoles, perms.projectRoles);
console.log('Permissions:', perms.permissions);- Verify permission exists:
const perm = await getPermissionByName('user.create');
console.log('Permission:', perm);- Check role permissions:
const rolePerms = await getRolePermissions(roleId);
console.log('Role permissions:', rolePerms);Clear user's permission cache:
import { invalidateUserPermissionCache } from '@/lib/rbac';
await invalidateUserPermissionCache(userId, projectId);- Verify user is in super_admins group
- Check session hasn't expired
- Ensure correct header:
X-Impersonation-Token - Verify session token is valid
See lib/rbac/examples.ts for comprehensive examples covering:
- Basic permission protection
- Multiple permission requirements
- Resource-specific permissions
- Project-scoped permissions
- Conditional permissions
- Impersonation-aware logic
Planned features:
- Attribute-Based Access Control (ABAC)
- Permission templates
- Advanced audit analytics
- Multi-tenancy support
- API key permissions