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.
This commit is contained in:
275
planning/25_Permissions_RBAC.md
Normal file
275
planning/25_Permissions_RBAC.md
Normal file
@@ -0,0 +1,275 @@
|
||||
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
|
||||
|
||||
```typescript
|
||||
// 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
|
||||
|
||||
```typescript
|
||||
// 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
|
||||
Reference in New Issue
Block a user