feat: Sprint 19 — password auth, security headers, login page
Auth system (off by default, zero friction for localhost): - New api/auth.py module: password hashing (SHA-256 + STATE_DIR salt), signed HMAC session cookies (24h TTL), auth middleware - Enable via HERMES_WEBUI_PASSWORD env var or Settings panel - Minimal dark-themed login page at /login (self-contained HTML) - POST /api/auth/login, /api/auth/logout, GET /api/auth/status - Settings panel: "Access Password" field + "Sign Out" button - password_hash added to settings.json (null = auth disabled) Security hardening: - Security headers on all responses: X-Content-Type-Options: nosniff, X-Frame-Options: DENY, Referrer-Policy: same-origin - POST body size limit: 20MB cap in read_body() to prevent DoS Closes #23. 9 new tests. Total: 304 passed, 0 regressions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -595,6 +595,7 @@ _SETTINGS_DEFAULTS = {
|
||||
'default_model': DEFAULT_MODEL,
|
||||
'default_workspace': str(DEFAULT_WORKSPACE),
|
||||
'send_key': 'enter', # 'enter' or 'ctrl+enter'
|
||||
'password_hash': None, # SHA-256 hash; None = auth disabled
|
||||
}
|
||||
|
||||
def load_settings() -> dict:
|
||||
@@ -616,7 +617,13 @@ _SETTINGS_ENUM_VALUES = {
|
||||
|
||||
def save_settings(settings: dict) -> dict:
|
||||
"""Save settings to disk. Returns the merged settings. Ignores unknown keys."""
|
||||
import hashlib as _hl
|
||||
current = load_settings()
|
||||
# Handle _set_password: hash and store as password_hash
|
||||
raw_pw = settings.pop('_set_password', None)
|
||||
if raw_pw and isinstance(raw_pw, str) and raw_pw.strip():
|
||||
salt = str(STATE_DIR).encode()
|
||||
current['password_hash'] = _hl.sha256(salt + raw_pw.strip().encode()).hexdigest()
|
||||
for k, v in settings.items():
|
||||
if k in _SETTINGS_ALLOWED_KEYS:
|
||||
# Validate enum-constrained keys
|
||||
|
||||
Reference in New Issue
Block a user