ভূমিকা-ভিত্তিক অ্যাক্সেস নিয়ন্ত্রণ। ব্যবহারকারী → ভূমিকা → অনুমতি। স্ট্যান্ডার্ড ভূমিকা: Partner Admin, Mid-Office Supervisor, Accountant, Travel Agent, Auditor। Maker-Checker প্যাটার্ন: একই ব্যবহারকারী থ্রেশহোল্ডের উপরে অপারেশন তৈরি ও অনুমোদন করতে পারে না।
Chapter 2.1 — Users, Roles & RBAC
chapter: 02-user-management/01-users-roles-rbac
version: 1.0.0
status: stable
last_reviewed: 2026-05-26
owners: [platform-engineering, security]
1. Purpose
This chapter defines how identity, role assignment, and permission checks work in travoBooks. It is the security backbone of every other module: any check of the form "can this person do this thing?" resolves here.
2. Why this matters in modern travel accounting
A travel business is a collaboration of distinct functions with incompatible authority profiles:
- A reservations agent must book and ticket but must not be able to issue refunds, post journal entries, or change a customer's credit limit.
- An accounts officer must reconcile and post but must not be able to alter a booking that has been issued.
- A manager must approve refunds above a threshold but should not routinely create them.
- An auditor must see everything and change nothing.
- A junior should never see net cost from the supplier; a manager must.
Without strict role separation, the same person who creates a customer can also write off their receivable balance — a classic embezzlement path. travoBooks encodes segregation of duties as a first-class system property.
3. Industry relevance
Both IATA agent-accreditation audits and SOX-style internal control reviews look for evidence of:
- Documented role definitions.
- Enforcement of those roles in software, not policy.
- An audit trail showing who did what.
- A "maker–checker" pattern for high-impact actions.
travoBooks satisfies all four out of the box.
4. Compliance considerations
- Segregation of duties (SoD) — booking creation, ticket issuance, refund issuance, journal posting, and payment release must each be separable. travoBooks ships SoD-aware role templates.
- Least privilege — default roles grant only what is necessary to perform a defined job.
- Right to revocation — disabling a user revokes access within seconds across all sessions and API tokens.
- Right to audit — every permission check is loggable; security-sensitive denials are always logged.
5. Business logic
5.1 Conceptual model
- A User is a person, identified by a globally-unique email.
- A user has one or more Partner-User memberships (one per partner they belong to).
- Each membership has one or more Roles.
- Each role grants a set of Permissions.
- Permissions may be scoped (e.g. "can refund within their own bookings only").
5.2 Permission grammar
Permissions use a three-part dotted form: resource.action.scope.
| Component | Examples |
|---|---|
resource |
booking, invoice, customer, supplier, journal, payment, user, report, setting |
action |
read, create, update, delete, approve, void, refund, post, export |
scope |
any, own, team, partner, branch:{id} |
Examples:
- booking.create.any — create a booking for any customer of the partner.
- booking.refund.own — refund a booking the actor originally created.
- invoice.post.partner — post any invoice in the partner.
- journal.read.any — read any journal entry (auditor).
5.3 Default role templates
travoBooks ships with the following role templates, applied on partner provisioning. Partners may clone and modify them but the templates themselves are read-only.
| Role | Purpose | Notable permissions |
|---|---|---|
partner_admin |
Full control of the partner | *.*.partner |
accountant |
Run the books | journal.*.partner, invoice.*.partner, payment.*.partner, report.read.partner, booking.read.partner (read-only on ops) |
senior_agent |
Sell + ticket | booking.*.partner, ticket.issue.partner, customer.*.partner, invoice.create.partner |
agent |
Sell, no ticket issuance | booking.create.own, booking.read.team, customer.read.partner, invoice.create.own |
cashier |
Receive payments | payment.create.partner, payment.read.partner, invoice.read.partner |
approver |
Approve high-value actions | booking.approve.partner, refund.approve.partner, payment.approve.partner |
auditor |
Read-only, all books | *.read.partner, audit.read.partner |
api_integration |
Server-to-server | scoped per integration |
viewer |
Dashboards only | report.read.partner |
5.4 Maker–checker
Certain actions require a different user to approve them. Configurable per partner with sensible defaults:
| Action | Threshold | Default approver |
|---|---|---|
| Refund | Any amount > partner.refund_approval_threshold (default 500 USD-eq) |
approver |
| Manual journal entry | Always | accountant ≠ creator |
| Credit note | Any amount > threshold | approver |
| Customer credit limit change | Always | partner_admin |
| Bank reconciliation force-match | Always | accountant ≠ matcher |
| User role assignment | Always | partner_admin |
The maker may not be the checker; this is enforced server-side. See Multi-User Workflows.
5.5 Scopes
Scope qualifies which instances of a resource a permission applies to:
any— all instances within the partner.partner— same asany(alias).own— only rows wherecreated_by_user_id = actor.user_id.team— rows wherecreated_by_user_idis inactor.team_ids.branch:{id}— rows wherebranch_id = {id}.
Scope resolution is computed at permission-check time. A role can grant the same resource.action at multiple scopes; the union applies.
5.6 Time-bound roles
Roles can be assigned with optional valid_from / valid_until. Use cases: temporary cover during leave; contractor with a fixed engagement; emergency support elevation.
6. Inputs → processing → outputs
6.1 Creating a user (input)
user:
email: "[email protected]"
full_name: "Sara Khatun"
phone: "+8801XXXXXXXXX"
locale: "en_BD"
membership:
partner_id: "P-001"
roles: ["senior_agent"]
branch_id: null
valid_from: null
valid_until: null
6.2 Processing
- Validate email format and uniqueness across the platform.
- Insert
usersrow (if new) with statusinvited. - Insert
partner_usersrow linking user to partner. - Insert
partner_user_rolesrows for each role. - Generate a one-time activation token; email it.
- Write audit log.
6.3 Outputs
- A new
user_idandmembership_id. - An activation email with a token-bearing URL (token expires in 72 hours).
- An audit entry visible in the partner's activity feed.
7. Module dependencies
- Reads from:
partners, country/locale reference, role templates. - Writes to:
users,partner_users,partner_user_roles,audit_logs,notification_outbox. - Consumed by: every other module via the permission middleware.
8. Security & permissions
8.1 Authentication (the predicate)
A user must first prove who they are; that is covered in 02-authentication.md. Permission checks presume an authenticated actor.
8.2 Permission check flow
8.3 Sensitive data redaction
Certain fields are redacted in API responses based on permission, not stripped from the DB:
| Field | Redacted unless… |
|---|---|
| Supplier net cost on a booking | actor has booking.read_cost.partner |
| Customer credit balance | actor has customer.read_credit.partner |
| Bank account numbers | actor has payment.read_bank.partner |
| Passenger passport / DOB | actor has passenger.read_pii.partner and the booking is within the actor's allowed scope |
8.4 API tokens
- Personal Access Tokens (PATs): owned by a user, inherit that user's permissions.
- Service tokens: owned by the partner, assigned a dedicated
api_integrationrole. - Both carry scopes (which is a token-level restriction on top of the user's permissions, never an expansion).
- Tokens are hashed at rest (Argon2id); the raw token is shown once at creation.
- Token rotation supported with a 7-day grace period.
8.5 Session security
- Cookie:
HttpOnly,Secure,SameSite=Strict,__Host-prefix. - Session TTL: 8 hours sliding, 24 hours absolute.
- Concurrent session policy per partner:
unlimited|single|count:{n}. - 🔒 Permission cache is keyed by
(user_id, partner_id, role_set_version). Bumpingrole_set_versionon any role change instantly invalidates all caches.
9. Validation rules
| Rule | Error code |
|---|---|
| Email must be valid RFC 5322 | EMAIL_INVALID |
| Email must be unique platform-wide | USER_DUPLICATE |
| At least one role required per membership | ROLES_REQUIRED |
| Role must exist and be enabled for the partner | ROLE_UNKNOWN |
Cannot remove the last partner_admin |
LAST_ADMIN_PROTECTED |
valid_until > valid_from |
VALIDITY_RANGE_INVALID |
| Maker cannot equal checker | MAKER_CHECKER_SAME_ACTOR |
| Cannot self-assign roles you don't already have | PRIVILEGE_ESCALATION_BLOCKED |
10. Error handling
| Scenario | Behaviour |
|---|---|
| Expired activation token | 410 GONE, code ACTIVATION_EXPIRED, offer resend |
| Disabled user attempts to log in | 401, code USER_DISABLED (do not specify which) |
| Permission check fails | 403 + audit log; UI hides affordance |
| Role removed mid-session | Next request invalidates cache and re-evaluates; affected actions return 403 |
| Maker–checker stalled | Pending approvals dashboard surfaces; auto-expire after 7 days |
11. Real-world example
Scenario. Junior agent Asha creates a booking with a USD 1,200 markup discount. Discounts > 10% require approval. The booking is held in pending_approval state. Senior agent Mahin opens the approvals queue, reviews the discount note, approves. The booking advances to confirmed; an audit log entry records both actors. A refund is later issued by accountant Tanvir, requiring approval by the approver role; Mahin approves. The booking, refund, and approval chain are all linked in the audit log.
🔒 Critically, Asha cannot approve her own discount because the approver check enforces actor.user_id != record.created_by. The UI hides the "Approve" button when viewing her own pending booking, and the server enforces the rule even if she calls the API directly.
12. Step-by-step workflow — granting a role
13. Database tables touched
users— global identity.partner_users— membership.partner_user_roles— role assignments.roles— partner-scoped role definitions.role_permissions— role → permission bindings.permissions— the catalog.api_tokens— PATs and service tokens.sessions— active sessions.audit_logs— every change.
14. Future scalability
| Pressure | Response |
|---|---|
| Attribute-based access for complex rules ("only bookings from corporate clients in the EU") | Layer ABAC on top of RBAC: policy expressions evaluated at request time. |
| SSO with enterprise IdPs | SAML + OIDC support; group claims map to roles via a configurable mapping. |
| SCIM-driven provisioning | SCIM 2.0 endpoint; per-partner secrets; conflict rules for the unique-email constraint. |
| Just-in-time elevation ("break-glass") | Time-bounded role with mandatory approver and visible banner during elevation. |
15. Common pitfalls
- ⚠️ Granting
partner_admin"for now" — there is no "for now". Use time-bounded roles instead. - ⚠️ Caching permissions too aggressively. The 5-minute TTL is safe because
role_set_versioninvalidates immediately on change; do not relax this further. - ⚠️ Allowing the UI to drive what's possible. The server is the source of truth; the UI is a hint.
- 🔒 Never expose a "permissions diff" to a user being denied — that aids reconnaissance.
Next: 02-authentication.md — login, MFA, session, and API token mechanics.