[feat](trx-frontend-http): implement HTTP authentication (phases 1-3)
Add optional passphrase-based authentication with two roles (rx/control), session management, auth middleware, and protected routes. Phase 1: Config model with HttpAuthConfig struct, CookieSameSite enum, validation logic for enabled auth requiring at least one passphrase. Phase 2: Auth module with: - AuthRole enum (Rx, Control) - SessionRecord and SessionStore for in-memory session management - AuthConfig at runtime - /auth/login, /auth/logout, /auth/session endpoints - Constant-time passphrase comparison for timing attack mitigation Phase 3: Integration with: - AuthMiddleware for route protection with public/read/control classification - Server-side AuthState setup with cleanup task for expired sessions - Auth endpoints registered in api.rs configure() Sessions use 128-bit random IDs (hex-encoded), HttpOnly cookies, configurable SameSite attribute. Auth is disabled by default to preserve current behavior. All unit and integration tests passing. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
This commit is contained in:
@@ -0,0 +1,190 @@
|
||||
# HTTP Frontend Authentication Draft
|
||||
|
||||
## Goal
|
||||
Add optional passphrase authentication for `trx-frontend-http` with two roles:
|
||||
- `rx` passphrase: read-only access
|
||||
- `control` passphrase: read + control (RX+TX)
|
||||
|
||||
API/control routes stay locked until a user logs in from the web UI.
|
||||
|
||||
This design keeps current behavior when auth is disabled.
|
||||
|
||||
## Scope
|
||||
- Protect HTTP API endpoints used by the web UI.
|
||||
- Protect SSE (`/events`, `/decode`) and audio WebSocket (`/audio`).
|
||||
- Keep static assets and login page accessible so user can authenticate.
|
||||
- Do not change rigctl/http_json auth behavior in this draft.
|
||||
|
||||
## Security Model
|
||||
- Two optional passphrases configured locally (`rx`, `control`).
|
||||
- On successful login, server issues short-lived session cookie.
|
||||
- Session required for all protected routes, with role attached.
|
||||
- Brute-force mitigation via simple per-IP rate limiting.
|
||||
- TX access can be globally hidden/blocked unless `control` role is present.
|
||||
|
||||
This is not multi-user IAM; it is a pragmatic local/ham-shack gate.
|
||||
|
||||
## Config Proposal
|
||||
Add to `trx-client.toml`:
|
||||
|
||||
```toml
|
||||
[frontends.http.auth]
|
||||
enabled = false
|
||||
# Plaintext passphrases (as requested)
|
||||
rx_passphrase = "rx-only-passphrase"
|
||||
control_passphrase = "full-control-passphrase"
|
||||
|
||||
# If true, TX/PTT controls/endpoints are never available without control auth.
|
||||
tx_access_control_enabled = true
|
||||
|
||||
# Session lifetime in minutes
|
||||
session_ttl_min = 480
|
||||
|
||||
# Cookie security
|
||||
cookie_secure = false # true if served via HTTPS
|
||||
cookie_same_site = "Lax" # Strict|Lax|None
|
||||
```
|
||||
|
||||
Validation rules:
|
||||
- If `enabled=false`, all auth fields ignored.
|
||||
- If `enabled=true`, require at least one passphrase (`rx` and/or `control`).
|
||||
- `rx_passphrase` only: read-only deployment.
|
||||
- `control_passphrase` only: control-capable deployment.
|
||||
- both set: mixed deployment with role split.
|
||||
|
||||
Behavior by mode:
|
||||
- `enabled=false` (default): no authentication, current behavior unchanged.
|
||||
- `enabled=true`: authentication enforced per role/route rules in this document.
|
||||
|
||||
## Runtime Structures
|
||||
Add in `src/trx-client/trx-frontend/src/lib.rs` (or HTTP crate-local state):
|
||||
- `HttpAuthConfig`:
|
||||
- `enabled: bool`
|
||||
- `rx_passphrase: Option<String>`
|
||||
- `control_passphrase: Option<String>`
|
||||
- `tx_access_control_enabled: bool`
|
||||
- `session_ttl: Duration`
|
||||
- `cookie_secure: bool`
|
||||
- `same_site: SameSite`
|
||||
- `SessionStore` in-memory map:
|
||||
- key: random session id (128-bit+)
|
||||
- value: `{ role, issued_at, expires_at, last_seen, ip_hash? }`
|
||||
|
||||
Role enum:
|
||||
- `AuthRole::Rx`
|
||||
- `AuthRole::Control`
|
||||
|
||||
Periodic cleanup task (e.g., every 5 min) removes expired sessions.
|
||||
|
||||
## Route Design
|
||||
New endpoints:
|
||||
- `POST /auth/login`
|
||||
- body: `{ "passphrase": "..." }`
|
||||
- server checks passphrase against `control` first, then `rx`
|
||||
- on success: set `HttpOnly` cookie `trx_http_sid`, return `{ role: "rx"|"control" }`
|
||||
- on failure: 401 generic error
|
||||
- `POST /auth/logout`
|
||||
- clears cookie and invalidates server session
|
||||
- `GET /auth/session`
|
||||
- returns `{ authenticated: true|false, role?: "rx"|"control" }`
|
||||
|
||||
Protected existing endpoints:
|
||||
- Control APIs (`control` role required): `/set_freq`, `/set_mode`, `/set_ptt`, `/toggle_power`, `/toggle_vfo`, `/lock`, `/unlock`, `/set_tx_limit`, `/toggle_*_decode`, `/clear_*_decode`, CW tuning endpoints, etc.
|
||||
- Read APIs (`rx` or `control`): `/status`, `/events`, `/decode`, `/audio`
|
||||
|
||||
TX/PTT hard-gate behavior when `tx_access_control_enabled=true`:
|
||||
- Do not render TX/PTT controls for unauthenticated or `rx` role.
|
||||
- Reject TX/PTT and mutating control endpoints unless role is `control`.
|
||||
- Prefer returning `404` for hidden TX/PTT endpoints to avoid capability leakage
|
||||
(or `403` if explicit error semantics are preferred).
|
||||
|
||||
Public endpoints:
|
||||
- `/` (HTML shell)
|
||||
- static assets (`/style.css`, `/app.js`, plugin js, logo, favicon)
|
||||
- `/auth/*`
|
||||
|
||||
## Middleware Behavior
|
||||
Implement Actix middleware/wrap fn in `trx-frontend-http`:
|
||||
- Resolve session from cookie.
|
||||
- Validate in store and expiry.
|
||||
- If missing/invalid:
|
||||
- API routes: return `401` JSON/text
|
||||
- SSE/WS routes: return `401`
|
||||
- If valid:
|
||||
- enforce route role (`rx` or `control`)
|
||||
- return `403` when authenticated but role is insufficient
|
||||
- continue request
|
||||
- optionally slide expiry (`last_seen + ttl`) with cap.
|
||||
|
||||
Keep middleware route-aware by checking request path against allowlist.
|
||||
|
||||
## Passphrase Handling
|
||||
- Use exact passphrase comparison against config values (no hash layer in this draft).
|
||||
- Still use constant-time string comparison helper to reduce timing leakage.
|
||||
- Keep passphrases out of logs and API responses.
|
||||
|
||||
## Cookie Settings
|
||||
Session cookie:
|
||||
- `HttpOnly=true`
|
||||
- `Secure` configurable (true for TLS)
|
||||
- `SameSite=Lax` default
|
||||
- `Path=/`
|
||||
- Max-Age = session TTL
|
||||
|
||||
## Frontend Flow
|
||||
In `assets/web/app.js`:
|
||||
1. On startup call `/auth/session`.
|
||||
2. If unauthenticated, show blocking screen with logo + `Access denied`.
|
||||
3. Submit to `/auth/login`.
|
||||
4. On success initialize normal app flow (`connect()`, decode stream).
|
||||
5. If role is `rx`, disable/hide all TX/PTT/mutating controls.
|
||||
6. If role is `control`, enable full UI.
|
||||
7. If protected call returns 401/403, stop streams and return to login panel.
|
||||
8. Add logout button in About tab or header.
|
||||
|
||||
UI minimal requirement:
|
||||
- Default unauthenticated view: logo + `Access denied` + passphrase field + login button.
|
||||
- Generic error message on failure.
|
||||
- No passphrase persistence in localStorage.
|
||||
|
||||
## Implementation Steps
|
||||
1. Extend client config structs + parser defaults.
|
||||
2. Build auth state (passphrases + session store) in HTTP server startup.
|
||||
3. Add `/auth/login`, `/auth/logout`, `/auth/session` handlers.
|
||||
4. Add middleware and protect selected routes.
|
||||
5. Update frontend JS with login gate and 401 handling.
|
||||
6. Add docs to `README.md` + `trx-client.toml.example`.
|
||||
7. Add role matrix tests and frontend role UI handling.
|
||||
|
||||
## Test Plan
|
||||
Unit tests:
|
||||
- Config validation combinations.
|
||||
- Login success/failure.
|
||||
- Session expiry.
|
||||
- Middleware path allowlist/protection.
|
||||
- Role enforcement (`rx` denied on control routes).
|
||||
- TX visibility policy (`tx_access_control_enabled`) endpoint behavior.
|
||||
|
||||
Integration tests (Actix test server):
|
||||
- Unauthed call to `/set_freq` -> 401.
|
||||
- `rx` login -> cookie set -> `/status` accepted, `/set_freq` -> 403.
|
||||
- `control` login -> `/set_freq` accepted.
|
||||
- With `tx_access_control_enabled=true`, unauth/`rx` cannot use `/set_ptt`.
|
||||
- Expired session -> 401.
|
||||
- `/events` and `/audio` reject unauthenticated clients.
|
||||
|
||||
Manual checks:
|
||||
- Browser login works.
|
||||
- WSJT-X/hamlib unaffected (non-http frontends).
|
||||
- Auth disabled mode behaves exactly as before.
|
||||
|
||||
## Operational Notes
|
||||
- This is in-memory session state. Restart invalidates sessions.
|
||||
- For reverse proxy deployments, use TLS and set `cookie_secure=true`.
|
||||
- If remote exposure is possible, use strong passphrase and firewall.
|
||||
|
||||
## Future Extensions
|
||||
- Optional API bearer token for automation scripts.
|
||||
- Optional migration to hashed passphrases if threat model increases.
|
||||
- Persistent sessions with signed tokens/JWT (if needed).
|
||||
- Optional TOTP second factor for internet-exposed deployments.
|
||||
Reference in New Issue
Block a user