Initial commit: Portal Auth Admin Dashboard

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-02-18 08:18:50 +02:00
commit 7caa62a428
20 changed files with 1347 additions and 0 deletions

View File

@@ -0,0 +1,200 @@
# Auth LXC Shared Portal Auth Database
## Purpose
The **Auth LXC** is a dedicated container that runs PostgreSQL and hosts the **shared auth database** (`portal_auth`). It allows multiple portal applications (e.g. FreePBX Portal, future billing or other portals) to use the **same user accounts** without duplicating users or maintaining separate credentials per portal.
- **One set of users:** Create an account once; it works for every portal that points to this DB.
- **Central auth data:** Users, sessions, auth logs, and API tokens live here.
- **Portal-specific data stays elsewhere:** Each portal keeps its own database (e.g. extensions, companies, assignments) on its own host or on this LXC; only auth is shared.
---
## Host Details
| Item | Value |
|------|--------|
| **Host / LXC** | Auth (DB) LXC |
| **IP address** | `10.110.60.210` |
| **SSH access** | `ssh root@10.110.60.210` |
| **OS** | Debian GNU/Linux 13 (trixie) |
| **PostgreSQL** | 17 (Debian package) |
| **Listen** | All interfaces, port `5432` |
---
## What This LXC Hosts
### Database: `portal_auth`
This is the **only database** used for shared authentication. It contains four tables:
| Table | Purpose |
|-------|--------|
| `users` | User accounts (username, password hash, role, active flag) |
| `sessions` | Active login sessions (session_id, username, expires_at) |
| `auth_logs` | Auth events (login, logout, failed login, account_locked, deactivated) |
| `api_tokens` | API tokens for programmatic access (token hash, username, expiry, active) |
No other application data (extensions, companies, trunks, activity logs, etc.) is stored here; that remains in each portals own database.
---
## Database Structure (`portal_auth`)
### 1. `users`
| Column | Type | Constraints | Description |
|--------|------|-------------|-------------|
| `id` | SERIAL | PRIMARY KEY | Auto-incrementing user ID |
| `username` | VARCHAR(255) | UNIQUE, NOT NULL | Login username |
| `password_hash` | TEXT | NOT NULL | Bcrypt or legacy salt:hash |
| `role` | VARCHAR(50) | NOT NULL, DEFAULT 'support' | `admin` or `support` |
| `created_at` | TIMESTAMP | NOT NULL | Account creation (UTC) |
| `created_by` | VARCHAR(255) | NULL | Creator username |
| `is_active` | BOOLEAN | DEFAULT TRUE | Whether the account can log in |
### 2. `sessions`
| Column | Type | Constraints | Description |
|--------|------|-------------|-------------|
| `session_id` | VARCHAR(255) | PRIMARY KEY | Session identifier (cookie value) |
| `username` | VARCHAR(255) | NOT NULL, FK → users(username) | Owner of the session |
| `created_at` | TIMESTAMP | NOT NULL | Session start (UTC) |
| `expires_at` | TIMESTAMP | NOT NULL | Session end (UTC) |
Sessions are validated on each request; expired rows can be purged by the app (e.g. on startup).
### 3. `auth_logs`
| Column | Type | Constraints | Description |
|--------|------|-------------|-------------|
| `id` | SERIAL | PRIMARY KEY | Log entry ID |
| `username` | VARCHAR(255) | NOT NULL | User involved |
| `event_type` | VARCHAR(50) | NOT NULL | `login`, `logout`, `login_failed`, `deactivated`, `account_locked` |
| `timestamp` | TIMESTAMP | NOT NULL | Event time (UTC) |
| `ip_address` | VARCHAR(45) | NULL | Client IP (IPv4/IPv6) |
| `user_agent` | TEXT | NULL | Browser/client string |
| `details` | TEXT | NULL | Extra info |
**Indexes:** `idx_auth_logs_username`, `idx_auth_logs_timestamp`, `idx_auth_logs_event_type`
### 4. `api_tokens`
| Column | Type | Constraints | Description |
|--------|------|-------------|-------------|
| `id` | SERIAL | PRIMARY KEY | Token ID |
| `token_hash` | VARCHAR(255) | UNIQUE, NOT NULL | SHA-256 hash of the token |
| `token_prefix` | VARCHAR(20) | NOT NULL | First 20 chars (for display only) |
| `username` | VARCHAR(255) | NOT NULL, FK → users(username) | Token owner |
| `description` | TEXT | NULL | Optional label |
| `created_at` | TIMESTAMP | NOT NULL | Creation time (UTC) |
| `created_by` | VARCHAR(255) | NULL | Creator username |
| `last_used_at` | TIMESTAMP | NULL | Last validation time |
| `expires_at` | TIMESTAMP | NULL | Optional expiry |
| `is_active` | BOOLEAN | DEFAULT TRUE | Whether the token is valid |
**Indexes:** `idx_api_tokens_token_hash`, `idx_api_tokens_username`, `idx_api_tokens_is_active`
### Relationships
```
users (username)
├── sessions.username → users(username)
└── api_tokens.username → users(username) ON DELETE CASCADE
```
`auth_logs` references users only by `username` (no FK); it is an append-only log.
---
## Access and Security
- **DB user:** `portal_user` (password set on the server; same as used in portal `.env` for `DB_AUTH_PASSWORD`).
- **Allowed clients (pg_hba.conf):**
- `10.110.60.0/24` portal LXCs on the same subnet
- `192.168.200.0/24` gateway/subnet used by the production portal host to reach this LXC
- **Authentication:** `scram-sha-256`.
- **Timezone:** Connections use UTC.
Adding another portal: ensure its host IP (or subnet) is allowed in `/etc/postgresql/17/main/pg_hba.conf` for database `portal_auth` and user `portal_user`, then reload PostgreSQL.
---
## How Portals Use This LXC
Each portal (e.g. FreePBX Portal) is configured with two DB targets:
1. **Auth DB (this LXC):** `DB_AUTH_HOST=10.110.60.210`, `DB_AUTH_NAME=portal_auth`, etc.
Used for: login, sessions, auth logs, user management, API tokens.
2. **Portal DB (local or elsewhere):** `DB_HOST`, `DB_NAME=freepbx_portal`, etc.
Used for: extensions, companies, assignments, activity logs, and all other app data.
In the portal `.env`:
```ini
# Shared auth (this LXC)
DB_AUTH_HOST=10.110.60.210
DB_AUTH_PORT=5432
DB_AUTH_NAME=portal_auth
DB_AUTH_USER=portal_user
DB_AUTH_PASSWORD=<same password as on Auth LXC>
# Portals own data (e.g. same host or another DB server)
DB_HOST=localhost
DB_NAME=freepbx_portal
DB_USER=portal_user
DB_PASSWORD=<password>
```
If `DB_AUTH_HOST` is not set, the portal runs in single-DB mode and uses only its own database for everything (no use of this LXC).
---
## Verification
- **Connections from a portal:** On the Auth LXC, as `postgres`:
```sql
SELECT count(*), client_addr
FROM pg_stat_activity
WHERE datname = 'portal_auth'
GROUP BY client_addr;
```
You should see connections from the portal host(s).
- **Recent logins:** After logging in via a portal:
```sql
SELECT username, event_type, timestamp, ip_address
FROM auth_logs
ORDER BY timestamp DESC
LIMIT 10;
```
- **Active sessions:**
```sql
SELECT session_id, username, created_at, expires_at
FROM sessions;
```
---
## Maintenance
- **Backups:** Backup the `portal_auth` database (e.g. `pg_dump -U postgres -d portal_auth`) regularly; it holds all shared identities and sessions.
- **Log retention:** The portal app can purge old `auth_logs` rows (e.g. via `AUTH_LOG_RETENTION_DAYS`); expired `sessions` are removed on app startup or by scheduled jobs.
- **PostgreSQL:** Managed with systemd (`postgresql.service`); config under `/etc/postgresql/17/main/` (`postgresql.conf`, `pg_hba.conf`).
---
## Summary
| What | Where |
|------|--------|
| **LXC** | 10.110.60.210 (Auth / DB LXC) |
| **Use** | Shared user accounts and auth for multiple portals |
| **Database** | `portal_auth` |
| **Tables** | `users`, `sessions`, `auth_logs`, `api_tokens` |
| **Portal config** | Set `DB_AUTH_HOST=10.110.60.210` (and related `DB_AUTH_*`) in each portals `.env` |
For full schema details of auth and portal tables, see [DATABASE_SCHEMA.md](DATABASE_SCHEMA.md).