Planned role-based access control (RBAC) implementation
Planned feature: This page describes the intended RBAC implementation for OrgStack. The current codebase includes Spring Security as a dependency but does not yet implement roles, permissions, or authorization logic.
OrgStack will implement Role-Based Access Control (RBAC) to manage what authenticated users can do within their organization. After authentication (proving who you are), authorization will determine what actions you’re allowed to perform.
OrgStack implements a hierarchical role structure where higher roles inherit permissions from lower roles:
SYSTEM_ADMIN (platform-level, across all tenants) | └─ ORGANIZATION_ADMIN (full control within organization) | ├─ MANAGER (manage users and projects) │ | │ └─ USER (basic access) │ | │ └─ GUEST (read-only) │ └─ AUDITOR (read-only across all data)
Roles are scoped to organizations. An ADMIN in Organization A has no privileges in Organization B. The only exception is SYSTEM_ADMIN, which should be used sparingly.
@RestController@RequestMapping("/api/users")public class UserController { @GetMapping @PreAuthorize("hasRole('USER')") public List<User> listUsers() { // All authenticated users can list users in their org } @PostMapping @PreAuthorize("hasRole('ADMIN')") public User createUser(@RequestBody CreateUserRequest request) { // Only admins can create users } @DeleteMapping("/{id}") @PreAuthorize("hasRole('ADMIN') and #id != authentication.principal.id") public void deleteUser(@PathVariable UUID id) { // Admins can delete users, but not themselves }}
Authorization shouldn’t stop at the controller layer. Service methods should also validate permissions:
@Servicepublic class ProjectService { public Project updateProject(UUID projectId, UpdateProjectRequest request) { Project project = projectRepository.findById(projectId) .orElseThrow(() -> new NotFoundException("Project not found")); // Verify the project belongs to the current user's organization UUID currentOrgId = SecurityContext.getCurrentOrganizationId(); if (!project.getOrganizationId().equals(currentOrgId)) { throw new ForbiddenException("Cannot access project from different organization"); } // Verify the user has permission to update this project if (!SecurityContext.hasRole("ADMIN") && !project.getOwnerId().equals(SecurityContext.getCurrentUserId())) { throw new ForbiddenException("Only project owners or admins can update projects"); } // Proceed with update return projectRepository.save(project); }}
public enum Role { ADMIN(Permission.values()), // All permissions MANAGER(Permission.USER_READ, Permission.USER_UPDATE, Permission.PROJECT_CREATE, Permission.PROJECT_READ, Permission.PROJECT_UPDATE), USER(Permission.PROJECT_READ, Permission.USER_READ), GUEST(Permission.PROJECT_READ); private final Set<Permission> permissions;}