Files
Ken 5ba367af2f fix: code review fixes — auth gap, path traversal, dead code, health-check timeout
- backend/app.py: add Depends(_require_auth) to /api/copilotkit endpoint (critical
  security fix — endpoint was fully unprotected; now requires valid session cookie)
- backend/app.py: move 'from ag_ui.core import RunStartedEvent' from inline function
  body to top-level imports (style consistency)
- backend/auth.py: remove dead AUTH_PASS_HASH module-level constant (verify_password
  already reads from os.environ at call time; the cached binding was unused dead code)
- backend/artifacts.py: add path traversal guards to list_artifacts, get_artifact,
  and save_artifact — resolve paths and verify they stay within ARTIFACTS_DIR
- entrypoint.sh: health-check loop now sets READY flag and exits 1 if amplifierd
  fails to start within 30s (previously fell through silently, causing 502s)
- tests/test_app.py: add TestCopilotKitAuth — verifies unauthenticated requests to
  /api/copilotkit return 401; includes note explaining why streaming is not tested here
- tests/test_structure.sh: update to reflect that Dockerfile and entrypoint.sh now
  exist (created in Tasks 7+8); convert absent-checks to presence-checks

Co-authored-by: Amplifier <amplifier@anthropic.com>
2026-05-26 18:52:12 +00:00

53 lines
1.6 KiB
Python

"""Auth module — bcrypt password verification and session token management."""
import os
import secrets
import bcrypt
# AUTH_USER is read at import time (used by app.py to compare against the submitted email).
# AUTH_PASS_HASH is intentionally NOT cached here — verify_password() reads it fresh from
# os.environ at call time so runtime env changes (e.g. secret rotation) are respected.
AUTH_USER: str = os.environ.get("AUTH_USER", "admin@localhost")
# In-memory session store: token -> email
_sessions: dict[str, str] = {}
def verify_password(password: str) -> bool:
"""Return True if password matches the bcrypt hash in AUTH_PASS_HASH env var.
Reads AUTH_PASS_HASH from env at call time so runtime env changes are respected.
Returns False if no hash is configured, if password is empty, or on any error.
"""
if not password:
return False
hash_str = os.environ.get("AUTH_PASS_HASH", "")
if not hash_str:
return False
try:
return bcrypt.checkpw(password.encode(), hash_str.encode())
except (ValueError, TypeError):
return False
def create_session_token(email: str) -> str:
"""Generate a session token, store it mapped to email, and return the token."""
token = secrets.token_hex(32)
_sessions[token] = email
return token
def validate_session_token(token: str) -> str | None:
"""Return the email for a valid token, or None if token is empty/unknown."""
if not token:
return None
return _sessions.get(token)
def invalidate_session_token(token: str) -> None:
"""Remove a session token from the store."""
_sessions.pop(token, None)