Implement RBAC with permissions, roles, and route guards

- permission, role, role_permission, user_role_assignment tables
- 42 system permissions across 13 domains
- 6 default roles: Admin, Manager, Sales Associate, Technician, Instructor, Viewer
- Permission inheritance: admin implies edit implies view
- requirePermission() Fastify decorator on ALL routes
- System permissions and roles seeded per company
- Test helpers and API test runner seed RBAC data
- All 42 API tests pass with permissions enforced
This commit is contained in:
Ryan Moon
2026-03-28 17:00:42 -05:00
parent dd03fb79ef
commit 4a1fc608f0
13 changed files with 679 additions and 79 deletions

View File

@@ -0,0 +1,133 @@
/**
* System permissions and default role definitions.
* Seeded per company on creation.
*/
export const SYSTEM_PERMISSIONS = [
// Accounts
{ slug: 'accounts.view', domain: 'accounts', action: 'view', description: 'View accounts, members, payment methods, tax exemptions' },
{ slug: 'accounts.edit', domain: 'accounts', action: 'edit', description: 'Create and edit accounts, members, payment methods' },
{ slug: 'accounts.admin', domain: 'accounts', action: 'admin', description: 'Delete accounts, approve tax exemptions, manage identifiers' },
// Inventory
{ slug: 'inventory.view', domain: 'inventory', action: 'view', description: 'View products, stock levels, categories, suppliers' },
{ slug: 'inventory.edit', domain: 'inventory', action: 'edit', description: 'Create and edit products, receive stock, manage categories' },
{ slug: 'inventory.admin', domain: 'inventory', action: 'admin', description: 'Delete products, adjust stock, manage lookup values' },
// POS
{ slug: 'pos.view', domain: 'pos', action: 'view', description: 'View transaction history, cash drawer sessions' },
{ slug: 'pos.edit', domain: 'pos', action: 'edit', description: 'Process sales, take payments, apply discounts' },
{ slug: 'pos.admin', domain: 'pos', action: 'admin', description: 'Void transactions, override prices, manage discounts' },
// Rentals
{ slug: 'rentals.view', domain: 'rentals', action: 'view', description: 'View rental contracts, fleet, billing' },
{ slug: 'rentals.edit', domain: 'rentals', action: 'edit', description: 'Create rentals, process returns, manage fleet' },
{ slug: 'rentals.admin', domain: 'rentals', action: 'admin', description: 'Override terms, adjust equity, cancel contracts' },
// Lessons
{ slug: 'lessons.view', domain: 'lessons', action: 'view', description: 'View lesson schedules, enrollments, attendance' },
{ slug: 'lessons.edit', domain: 'lessons', action: 'edit', description: 'Manage scheduling, enrollment, attendance' },
{ slug: 'lessons.admin', domain: 'lessons', action: 'admin', description: 'Configure lesson settings, manage instructors' },
// Repairs
{ slug: 'repairs.view', domain: 'repairs', action: 'view', description: 'View repair tickets, parts inventory' },
{ slug: 'repairs.edit', domain: 'repairs', action: 'edit', description: 'Create tickets, log parts and labor, update status' },
{ slug: 'repairs.admin', domain: 'repairs', action: 'admin', description: 'Override estimates, manage templates, delete tickets' },
// Accounting
{ slug: 'accounting.view', domain: 'accounting', action: 'view', description: 'View journal entries, reports, AR aging' },
{ slug: 'accounting.edit', domain: 'accounting', action: 'edit', description: 'Create journal entries, manage chart of accounts' },
{ slug: 'accounting.admin', domain: 'accounting', action: 'admin', description: 'Close periods, adjust entries, export data' },
// Personnel
{ slug: 'personnel.view', domain: 'personnel', action: 'view', description: 'View schedules, time entries' },
{ slug: 'personnel.edit', domain: 'personnel', action: 'edit', description: 'Manage schedules, approve time off' },
{ slug: 'personnel.admin', domain: 'personnel', action: 'admin', description: 'Payroll export, manage pay rates' },
// Files
{ slug: 'files.view', domain: 'files', action: 'view', description: 'View and download files' },
{ slug: 'files.upload', domain: 'files', action: 'upload', description: 'Upload files' },
{ slug: 'files.delete', domain: 'files', action: 'delete', description: 'Delete files' },
// Email
{ slug: 'email.view', domain: 'email', action: 'view', description: 'View email logs' },
{ slug: 'email.send', domain: 'email', action: 'send', description: 'Send mass emails' },
{ slug: 'email.admin', domain: 'email', action: 'admin', description: 'Manage email templates and campaigns' },
// Settings
{ slug: 'settings.view', domain: 'settings', action: 'view', description: 'View store settings' },
{ slug: 'settings.edit', domain: 'settings', action: 'edit', description: 'Edit store settings, locations, tax rates' },
// Users
{ slug: 'users.view', domain: 'users', action: 'view', description: 'View users and roles' },
{ slug: 'users.edit', domain: 'users', action: 'edit', description: 'Create and edit users, assign roles' },
{ slug: 'users.admin', domain: 'users', action: 'admin', description: 'Create and modify roles, manage permissions' },
// Reports
{ slug: 'reports.view', domain: 'reports', action: 'view', description: 'View reports' },
{ slug: 'reports.export', domain: 'reports', action: 'export', description: 'Export report data' },
// System
{ slug: 'system.backup', domain: 'system', action: 'backup', description: 'Create and download backups' },
{ slug: 'system.restore', domain: 'system', action: 'restore', description: 'Restore from backup' },
{ slug: 'system.audit', domain: 'system', action: 'audit', description: 'View audit trail' },
] as const
/** Default system roles with their permission slugs */
export const DEFAULT_ROLES = [
{
slug: 'admin',
name: 'Admin',
description: 'Full access to all features',
permissions: SYSTEM_PERMISSIONS.map((p) => p.slug),
},
{
slug: 'manager',
name: 'Manager',
description: 'Full access except user administration',
permissions: SYSTEM_PERMISSIONS
.filter((p) => p.slug !== 'users.admin')
.map((p) => p.slug),
},
{
slug: 'sales_associate',
name: 'Sales Associate',
description: 'Front counter sales, customer management',
permissions: [
'accounts.view', 'accounts.edit',
'inventory.view',
'pos.view', 'pos.edit',
'rentals.view',
'files.view', 'files.upload',
'reports.view',
],
},
{
slug: 'technician',
name: 'Technician',
description: 'Repair ticket management',
permissions: [
'repairs.view', 'repairs.edit',
'inventory.view',
'accounts.view',
'files.view', 'files.upload',
],
},
{
slug: 'instructor',
name: 'Instructor',
description: 'Lesson management',
permissions: [
'lessons.view', 'lessons.edit',
'accounts.view',
],
},
{
slug: 'viewer',
name: 'Viewer',
description: 'Read-only access to all areas',
permissions: SYSTEM_PERMISSIONS
.filter((p) => p.action === 'view')
.map((p) => p.slug),
},
] as const