feat: optional auth + backup/restore reliability fixes
Some checks failed
Lint & Test / test (push) Failing after 29s
Some checks failed
Lint & Test / test (push) Failing after 29s
Auth is now optional: when `auth.api_keys` is empty, all endpoints are open (no login screen, no Bearer tokens). Health endpoint reports `auth_required` so the frontend knows which mode to use. Backup/restore fixes: - Auto-backup uses atomic writes (was `write_text`, risked corruption) - Startup backup skipped if recent backup exists (<5 min cooldown), preventing rapid restarts from rotating out good backups - Restore rejects all-empty backups to prevent accidental data wipes - Store saves frozen after restore to prevent stale in-memory data from overwriting freshly-restored files before restart completes - Missing stores during restore logged as warnings - STORE_MAP completeness verified at startup against StorageConfig
This commit is contained in:
@@ -15,11 +15,19 @@ logger = get_logger(__name__)
|
||||
security = HTTPBearer(auto_error=False)
|
||||
|
||||
|
||||
def is_auth_enabled() -> bool:
|
||||
"""Return True when at least one API key is configured."""
|
||||
return bool(get_config().auth.api_keys)
|
||||
|
||||
|
||||
def verify_api_key(
|
||||
credentials: Annotated[HTTPAuthorizationCredentials | None, Security(security)]
|
||||
) -> str:
|
||||
"""Verify API key from Authorization header.
|
||||
|
||||
When no API keys are configured, authentication is disabled and all
|
||||
requests are allowed through as "anonymous".
|
||||
|
||||
Args:
|
||||
credentials: HTTP authorization credentials
|
||||
|
||||
@@ -31,6 +39,10 @@ def verify_api_key(
|
||||
"""
|
||||
config = get_config()
|
||||
|
||||
# No keys configured → auth disabled, allow all requests
|
||||
if not config.auth.api_keys:
|
||||
return "anonymous"
|
||||
|
||||
# Check if credentials are provided
|
||||
if not credentials:
|
||||
logger.warning("Request missing Authorization header")
|
||||
@@ -43,14 +55,6 @@ def verify_api_key(
|
||||
# Extract token
|
||||
token = credentials.credentials
|
||||
|
||||
# Verify against configured API keys
|
||||
if not config.auth.api_keys:
|
||||
logger.error("No API keys configured - server misconfiguration")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Server authentication not configured properly",
|
||||
)
|
||||
|
||||
# Find matching key and return its label using constant-time comparison
|
||||
authenticated_as = None
|
||||
for label, api_key in config.auth.api_keys.items():
|
||||
@@ -80,10 +84,14 @@ AuthRequired = Annotated[str, Depends(verify_api_key)]
|
||||
def verify_ws_token(token: str) -> bool:
|
||||
"""Check a WebSocket query-param token against configured API keys.
|
||||
|
||||
Use this for WebSocket endpoints where FastAPI's Depends() isn't available.
|
||||
When no API keys are configured, authentication is disabled and all
|
||||
WebSocket connections are allowed.
|
||||
"""
|
||||
config = get_config()
|
||||
if token and config.auth.api_keys:
|
||||
# No keys configured → auth disabled, allow all connections
|
||||
if not config.auth.api_keys:
|
||||
return True
|
||||
if token:
|
||||
for _label, api_key in config.auth.api_keys.items():
|
||||
if secrets.compare_digest(token, api_key):
|
||||
return True
|
||||
|
||||
Reference in New Issue
Block a user