The roles
| Role | Can see |
|---|---|
| Employee | Their own leave, claims, benefits, payslips-free profile, policies and room bookings. |
| Approver | The above, plus the leave and claim queues for the people who report to them. |
| Admin | The full admin console: employees, entitlements, reports, benefits, policies, settings. |
| Super admin | Everything an admin can do, plus onboarding, offboarding approval, management notes, entitlement overrides and the audit log. |
| Managing director | Company-wide read access and the audit log, without the administrative controls. |
Where checks happen
There are three layers, and they are not redundant — each catches something the others do not.
- 1Request middleware — rejects unauthenticated requests and blocks admin routes for non-admin roles before a page is even rendered.
- 2Server actions — every mutation re-checks the caller's role. This is the layer that matters, because it is the one an attacker cannot skip by crafting a request directly.
- 3Query scoping — every query is filtered by the caller's company. A valid employee ID belonging to another tenant reads as *not found*, not as *forbidden*, so the isolation does not leak the existence of other tenants' records.
Sessions
Each employee record carries a session version. Changing someone's role, terminating them, or resetting their password increments it, and live sessions are re-validated against it. The practical effect: revoking access takes effect now, not whenever the person's existing token happens to expire.
Audit log
Sensitive actions write an append-only audit entry: who, what, which record, when, and any relevant metadata. That includes NRIC decryption, entitlement overrides, role changes, deletion approvals, data exports and administrative overrides of an approval decision. Super admins and the managing director can read it; nobody can edit it through the application.