Files
lunarfront-app/planning/25_Permissions_RBAC.md
Ryan Moon 693121ec14 Add permissions and RBAC planning doc
Granular permission system: domain.action pattern (accounts.view, pos.edit,
repairs.admin). Custom roles composed of permissions. Default system roles
(Admin, Manager, Sales Associate, Technician, Instructor, Viewer). Migration
plan from current role enum. API and frontend guard patterns.
2026-03-28 16:06:23 -05:00

9.2 KiB

Music Store Management Platform

Permissions & Role-Based Access Control

Version 1.0 | Draft

1. Overview

The platform uses a permission-based access control system. Permissions are granular actions (view accounts, edit inventory, approve tax exemptions). Roles are named collections of permissions. Stores can create custom roles by selecting which permissions to include. The system ships with default roles that cover common staff configurations.

2. Permission Structure

Each permission follows the pattern: {domain}.{action}

2.1 Actions

Action | Description view | Read-only access — list, search, view detail edit | Create, update, soft-delete admin | Full control — includes configuration, approval workflows, destructive actions manage | Alias for edit + admin (shorthand for full domain control)

2.2 Domains & Permissions

Domain | Permissions | Description accounts | accounts.view, accounts.edit, accounts.admin | Customer accounts, members, payment methods inventory | inventory.view, inventory.edit, inventory.admin | Products, categories, suppliers, stock pos | pos.view, pos.edit, pos.admin | Transactions, cash drawer, discounts rentals | rentals.view, rentals.edit, rentals.admin | Rental contracts, fleet, billing lessons | lessons.view, lessons.edit, lessons.admin | Scheduling, enrollment, attendance repairs | repairs.view, repairs.edit, repairs.admin | Repair tickets, parts, labor accounting | accounting.view, accounting.edit, accounting.admin | Journal entries, chart of accounts, reports personnel | personnel.view, personnel.edit, personnel.admin | Time clock, scheduling, payroll files | files.view, files.upload, files.delete | File storage operations email | email.view, email.send, email.admin | Email logs, send mass email, manage templates settings | settings.view, settings.edit | Store settings, locations, tax rates users | users.view, users.edit, users.admin | User accounts, roles, permissions reports | reports.view, reports.export | View reports, export data

3. What Each Action Controls

3.1 accounts

Permission | Allows accounts.view | List/search accounts, view account detail, view members, view payment methods, view tax exemptions accounts.edit | Create/update accounts, add/edit/move members, add payment methods, create tax exemption requests accounts.admin | Delete accounts, delete members, approve/revoke tax exemptions, set primary member, manage identifiers

3.2 inventory

Permission | Allows inventory.view | List/search products, view detail, view stock levels, view categories/suppliers inventory.edit | Create/update products, receive stock, update inventory units, manage categories/suppliers inventory.admin | Delete products, adjust stock (cycle counts), manage lookup values (custom statuses/conditions), bulk operations

3.3 pos

Permission | Allows pos.view | View transaction history, view cash drawer sessions pos.edit | Process sales, take payments, apply discounts (within threshold), open/close drawer pos.admin | Void transactions, override prices below min_price, apply discounts above threshold, manage discount rules

3.4 rentals

Permission | Allows rentals.view | View rental contracts, fleet inventory, billing history rentals.edit | Create rentals, process returns, manage fleet, generate agreements rentals.admin | Override rental terms, adjust equity, cancel contracts, approve buyouts

3.5 repairs

Permission | Allows repairs.view | View repair tickets, parts inventory, technician assignments repairs.edit | Create tickets, log parts/labor, update status, manage repair parts inventory repairs.admin | Override estimates, adjust billing, manage repair templates, delete tickets

3.6 files

Permission | Allows files.view | View/download files files.upload | Upload files files.delete | Delete files

3.7 users

Permission | Allows users.view | List users, view roles users.edit | Create/update users, assign roles users.admin | Create/modify roles, manage permissions, delete users

4. Database Schema

4.1 permission

System-defined. Seeded on first run. Not editable by stores.

Column | Type | Notes id | uuid PK | slug | varchar | Unique. e.g. accounts.view, pos.edit domain | varchar | e.g. accounts, pos, inventory action | varchar | e.g. view, edit, admin description | varchar | Human-readable description created_at | timestamptz |

4.2 role

Store-defined roles. System provides defaults, stores can create custom.

