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>
6.9 KiB
HTTP Frontend Authentication Draft
Goal
Add optional passphrase authentication for trx-frontend-http with two roles:
rxpassphrase: read-only accesscontrolpassphrase: 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
controlrole is present.
This is not multi-user IAM; it is a pragmatic local/ham-shack gate.
Config Proposal
Add to trx-client.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 (rxand/orcontrol). rx_passphraseonly: read-only deployment.control_passphraseonly: 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: boolrx_passphrase: Option<String>control_passphrase: Option<String>tx_access_control_enabled: boolsession_ttl: Durationcookie_secure: boolsame_site: SameSite
SessionStorein-memory map:- key: random session id (128-bit+)
- value:
{ role, issued_at, expires_at, last_seen, ip_hash? }
Role enum:
AuthRole::RxAuthRole::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
controlfirst, thenrx - on success: set
HttpOnlycookietrx_http_sid, return{ role: "rx"|"control" } - on failure: 401 generic error
- body:
POST /auth/logout- clears cookie and invalidates server session
GET /auth/session- returns
{ authenticated: true|false, role?: "rx"|"control" }
- returns
Protected existing endpoints:
- Control APIs (
controlrole 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 (
rxorcontrol):/status,/events,/decode,/audio
TX/PTT hard-gate behavior when tx_access_control_enabled=true:
- Do not render TX/PTT controls for unauthenticated or
rxrole. - Reject TX/PTT and mutating control endpoints unless role is
control. - Prefer returning
404for hidden TX/PTT endpoints to avoid capability leakage (or403if 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
401JSON/text - SSE/WS routes: return
401
- API routes: return
- If valid:
- enforce route role (
rxorcontrol) - return
403when authenticated but role is insufficient - continue request
- optionally slide expiry (
last_seen + ttl) with cap.
- enforce route role (
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=trueSecureconfigurable (true for TLS)SameSite=LaxdefaultPath=/- Max-Age = session TTL
Frontend Flow
In assets/web/app.js:
- On startup call
/auth/session. - If unauthenticated, show blocking screen with logo +
Access denied. - Submit to
/auth/login. - On success initialize normal app flow (
connect(), decode stream). - If role is
rx, disable/hide all TX/PTT/mutating controls. - If role is
control, enable full UI. - If protected call returns 401/403, stop streams and return to login panel.
- 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
- Extend client config structs + parser defaults.
- Build auth state (passphrases + session store) in HTTP server startup.
- Add
/auth/login,/auth/logout,/auth/sessionhandlers. - Add middleware and protect selected routes.
- Update frontend JS with login gate and 401 handling.
- Add docs to
README.md+trx-client.toml.example. - 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 (
rxdenied on control routes). - TX visibility policy (
tx_access_control_enabled) endpoint behavior.
Integration tests (Actix test server):
- Unauthed call to
/set_freq-> 401. rxlogin -> cookie set ->/statusaccepted,/set_freq-> 403.controllogin ->/set_freqaccepted.- With
tx_access_control_enabled=true, unauth/rxcannot use/set_ptt. - Expired session -> 401.
/eventsand/audioreject 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.