M01 Users + M02 Roles & Permissions
9 default roles · ~150 module.action permissions · 3-tier scope (self/department/all). Role-permission grants editable per firm. Active-sessions screen. User → employee mapping for cross-module identity. RBAC enforced at controller + service layer, not just the UI.
The 9 default roles
| Role | Hierarchy | Typical use |
|---|---|---|
| super_admin | 1 (highest) | System administrator · all permissions |
| partner | 10 | Engagement partner · all-scope on most modules |
| manager | 20 | Audit manager · department scope |
| senior_auditor | 30 | Senior · assigned scope on jobs/clients |
| staff_auditor | 50 | Staff · assigned scope |
| accountant | 40 | Finance ops · full M11/M12, view M07 |
| admin_staff | 35 | Admin · all-scope on most ops minus delete |
| read_only | 90 | External auditor · view-only across modules |
| portal (future) | 99 | Reserved for client-portal users |
The permission model
Each permission is structured as module.action + optional scope:
- module — m01, m02, ..., m20 (corresponds to module folder)
- action — view, create, update, delete, approve, sign, etc.
- scope — self / department / assigned / all
Examples: m07.view (assigned), m11.write_off (all), m13.manage_compensation (all).
RBAC matrix screen
URL: /settings/roles-and-permissions. Visual grid:
- Rows = permissions
- Columns = roles
- Cells = scope (none / self / department / assigned / all) — clickable dropdown
- Filter by module
- Save changes per row · audit-logged
Most firms use defaults; customisation is for unusual organisational structures.
User row fields
Case-insensitive match.
Never stored plaintext. Min 12 chars on set.
Displayed in headers, audit logs, sign-offs.
Single role per user. Hierarchy resolved via role.
Drives "department" scope filtering.
Links user to M13 employee record. Powers My Profile, leave portal, expense submission.
Inactive users cannot login but data preserved (audit-trail integrity).
Stamped on each login. Used in active-sessions + admin reports.
Drives 5-attempts → 30-min lockout.
Step-by-step — onboard a new user
Administration → Users → New user
Fill: email · full_name · role · department · employee link (if exists)
Save
Auto-generates 12-char temp password. Emailed via M17 to user's email with reset link.
User receives email · sets new password · logs in
Active session captures their device + IP
Audit log entry:
m01.user.create
Active sessions admin view
URL: /settings/active-sessions. Lists all currently-active sessions across the firm:
- User · IP · login timestamp · last activity · device parsing
- Filter by user / role / IP / activity-window
- Revoke individual or bulk sessions
- Audit log entry on each revocation
Useful for: terminated employee whose sessions need killing immediately, suspicious activity investigation, partner-mandated mass-logout (e.g. before a security audit).
Permission editor
Settings → Roles & Permissions → click any role → grants editor:
- Grouped by module
- Per-permission scope dropdown
- Bulk select all / none
- Save → audit-logged · all currently-logged-in users with that role get session_regenerate_id on next request (privilege change)
RBAC enforcement points
RBAC is enforced at every layer:
- Routes — gate at registration via permission attribute
- Controllers —
Rbac::can()check at top of every action - Services — service-layer guards for sensitive ops (write-off, sign-off, archive)
- Views — UI hides actions user can't perform (defensive)
- API endpoints — same Rbac::can() check
Defence in depth — UI hiding alone is not security; the service-layer check is the real gate.
Don't grant super_admin to multiple users. The role bypasses every RBAC check — single super_admin reduces blast radius if an account is compromised. Most firms have 1 super_admin (system designer / IT) and use partner role for all business users.
If a junior needs partner-level access for a specific task (e.g. running a one-off report), temporarily grant + immediately revoke afterward. Don't share partner login. Audit log captures both grants — partner sees what was elevated and why.