RBAC
Lumie's authorization model is layered:
- coarse identity and role come from the auth module and JWT claims
- tenant-scoped staff permissions live in the staff module
- module services add business-state checks such as ownership, lifecycle, and quota rules
This page is a reference document for that current model.
Source Paths
| Path | Role |
|---|---|
libs/common/src/main/java/com/lumie/common/auth/Role.java | Global role hierarchy and legacy alias parsing |
libs/common/src/main/java/com/lumie/common/auth/AuthorizationGuard.java | Shared owner-only guard |
modules/staff/src/main/java/com/lumie/staff/domain/entity/Permission.java | Tenant-scoped permission catalog |
modules/staff/src/main/java/com/lumie/staff/domain/entity/StaffPermission.java | Staff-to-permission assignment rows |
modules/staff/src/main/java/com/lumie/staff/domain/vo/AccessLevel.java | Permission access level enum |
modules/staff/src/main/java/com/lumie/staff/application/service/StaffCommandService.java | Role hierarchy enforcement on staff lifecycle operations |
modules/staff/src/main/java/com/lumie/staff/adapter/in/web/PermissionController.java | Permission query surface |
Enforcement Layers
Global Role Hierarchy
The shared Role enum defines four coarse roles:
| Role | Authority level | Typical meaning |
|---|---|---|
OWNER | Highest | Tenant owner |
MANAGER | High | Management staff |
INSTRUCTOR | Mid | Teaching or staff role |
STUDENT | Lowest | Student-facing user |
Two helper methods matter:
hasAuthority(required)means equal or higher privilegecanManage(target)means strictly outranks the target
Role.fromString(...) also preserves legacy aliases:
ADMIN -> INSTRUCTORDEVELOPER -> MANAGER
That alias behavior matters when old data or old clients still send historical role names.
Tenant-Scoped Staff Permission Model
The staff module adds a second layer of authorization for staff users:
| Type | Storage role |
|---|---|
Permission | Catalog row with code, name, category, and description |
StaffPermission | Join row keyed by staff_id + permission_code |
AccessLevel | NONE, READ, or WRITE |
Important boundary facts:
PermissionextendsTenantScopedEntity, so permission catalogs are tenant-scoped.StaffPermissiondoes not extend the base class, but it still writestenant_idexplicitly at insert time.- Permission query endpoints live under
/v1/permissions. - Staff permission assignment flows live in the staff service and are not globally auto-applied by Spring Security.
How The Backend Enforces RBAC Today
Layer 1: authentication and coarse role
JwtAuthenticationFilterwrites the user's role intoUserContextHolder.- Spring Security maps
/internal/**to syntheticROLE_INTERNAL.
Layer 2: explicit shared guards
AuthorizationGuard.requireOwner() is used for actions that must be owner-only,
such as homepage publishing and parts of tenant administration or exports.
Layer 3: module service rules
StaffCommandService is the clearest example:
- creation only allows
MANAGERorINSTRUCTOR - actors cannot manage themselves
- actors must strictly outrank the target via
canManage(...) - billing quota and assigned classes can still block the operation even after role checks pass
This is an important architectural point: RBAC is not the whole authorization decision. Business invariants continue after role evaluation.
What The Model Does Not Currently Do
- There is no centralized policy engine or single annotation-based fine-grained permission framework.
- Staff permissions are not automatically checked for every endpoint by the web layer.
- Students use the coarse role model; the staff permission catalog is for staff capabilities.
As a result, authorization logic is distributed across:
- Spring Security
- shared guards
- application services
- tenant-scoped permission lookups
Failure Modes And Boundary Risks
- Treating
Role.fromString(...)as strict parsing would miss the legacy alias behavior still present in code. - Adding a new admin workflow without explicit service-level checks can create an authorization hole even if the caller is authenticated.
- Permission data is tenant-scoped, so any cross-tenant administrative tooling must restore tenant context correctly before reading or writing permission rows.
Verification Commands
cd /Users/bluemayne/Projects/Lumie/lumie-backend
./gradlew test
./gradlew :modules:staff:test
./gradlew :modules:auth:test
Most relevant test coverage currently lives in:
modules/staff/src/test/java/com/lumie/staff/application/service/StaffCommandServiceTest.java