Security:
- Add token-based WebSocket authentication (closes critical security gap)
- In-memory token store with 24h TTL (token_store.py)
- POST /api/auth/token exchanges Basic Auth for WS token
- GET /api/auth/status public endpoint for auth check
- WebSocket validates token query param, rejects with close code 4401
- Frontend LoginPrompt modal for credential entry
- WebSocket manager handles full auth flow with auth_required state
- No-op when AUTH_ENABLED=false (preserves existing behavior)
HTTPS:
- Wire HTTPS toggle in Settings UI (POST /api/system/ssl/toggle)
- Add certificate regeneration button
- Display SSL info (expiration, SANs, SHA256 fingerprint)
Plugins:
- Wire trigger_hook("pm3_command") in PM3 service
- Wire trigger_hook("update_check") in update manager
Build/Infrastructure:
- Enable NetworkManager in pi-gen AP setup stage
- Add HF booster board detection patch for Proxmark3
- Update LED PWM control patch
- Fix BLE adapter, UPS drivers, WiFi manager improvements
- Update HTTPS support stage script
Documentation:
- Update PROJECT_STATUS.md and IMPLEMENTATION_PRIORITIES.md
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
56 lines
1.4 KiB
Python
56 lines
1.4 KiB
Python
"""In-memory token store for WebSocket authentication.
|
|
|
|
Tokens are short-lived (24h) and stored in memory. Since this is a
|
|
single-device appliance with typically one user, persistence is not needed.
|
|
Tokens are cleaned up lazily on create/validate calls.
|
|
"""
|
|
import secrets
|
|
from datetime import datetime, timezone, timedelta
|
|
|
|
_tokens: dict[str, dict] = {}
|
|
TOKEN_TTL_HOURS = 24
|
|
|
|
|
|
def create_token(username: str) -> str:
|
|
"""Create a new auth token for the given username.
|
|
|
|
Args:
|
|
username: The authenticated username
|
|
|
|
Returns:
|
|
URL-safe token string
|
|
"""
|
|
token = secrets.token_urlsafe(32)
|
|
_tokens[token] = {
|
|
"username": username,
|
|
"expires_at": datetime.now(timezone.utc) + timedelta(hours=TOKEN_TTL_HOURS),
|
|
}
|
|
_cleanup_expired()
|
|
return token
|
|
|
|
|
|
def validate_token(token: str) -> str | None:
|
|
"""Validate a token and return the associated username.
|
|
|
|
Args:
|
|
token: The token to validate
|
|
|
|
Returns:
|
|
Username if valid, None if invalid or expired
|
|
"""
|
|
entry = _tokens.get(token)
|
|
if not entry:
|
|
return None
|
|
if datetime.now(timezone.utc) > entry["expires_at"]:
|
|
del _tokens[token]
|
|
return None
|
|
return entry["username"]
|
|
|
|
|
|
def _cleanup_expired() -> None:
|
|
"""Remove expired tokens."""
|
|
now = datetime.now(timezone.utc)
|
|
expired = [t for t, e in _tokens.items() if now > e["expires_at"]]
|
|
for t in expired:
|
|
del _tokens[t]
|