Column | Type | Notes id | uuid PK | company_id | uuid FK | Tenant scoping name | varchar | Role display name. e.g. "Sales Associate", "Repair Tech" slug | varchar | Unique per company. e.g. sales_associate description | text | is_system | boolean | System roles can't be deleted (admin, manager, staff, technician, instructor) is_active | boolean | created_at | timestamptz | updated_at | timestamptz |

4.3 role_permission

Many-to-many: which permissions a role has.

Column | Type | Notes id | uuid PK | role_id | uuid FK | permission_id | uuid FK | created_at | timestamptz |

Unique constraint on (role_id, permission_id).

4.4 user_role

Replaces the current role enum on the user table. Users can have multiple roles.

Column | Type | Notes id | uuid PK | user_id | uuid FK | role_id | uuid FK | assigned_by | uuid FK | Who assigned this role created_at | timestamptz |

Unique constraint on (user_id, role_id).

5. Default Roles

5.1 System Roles (seeded per company)

Role | Permissions | Typical User Admin | All permissions | Store owner, IT admin Manager | All except users.admin, settings.edit | Assistant manager, department head Sales Associate | accounts.view, accounts.edit, pos.view, pos.edit, inventory.view, rentals.view, files.view, files.upload | Front counter staff Technician | repairs.view, repairs.edit, inventory.view, files.view, files.upload | Repair technician Instructor | lessons.view, lessons.edit, accounts.view | Music teacher Viewer | *.view (all view permissions) | Read-only access, auditors

5.2 Custom Role Example

A store could create:

"School Sales Rep"

  • accounts.view, accounts.edit
  • rentals.view, rentals.edit
  • pos.view, pos.edit
  • inventory.view

This gives them everything they need to work with school accounts and rental contracts without access to repairs, lessons, or admin functions.

6. Permission Checking

6.1 API Route Guards

// Single permission
app.get('/accounts', {
  preHandler: [app.authenticate, app.requirePermission('accounts.view')]
}, handler)

// Any of multiple permissions
app.post('/transactions', {
  preHandler: [app.authenticate, app.requirePermission('pos.edit')]
}, handler)

// Admin-level action
app.post('/tax-exemptions/:id/approve', {
  preHandler: [app.authenticate, app.requirePermission('accounts.admin')]
}, handler)

6.2 Frontend Guards

// Route level
{
  beforeLoad: () => requirePermission('accounts.view'),
  component: AccountsList,
}

// Component level
{hasPermission('accounts.edit') && <Button>New Account</Button>}

6.3 Resolution

When checking a permission:

  1. Get all roles assigned to the user
  2. Get all permissions for those roles
  3. Check if the required permission is in the set
  4. Cache the permission set in memory for the request duration (loaded on auth)

7. Migration Plan

7.1 Current State

The user table has a role enum column with values: admin, manager, staff, technician, instructor. This is checked via app.requireRole() in a few places.

7.2 Migration Steps

  1. Create permission, role, role_permission, user_role tables
  2. Seed system permissions (all domain.action combinations)
  3. Seed default roles with their permission mappings
  4. Migrate existing users: map current role enum to the equivalent new role
  5. Add app.requirePermission() decorator alongside existing app.requireRole()
  6. Gradually replace requireRole() calls with requirePermission() in routes
  7. Drop the role enum column from user table (after full migration)
  8. Update frontend to check permissions instead of roles

7.3 Backward Compatibility

During migration, both requireRole() and requirePermission() work. The JWT payload includes both role (legacy) and permissions (new array of slugs). Once all routes are migrated, the legacy role field can be removed.

8. Admin UI

8.1 Roles Page

  • List all roles (system + custom)
  • Create custom role: name, description, select permissions via checkboxes grouped by domain
  • Edit custom role permissions
  • View which users are assigned to each role
  • System roles show as locked (can't delete, can edit description)

8.2 User Management

  • Assign/remove roles from users
  • View effective permissions for a user (union of all role permissions)
  • Quick-assign: dropdown of available roles when creating/editing a user

9. Business Rules

  • Every user must have at least one role
  • Admin role cannot be removed from the last admin user in a company
  • System roles can be customized (add/remove permissions) but not deleted
  • Custom roles are company-scoped
  • Permission checks are additive — if any assigned role has the permission, the user has it
  • New permissions added in updates are automatically added to the Admin role
  • Role changes take effect on next login (JWT refresh)
  • Audit log records all role assignments and permission changes