# Roles & access ## Authentication HTTP **Basic auth**, scoped per tenant. Every API call (except the two public endpoints) carries: - an `Authorization: Basic base64(email:password)` header, and - an `X-Tenant` header naming the agency (or the agency is taken from the subdomain). The password is verified against the user row **inside the named tenant**, so credentials from one agency never authenticate at another. Passwords are stored as bcrypt hashes. > Basic auth replays a reusable credential on every request and the SPA holds it > client-side. That is acceptable for this reference build behind TLS; a public > deployment should move to short-lived tokens. See > [SECURITY.md](../../SECURITY.md). ## Roles | Role | Intended capability | | ---- | ------------------- | | **agent** | Search, book, manage and cancel bookings; view the dashboard. | | **admin** | Everything an agent can do, plus agency administration. | Both roles exist on the user record and the API exposes an `admin`/`agent` distinction. **Note:** admin-only functionality is not yet built, so today the two roles are functionally identical — the `RequireAdmin` gate exists but is wired to no route. When admin features are added they must be gated explicitly (see [GOVERNANCE.md](../../GOVERNANCE.md)). ## Tenant status A tenant is `active` or `suspended`. A suspended tenant's users cannot sign in, and the tenant is hidden from the public agency picker. ## Demo accounts Seeded by `002_seed.sql`. Password for all: **`demo1234`**. | Agency | Email | Role | | ------ | ----- | ---- | | Skyline Travel | `admin@skyline.test` | admin | | Skyline Travel | `agent@skyline.test` | agent | | Globetrotter Tours | `admin@globetrotter.test` | admin | | Globetrotter Tours | `agent@globetrotter.test` | agent | Sign in to each agency and search `JFK → LHR`: the same route is priced differently per tenant — the isolation made visible. ## Public (unauthenticated) endpoints Only two, by necessity: - `GET /api/health` — liveness. - `GET /api/tenants` — the agency list for the sign-in picker (returns slug and name only; active tenants only). Everything else requires a valid, tenant-scoped credential.