Skip to main content

RBAC

Lumie's authorization model is layered:

  1. coarse identity and role come from the auth module and JWT claims
  2. tenant-scoped staff permissions live in the staff module
  3. 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

PathRole
libs/common/src/main/java/com/lumie/common/auth/Role.javaGlobal role hierarchy and legacy alias parsing
libs/common/src/main/java/com/lumie/common/auth/AuthorizationGuard.javaShared owner-only guard
modules/staff/src/main/java/com/lumie/staff/domain/entity/Permission.javaTenant-scoped permission catalog
modules/staff/src/main/java/com/lumie/staff/domain/entity/StaffPermission.javaStaff-to-permission assignment rows
modules/staff/src/main/java/com/lumie/staff/domain/vo/AccessLevel.javaPermission access level enum
modules/staff/src/main/java/com/lumie/staff/application/service/StaffCommandService.javaRole hierarchy enforcement on staff lifecycle operations
modules/staff/src/main/java/com/lumie/staff/adapter/in/web/PermissionController.javaPermission query surface

Enforcement Layers

Global Role Hierarchy

The shared Role enum defines four coarse roles:

RoleAuthority levelTypical meaning
OWNERHighestTenant owner
MANAGERHighManagement staff
INSTRUCTORMidTeaching or staff role
STUDENTLowestStudent-facing user

Two helper methods matter:

  • hasAuthority(required) means equal or higher privilege
  • canManage(target) means strictly outranks the target

Role.fromString(...) also preserves legacy aliases:

  • ADMIN -> INSTRUCTOR
  • DEVELOPER -> 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:

TypeStorage role
PermissionCatalog row with code, name, category, and description
StaffPermissionJoin row keyed by staff_id + permission_code
AccessLevelNONE, READ, or WRITE

Important boundary facts:

  • Permission extends TenantScopedEntity, so permission catalogs are tenant-scoped.
  • StaffPermission does not extend the base class, but it still writes tenant_id explicitly 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

  • JwtAuthenticationFilter writes the user's role into UserContextHolder.
  • Spring Security maps /internal/** to synthetic ROLE_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 MANAGER or INSTRUCTOR
  • 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