Spaces:
Running
Running
Commit
·
ea856a6
1
Parent(s):
795b72e
Security & HF Spaces fixes: Enable CSRF, auth middleware, persistent storage
Browse files- Re-enable CSRF state verification in OAuth callback (auth.py:165-177)
- Re-enable ConditionalAuthMiddleware in app.py
- Add /auth/oauth-callback to excluded paths in auth middleware
- Centralize DB_URI config to use /data persistent storage in HF Spaces
- Update database/__init__.py and init_db.py to use central config
- Add graceful error handling for /data directory creation
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- backend/app.py +1 -2
- backend/database/__init__.py +3 -9
- backend/database/init_db.py +17 -6
- backend/middleware/auth.py +2 -1
- backend/routers/auth.py +10 -6
- utils/config.py +17 -1
backend/app.py
CHANGED
|
@@ -90,8 +90,7 @@ else:
|
|
| 90 |
)
|
| 91 |
|
| 92 |
# Add conditional authentication middleware (after session)
|
| 93 |
-
|
| 94 |
-
# app.add_middleware(ConditionalAuthMiddleware)
|
| 95 |
|
| 96 |
# Add usage tracking middleware (last, to track authenticated requests)
|
| 97 |
app.add_middleware(UsageTrackingMiddleware)
|
|
|
|
| 90 |
)
|
| 91 |
|
| 92 |
# Add conditional authentication middleware (after session)
|
| 93 |
+
app.add_middleware(ConditionalAuthMiddleware)
|
|
|
|
| 94 |
|
| 95 |
# Add usage tracking middleware (last, to track authenticated requests)
|
| 96 |
app.add_middleware(UsageTrackingMiddleware)
|
backend/database/__init__.py
CHANGED
|
@@ -4,20 +4,14 @@ This package provides database access and utilities for agent monitoring.
|
|
| 4 |
"""
|
| 5 |
|
| 6 |
import os
|
| 7 |
-
from dotenv import load_dotenv
|
| 8 |
from sqlalchemy import create_engine
|
| 9 |
from sqlalchemy.ext.declarative import declarative_base
|
| 10 |
from sqlalchemy.orm import sessionmaker, scoped_session
|
| 11 |
|
| 12 |
-
#
|
| 13 |
-
|
| 14 |
|
| 15 |
-
|
| 16 |
-
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
| 17 |
-
|
| 18 |
-
# Database URL - use DB_URI from env, fallback to default
|
| 19 |
-
DEFAULT_DB_PATH = f"sqlite:///{os.path.join(ROOT_DIR, 'datasets/db/agent_monitoring.db')}"
|
| 20 |
-
DATABASE_URL = os.getenv("DB_URI", DEFAULT_DB_PATH)
|
| 21 |
|
| 22 |
# Create engine
|
| 23 |
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
|
|
|
|
| 4 |
"""
|
| 5 |
|
| 6 |
import os
|
|
|
|
| 7 |
from sqlalchemy import create_engine
|
| 8 |
from sqlalchemy.ext.declarative import declarative_base
|
| 9 |
from sqlalchemy.orm import sessionmaker, scoped_session
|
| 10 |
|
| 11 |
+
# Import DB_URI from central config (handles HF Spaces vs local dev)
|
| 12 |
+
from utils.config import DB_URI
|
| 13 |
|
| 14 |
+
DATABASE_URL = DB_URI
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
|
| 16 |
# Create engine
|
| 17 |
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
|
backend/database/init_db.py
CHANGED
|
@@ -37,14 +37,15 @@ logger = logging.getLogger(__name__)
|
|
| 37 |
# Get the absolute path to the project root directory
|
| 38 |
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
| 39 |
|
| 40 |
-
#
|
| 41 |
-
|
| 42 |
-
|
| 43 |
# Extract path from sqlite URI
|
| 44 |
if DB_URI.startswith("sqlite:///"):
|
| 45 |
DB_PATH = DB_URI.replace("sqlite:///", "")
|
| 46 |
else:
|
| 47 |
-
|
|
|
|
| 48 |
|
| 49 |
def confirm_reset():
|
| 50 |
"""Ask for user confirmation before force resetting the database."""
|
|
@@ -56,13 +57,23 @@ def confirm_reset():
|
|
| 56 |
def init_database(reset=False, force=False):
|
| 57 |
"""
|
| 58 |
Initialize the database with the required tables.
|
| 59 |
-
|
| 60 |
Args:
|
| 61 |
reset: If True, drop and recreate the tables
|
| 62 |
force: If True, delete the database file completely
|
| 63 |
"""
|
| 64 |
# Make sure the directory exists
|
| 65 |
-
os.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
|
| 67 |
# Check if database exists
|
| 68 |
db_exists = os.path.exists(DB_PATH) and os.path.getsize(DB_PATH) > 0
|
|
|
|
| 37 |
# Get the absolute path to the project root directory
|
| 38 |
ROOT_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
| 39 |
|
| 40 |
+
# Import DB_URI from central config
|
| 41 |
+
from utils.config import DB_URI
|
| 42 |
+
|
| 43 |
# Extract path from sqlite URI
|
| 44 |
if DB_URI.startswith("sqlite:///"):
|
| 45 |
DB_PATH = DB_URI.replace("sqlite:///", "")
|
| 46 |
else:
|
| 47 |
+
# Fallback for non-sqlite databases
|
| 48 |
+
DB_PATH = os.path.join(ROOT_DIR, 'datasets/db/agent_monitoring.db')
|
| 49 |
|
| 50 |
def confirm_reset():
|
| 51 |
"""Ask for user confirmation before force resetting the database."""
|
|
|
|
| 57 |
def init_database(reset=False, force=False):
|
| 58 |
"""
|
| 59 |
Initialize the database with the required tables.
|
| 60 |
+
|
| 61 |
Args:
|
| 62 |
reset: If True, drop and recreate the tables
|
| 63 |
force: If True, delete the database file completely
|
| 64 |
"""
|
| 65 |
# Make sure the directory exists
|
| 66 |
+
db_dir = os.path.dirname(DB_PATH)
|
| 67 |
+
if db_dir: # Only try to create if there's a directory path
|
| 68 |
+
try:
|
| 69 |
+
os.makedirs(db_dir, exist_ok=True)
|
| 70 |
+
logger.info(f"Database directory ensured at: {db_dir}")
|
| 71 |
+
except OSError as e:
|
| 72 |
+
logger.warning(f"Could not create database directory {db_dir}: {e}")
|
| 73 |
+
# In HF Spaces, /data might not be available until Persistent Storage is enabled
|
| 74 |
+
if "/data" in db_dir:
|
| 75 |
+
logger.warning("HF Spaces Persistent Storage may not be enabled. "
|
| 76 |
+
"Database will be stored in ephemeral storage.")
|
| 77 |
|
| 78 |
# Check if database exists
|
| 79 |
db_exists = os.path.exists(DB_PATH) and os.path.getsize(DB_PATH) > 0
|
backend/middleware/auth.py
CHANGED
|
@@ -30,12 +30,13 @@ class ConditionalAuthMiddleware:
|
|
| 30 |
# Paths that don't require authentication even in HF Spaces
|
| 31 |
self.excluded_paths = excluded_paths or [
|
| 32 |
"/docs",
|
| 33 |
-
"/redoc",
|
| 34 |
"/openapi.json",
|
| 35 |
"/api/observability/health-check",
|
| 36 |
"/api/observability/environment",
|
| 37 |
"/auth/login",
|
| 38 |
"/auth/callback",
|
|
|
|
| 39 |
"/auth/logout",
|
| 40 |
"/auth/login-page",
|
| 41 |
"/auth/status",
|
|
|
|
| 30 |
# Paths that don't require authentication even in HF Spaces
|
| 31 |
self.excluded_paths = excluded_paths or [
|
| 32 |
"/docs",
|
| 33 |
+
"/redoc",
|
| 34 |
"/openapi.json",
|
| 35 |
"/api/observability/health-check",
|
| 36 |
"/api/observability/environment",
|
| 37 |
"/auth/login",
|
| 38 |
"/auth/callback",
|
| 39 |
+
"/auth/oauth-callback", # OAuth callback from HF
|
| 40 |
"/auth/logout",
|
| 41 |
"/auth/login-page",
|
| 42 |
"/auth/status",
|
backend/routers/auth.py
CHANGED
|
@@ -163,13 +163,17 @@ async def handle_oauth_callback(request: Request, code: str, state: str):
|
|
| 163 |
stored_state = None
|
| 164 |
|
| 165 |
if not stored_state:
|
| 166 |
-
logger.
|
| 167 |
-
|
| 168 |
-
|
|
|
|
|
|
|
| 169 |
elif stored_state != state:
|
| 170 |
-
logger.error(f"🚫 OAuth state mismatch -
|
| 171 |
-
|
| 172 |
-
|
|
|
|
|
|
|
| 173 |
else:
|
| 174 |
logger.info("✅ OAuth state verification successful")
|
| 175 |
|
|
|
|
| 163 |
stored_state = None
|
| 164 |
|
| 165 |
if not stored_state:
|
| 166 |
+
logger.error("🚫 No stored OAuth state found - CSRF protection triggered")
|
| 167 |
+
raise HTTPException(
|
| 168 |
+
status_code=400,
|
| 169 |
+
detail="No stored state found. Your session may have expired. Please try logging in again."
|
| 170 |
+
)
|
| 171 |
elif stored_state != state:
|
| 172 |
+
logger.error(f"🚫 OAuth state mismatch - CSRF protection triggered")
|
| 173 |
+
raise HTTPException(
|
| 174 |
+
status_code=400,
|
| 175 |
+
detail="State parameter mismatch. Please try logging in again."
|
| 176 |
+
)
|
| 177 |
else:
|
| 178 |
logger.info("✅ OAuth state verification successful")
|
| 179 |
|
utils/config.py
CHANGED
|
@@ -53,7 +53,23 @@ if LANGFUSE_PUBLIC_KEY and LANGFUSE_SECRET_KEY:
|
|
| 53 |
ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY")
|
| 54 |
|
| 55 |
# Database Configuration
|
| 56 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
|
| 58 |
# Function to validate configuration
|
| 59 |
def validate_config():
|
|
|
|
| 53 |
ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY")
|
| 54 |
|
| 55 |
# Database Configuration
|
| 56 |
+
# For HF Spaces, use /data persistent storage directory
|
| 57 |
+
# For local development, use datasets/db directory
|
| 58 |
+
def _get_default_db_uri():
|
| 59 |
+
"""Get default database URI based on environment."""
|
| 60 |
+
if os.getenv("SPACE_ID"): # HF Spaces
|
| 61 |
+
# Use HF Persistent Storage at /data
|
| 62 |
+
# Note: /data directory is created by HF when Persistent Storage is enabled
|
| 63 |
+
# The directory creation is attempted at database init time, not config load time
|
| 64 |
+
return "sqlite:////data/agent_monitoring.db"
|
| 65 |
+
else:
|
| 66 |
+
# Local development - use datasets/db relative to project root
|
| 67 |
+
project_root = Path(__file__).parent.parent.resolve()
|
| 68 |
+
db_dir = project_root / "datasets" / "db"
|
| 69 |
+
os.makedirs(db_dir, exist_ok=True)
|
| 70 |
+
return f"sqlite:///{db_dir}/agent_monitoring.db"
|
| 71 |
+
|
| 72 |
+
DB_URI = os.getenv("DB_URI", _get_default_db_uri())
|
| 73 |
|
| 74 |
# Function to validate configuration
|
| 75 |
def validate_config():
|