HostAgentics Authentication and Authorization
Last updated: 2026-08-06
This document describes how authentication and authorization work on the HostAgentics platform: the identity provider, the session model, organization roles, tenant isolation, the invite flow, and the role requirements for operations. It is an internal engineering document; customer-facing text must not claim capabilities beyond what is implemented here.
1. Identity provider: Better Auth
Authentication is implemented with Better Auth (`apps/api/src/auth.ts`), using its Drizzle adapter against the platform database. Three plugins are enabled:
Email + password sign-in (with verification) and Google/GitHub OAuth are supported when credentials are configured (`AUTH_GITHUB_ID/SECRET`, `AUTH_GOOGLE_ID/SECRET`). Sessions, accounts, and verification tokens live in the `users`, `sessions`, `accounts`, and `verificationTokens` tables; MFA methods and TOTP secrets are stored as encrypted envelopes in `mfaMethods` — never plaintext.
2. Session model
Better Auth's session model applies: a session belongs to one user, carries an expiry, and is recorded with IP address and user agent for anomaly detection. Session revocation is handled by Better Auth's session lifecycle. `securityEvents` records suspicious activity (login throttling, MFA failures) and the API rate-limits auth endpoints. Users authenticate once per session; every protected request is then authorized server-side per organization (section 4) — authentication alone never authorizes cross-org access.
3. Organizations and roles
Every tenant is an `organization`. A user belongs to an organization through a `membership` with exactly one role:
| Role | Typical permissions (resource: actions) |
| --- | --- |
| `owner` | Runtime read/write/action/delete; billing read/write; member read/invite/remove/update; secrets read/write; relay manage; support read/write |
| `admin` | Runtime read/write/action; billing read/write; member read/invite/remove; secrets write; relay manage; support read/write |
| `developer` | Runtime read/write/action; secrets write; relay manage; support read/write |
| `operator` | Runtime read/action (operational actions, e.g. restart); support read/write; no secret or billing access |
| `billing` | Billing read/write only |
| `viewer` | Read-only access to the org's resources |
Role checks are enforced with an access-control matrix (`createAccessControl` in `apps/api/src/auth.ts`) **and** re-checked in route handlers via `requireRole` in `apps/api/src/authz.ts`. The `owner` role is the only role that can delete runtimes and remove members; only `owner`/`admin` can change billing. `operator` can take operational actions (restart, pause, resume) but cannot modify secrets. These requirements are enforced server-side on every operation; the UI merely hides what the role cannot do.
4. Organization isolation
Isolation is enforced at the query layer, not by client cooperation:
5. Invite flow
Members are added by invitation:
1. An `owner`/`admin` invites an email address with a target role. The system stores an `invitations` row with a **hashed** token and an expiry.
2. The invite link (with the plaintext token, shown once) is sent via transactional email (Resend).
3. On acceptance, the token is verified against the hash, the membership is created with the invited role, and `acceptedAt` is set. Expired invites are rejected; unaccepted invites can be revoked by the inviter.
6. Role requirements per operation (summary)
| Operation | Minimum role |
| --- | --- |
| View org resources, runtimes, logs | viewer |
| Restart/pause/resume a runtime | operator |
| Create/configure a runtime, write secrets | developer |
| Manage Relay tokens | developer |
| Manage invitations (invite/remove members) | admin |
| Change billing, plans, payment method | admin |
| Delete a runtime, remove members | owner |
7. Secrets handling
No plaintext secrets are stored in the identity or session tables. Passwords are hashed; MFA secrets and any stored key material are encrypted envelopes (see `docs/security.md` and `docs/secret-rotation.md`). Session tokens and invite tokens are stored hashed; plaintext values are displayed exactly once at issuance.