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.
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:
- Get all roles assigned to the user
- Get all permissions for those roles
- Check if the required permission is in the set
- 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
- Create permission, role, role_permission, user_role tables
- Seed system permissions (all domain.action combinations)
- Seed default roles with their permission mappings
- Migrate existing users: map current
roleenum to the equivalent new role - Add
app.requirePermission()decorator alongside existingapp.requireRole() - Gradually replace
requireRole()calls withrequirePermission()in routes - Drop the
roleenum column from user table (after full migration) - 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