Roles, RLS, and an audit trail you can actually defend.
Operra runs on row-level security policies tied to a separate user_roles table, enforced at the database layer. Roles can't be escalated from the client. Sessions time out. Every action is logged in a hash-chained, tamper-evident trail. This is the security posture your IT auditor wants to see.
No client-side admin flags. No 'localStorage.role = admin' games.
The story
A disgruntled former receptionist still had a saved browser session at 11 PM and tried to issue a void on a folio she no longer had access to. The void was rejected at the database — not the UI — because RLS no longer matched her active role assignment.
Her session was force-expired the moment the front-desk manager deactivated her account. The attempt was logged in the Audit Center with a full request fingerprint. The next morning the owner had a one-screen report of exactly what happened.
No data was changed. No data was even read. The system did its job, quietly.
What it gives you
Roles live in their own table — never on the profile. A compromised profile can't grant itself admin.
Every read and write is gated by a security-definer function. The database refuses queries that don't match the caller's role.
Cashier, supervisor, manager, owner — each role has explicit grants per action. No 'super user' shortcut.
Stale sessions expire automatically. A revoked role takes effect on the very next request — not the next browser refresh.
Every action is hashed into a chain. Backdating, deletion or modification breaks the chain — and is visible.
Filter the audit log by user, action, folio, room, time. Export as branded PDF for your auditor or insurer.
By the numbers
Privilege-escalation surface
Audit-log retention
RLS-protected tables
Session timeout default
Defence in depth
UI hides what you shouldn't see. The API rejects what you shouldn't call. The database refuses what you shouldn't read. The audit log records what you tried to do anyway. Three layers, all working together — because no single layer is enough.
Compared to the alternative
Capability
Roles in their own table
Typical PMS
Operra
Capability
Row-level security enforced
Typical PMS
Operra
Capability
Security-definer functions
Typical PMS
Operra
Capability
Hash-chained audit log
Typical PMS
Operra
Capability
Idle session timeout
Typical PMS
Operra
Capability
Per-action permission grants
Typical PMS
Operra
Capability
Per-property data isolation
Typical PMS
Operra
The hard questions
“The hardest security is the kind that's quiet. You don't notice it — until it stops something.”