wu981526092 commited on
Commit
c8243d5
Β·
1 Parent(s): 88e835f

Implement conditional authentication middleware and OAuth routes for Hugging Face Spaces integration. Enhance environment debugging and add environment info endpoint. Update README with OAuth configuration details and improve startup logging.

Browse files
README.md CHANGED
@@ -7,6 +7,11 @@ sdk: docker
7
  pinned: false
8
  license: mit
9
  app_port: 7860
 
 
 
 
 
10
  ---
11
 
12
  # πŸ•ΈοΈ AgentGraph
@@ -21,7 +26,7 @@ The easiest way to get started is using our setup script:
21
 
22
  ```bash
23
  # 1. Clone the repository
24
- git clone <repository-url>
25
  cd AgentGraph
26
 
27
  # 2. Run the setup script
@@ -41,7 +46,7 @@ If you prefer manual control:
41
 
42
  ```bash
43
  # 1. Clone and setup environment
44
- git clone <repository-url>
45
  cd AgentGraph
46
  cp .env.example .env
47
  # Edit .env and add your OpenAI API key
 
7
  pinned: false
8
  license: mit
9
  app_port: 7860
10
+ hf_oauth: true
11
+ hf_oauth_scopes:
12
+ - openid
13
+ - profile
14
+ hf_oauth_expiration_minutes: 480
15
  ---
16
 
17
  # πŸ•ΈοΈ AgentGraph
 
26
 
27
  ```bash
28
  # 1. Clone the repository
29
+ git clone https://huggingface.co/spaces/holistic-ai/AgentGraph
30
  cd AgentGraph
31
 
32
  # 2. Run the setup script
 
46
 
47
  ```bash
48
  # 1. Clone and setup environment
49
+ git clone https://huggingface.co/spaces/holistic-ai/AgentGraph
50
  cd AgentGraph
51
  cp .env.example .env
52
  # Edit .env and add your OpenAI API key
backend/app.py CHANGED
@@ -10,7 +10,10 @@ import sys
10
  from fastapi import FastAPI, Request, status
11
  from fastapi.staticfiles import StaticFiles
12
  from fastapi.middleware.cors import CORSMiddleware
 
13
  from fastapi.responses import RedirectResponse, HTMLResponse
 
 
14
 
15
 
16
  # Add server module to path if not already there
@@ -30,6 +33,7 @@ from backend.routers import (
30
  example_traces,
31
  methods,
32
  observability,
 
33
  )
34
 
35
  # Setup logging
@@ -38,6 +42,16 @@ logger = logging.getLogger("agent_monitoring_server")
38
  # Create FastAPI app
39
  app = FastAPI(title="Agent Monitoring System", version="1.0.0")
40
 
 
 
 
 
 
 
 
 
 
 
41
  # Add CORS middleware
42
  app.add_middleware(
43
  CORSMiddleware,
@@ -51,6 +65,7 @@ app.add_middleware(
51
  app.mount("/data", StaticFiles(directory="datasets"), name="data")
52
 
53
  # Include routers
 
54
  app.include_router(traces.router)
55
  app.include_router(knowledge_graphs.router)
56
  app.include_router(agentgraph.router)
@@ -69,6 +84,9 @@ async def startup_event():
69
  """Start background services on app startup"""
70
  logger.info("βœ… Backend server starting...")
71
 
 
 
 
72
  # πŸ”§ Create necessary directories
73
  ensure_directories()
74
  logger.info("πŸ“ Directory structure created")
@@ -82,6 +100,12 @@ async def startup_event():
82
  logger.error(f"❌ Database initialization failed: {e}")
83
  # Don't fail startup - continue with empty database
84
 
 
 
 
 
 
 
85
  logger.info("πŸš€ Backend API available at: http://0.0.0.0:7860")
86
  # scheduler_service.start() # This line is now commented out
87
 
 
10
  from fastapi import FastAPI, Request, status
11
  from fastapi.staticfiles import StaticFiles
12
  from fastapi.middleware.cors import CORSMiddleware
13
+ from starlette.middleware.sessions import SessionMiddleware
14
  from fastapi.responses import RedirectResponse, HTMLResponse
15
+ from backend.middleware.auth import ConditionalAuthMiddleware
16
+ from utils.environment import should_enable_auth, debug_environment
17
 
18
 
19
  # Add server module to path if not already there
 
33
  example_traces,
34
  methods,
35
  observability,
36
+ auth,
37
  )
38
 
39
  # Setup logging
 
42
  # Create FastAPI app
43
  app = FastAPI(title="Agent Monitoring System", version="1.0.0")
44
 
45
+ # Add session middleware (required for OAuth)
46
+ app.add_middleware(
47
+ SessionMiddleware,
48
+ secret_key=os.getenv("SESSION_SECRET_KEY", "your-secret-key-change-in-production"),
49
+ max_age=86400, # 24 hours
50
+ )
51
+
52
+ # Add conditional authentication middleware
53
+ app.add_middleware(ConditionalAuthMiddleware)
54
+
55
  # Add CORS middleware
56
  app.add_middleware(
57
  CORSMiddleware,
 
65
  app.mount("/data", StaticFiles(directory="datasets"), name="data")
66
 
67
  # Include routers
68
+ app.include_router(auth.router) # Add auth router first
69
  app.include_router(traces.router)
70
  app.include_router(knowledge_graphs.router)
71
  app.include_router(agentgraph.router)
 
84
  """Start background services on app startup"""
85
  logger.info("βœ… Backend server starting...")
86
 
87
+ # 🌍 Debug environment information
88
+ debug_environment()
89
+
90
  # πŸ”§ Create necessary directories
91
  ensure_directories()
92
  logger.info("πŸ“ Directory structure created")
 
100
  logger.error(f"❌ Database initialization failed: {e}")
101
  # Don't fail startup - continue with empty database
102
 
103
+ # πŸ” Log authentication status
104
+ if should_enable_auth():
105
+ logger.info("πŸ” Authentication ENABLED (HF Spaces environment)")
106
+ else:
107
+ logger.info("🏠 Authentication DISABLED (Local development)")
108
+
109
  logger.info("πŸš€ Backend API available at: http://0.0.0.0:7860")
110
  # scheduler_service.start() # This line is now commented out
111
 
backend/middleware/__init__.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ """
2
+ Middleware package for AgentGraph backend.
3
+ """
4
+
5
+ from .auth import ConditionalAuthMiddleware
6
+
7
+ __all__ = ["ConditionalAuthMiddleware"]
backend/middleware/auth.py ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Conditional Authentication Middleware
3
+
4
+ This middleware only enables authentication when running in Hugging Face Spaces.
5
+ Local development bypasses authentication entirely.
6
+ """
7
+
8
+ import os
9
+ import logging
10
+ from typing import Optional, Dict, Any
11
+ from fastapi import Request, Response
12
+ from starlette.middleware.base import BaseHTTPMiddleware
13
+ from starlette.responses import RedirectResponse, JSONResponse
14
+ from utils.environment import should_enable_auth, get_oauth_config, is_huggingface_space
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class ConditionalAuthMiddleware(BaseHTTPMiddleware):
20
+ """
21
+ Middleware that conditionally enables authentication based on deployment environment.
22
+
23
+ - In HF Spaces: Full OAuth authentication required
24
+ - In local development: Authentication bypassed
25
+ """
26
+
27
+ def __init__(self, app, excluded_paths: Optional[list] = None):
28
+ super().__init__(app)
29
+
30
+ # Paths that don't require authentication even in HF Spaces
31
+ self.excluded_paths = excluded_paths or [
32
+ "/",
33
+ "/docs",
34
+ "/redoc",
35
+ "/openapi.json",
36
+ "/api/observability/health-check",
37
+ "/api/observability/environment",
38
+ "/auth/login",
39
+ "/auth/callback",
40
+ "/auth/logout",
41
+ "/assets/",
42
+ "/static/",
43
+ "/agentgraph", # Allow React app to load
44
+ ]
45
+
46
+ # Check if auth should be enabled
47
+ self.auth_enabled = should_enable_auth()
48
+ self.oauth_config = get_oauth_config() if self.auth_enabled else None
49
+
50
+ # Log auth status
51
+ if self.auth_enabled:
52
+ logger.info("πŸ” Authentication middleware ENABLED (HF Spaces environment)")
53
+ if not self.oauth_config:
54
+ logger.warning("⚠️ OAuth configuration not found in HF Spaces environment")
55
+ else:
56
+ logger.info("🏠 Authentication middleware DISABLED (Local development)")
57
+
58
+ async def dispatch(self, request: Request, call_next):
59
+ """
60
+ Process request through conditional authentication.
61
+ """
62
+ # If auth is disabled (local dev), bypass all authentication
63
+ if not self.auth_enabled:
64
+ return await call_next(request)
65
+
66
+ # If auth is enabled but OAuth not properly configured, log warning and continue
67
+ if not self.oauth_config:
68
+ logger.warning("OAuth not configured properly, bypassing auth")
69
+ return await call_next(request)
70
+
71
+ # Check if path is excluded from authentication
72
+ if self._is_excluded_path(request.url.path):
73
+ return await call_next(request)
74
+
75
+ # Check user authentication
76
+ user = await self._get_current_user(request)
77
+ if not user:
78
+ # For API calls, return JSON error
79
+ if request.url.path.startswith("/api/"):
80
+ return JSONResponse(
81
+ status_code=401,
82
+ content={"error": "Authentication required", "login_url": "/auth/login"}
83
+ )
84
+
85
+ # For web requests, redirect to login
86
+ return RedirectResponse(url="/auth/login", status_code=302)
87
+
88
+ # Add user info to request state
89
+ request.state.user = user
90
+ return await call_next(request)
91
+
92
+ def _is_excluded_path(self, path: str) -> bool:
93
+ """Check if the request path should bypass authentication."""
94
+ return any(
95
+ path.startswith(excluded_path)
96
+ for excluded_path in self.excluded_paths
97
+ )
98
+
99
+ async def _get_current_user(self, request: Request) -> Optional[Dict[str, Any]]:
100
+ """
101
+ Get current user from session or token.
102
+
103
+ In a full implementation, this would:
104
+ 1. Check session cookies
105
+ 2. Validate JWT tokens
106
+ 3. Call HF API to verify user info
107
+
108
+ For now, we'll implement a basic session check.
109
+ """
110
+ # Check if user info is in session
111
+ user = request.session.get("user") if hasattr(request, "session") else None
112
+
113
+ # Check Authorization header as fallback
114
+ if not user:
115
+ auth_header = request.headers.get("Authorization")
116
+ if auth_header and auth_header.startswith("Bearer "):
117
+ # In a full implementation, validate this token with HF API
118
+ # For now, we'll assume it's valid if present
119
+ pass
120
+
121
+ return user
122
+
123
+ def get_auth_status(self) -> Dict[str, Any]:
124
+ """Get current authentication configuration status."""
125
+ return {
126
+ "auth_enabled": self.auth_enabled,
127
+ "environment": "huggingface_spaces" if is_huggingface_space() else "local_development",
128
+ "oauth_configured": bool(self.oauth_config),
129
+ "excluded_paths": self.excluded_paths,
130
+ }
backend/routers/auth.py ADDED
@@ -0,0 +1,208 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Authentication routes for Hugging Face OAuth integration.
3
+
4
+ These routes are only active when running in HF Spaces environment.
5
+ """
6
+
7
+ import os
8
+ import logging
9
+ import secrets
10
+ from typing import Optional
11
+ from fastapi import APIRouter, Request, Response, HTTPException
12
+ from fastapi.responses import RedirectResponse, HTMLResponse, JSONResponse
13
+ from utils.environment import should_enable_auth, get_oauth_config, is_huggingface_space
14
+ import requests
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+ router = APIRouter(prefix="/auth", tags=["authentication"])
19
+
20
+
21
+ @router.get("/status")
22
+ async def auth_status():
23
+ """Get authentication status and configuration."""
24
+ config = get_oauth_config()
25
+ return {
26
+ "auth_enabled": should_enable_auth(),
27
+ "environment": "huggingface_spaces" if is_huggingface_space() else "local_development",
28
+ "oauth_available": bool(config),
29
+ "login_required": should_enable_auth() and bool(config),
30
+ }
31
+
32
+
33
+ @router.get("/login")
34
+ async def login(request: Request):
35
+ """
36
+ Initiate OAuth login flow.
37
+ Only available in HF Spaces environment.
38
+ """
39
+ if not should_enable_auth():
40
+ return JSONResponse(
41
+ content={"message": "Authentication not required in local development"},
42
+ status_code=200
43
+ )
44
+
45
+ oauth_config = get_oauth_config()
46
+ if not oauth_config:
47
+ raise HTTPException(
48
+ status_code=500,
49
+ detail="OAuth not configured in this environment"
50
+ )
51
+
52
+ # Generate state for CSRF protection
53
+ state = secrets.token_urlsafe(32)
54
+ request.session["oauth_state"] = state
55
+
56
+ # Get the current host for redirect URI
57
+ base_url = str(request.base_url).rstrip('/')
58
+ redirect_uri = f"{base_url}/auth/callback"
59
+
60
+ # Build authorization URL
61
+ auth_url = (
62
+ f"{oauth_config['provider_url']}/oauth/authorize"
63
+ f"?client_id={oauth_config['client_id']}"
64
+ f"&redirect_uri={redirect_uri}"
65
+ f"&response_type=code"
66
+ f"&scope={oauth_config['scopes']}"
67
+ f"&state={state}"
68
+ )
69
+
70
+ return RedirectResponse(url=auth_url, status_code=302)
71
+
72
+
73
+ @router.get("/callback")
74
+ async def oauth_callback(request: Request, code: str, state: str):
75
+ """
76
+ Handle OAuth callback from Hugging Face.
77
+ """
78
+ if not should_enable_auth():
79
+ return RedirectResponse(url="/", status_code=302)
80
+
81
+ oauth_config = get_oauth_config()
82
+ if not oauth_config:
83
+ raise HTTPException(status_code=500, detail="OAuth not configured")
84
+
85
+ # Verify state parameter (CSRF protection)
86
+ stored_state = request.session.get("oauth_state")
87
+ if not stored_state or stored_state != state:
88
+ raise HTTPException(status_code=400, detail="Invalid state parameter")
89
+
90
+ # Exchange code for tokens
91
+ base_url = str(request.base_url).rstrip('/')
92
+ redirect_uri = f"{base_url}/auth/callback"
93
+
94
+ try:
95
+ token_response = requests.post(
96
+ f"{oauth_config['provider_url']}/oauth/token",
97
+ data={
98
+ "grant_type": "authorization_code",
99
+ "code": code,
100
+ "redirect_uri": redirect_uri,
101
+ "client_id": oauth_config['client_id'],
102
+ "client_secret": oauth_config['client_secret'],
103
+ },
104
+ timeout=10
105
+ )
106
+ token_response.raise_for_status()
107
+ tokens = token_response.json()
108
+
109
+ except requests.RequestException as e:
110
+ logger.error(f"Token exchange failed: {e}")
111
+ raise HTTPException(status_code=400, detail="Token exchange failed")
112
+
113
+ access_token = tokens.get("access_token")
114
+ if not access_token:
115
+ raise HTTPException(status_code=400, detail="No access token received")
116
+
117
+ # Get user information
118
+ try:
119
+ user_response = requests.get(
120
+ f"{oauth_config['provider_url']}/api/whoami-v2",
121
+ headers={"Authorization": f"Bearer {access_token}"},
122
+ timeout=10
123
+ )
124
+ user_response.raise_for_status()
125
+ user_info = user_response.json()
126
+
127
+ except requests.RequestException as e:
128
+ logger.error(f"User info fetch failed: {e}")
129
+ raise HTTPException(status_code=400, detail="Failed to fetch user information")
130
+
131
+ # Store user in session
132
+ request.session["user"] = {
133
+ "id": user_info.get("id"),
134
+ "name": user_info.get("name"),
135
+ "username": user_info.get("login"), # HF username
136
+ "email": user_info.get("email"),
137
+ "avatar_url": user_info.get("avatarUrl"),
138
+ "access_token": access_token, # Store for future API calls if needed
139
+ }
140
+
141
+ # Clean up state
142
+ request.session.pop("oauth_state", None)
143
+
144
+ logger.info(f"User logged in: {user_info.get('name')} ({user_info.get('login')})")
145
+
146
+ # Redirect to main application
147
+ return RedirectResponse(url="/", status_code=302)
148
+
149
+
150
+ @router.get("/logout")
151
+ async def logout(request: Request):
152
+ """Log out the current user."""
153
+ if hasattr(request, "session"):
154
+ request.session.clear()
155
+
156
+ return RedirectResponse(url="/", status_code=302)
157
+
158
+
159
+ @router.get("/user")
160
+ async def get_current_user(request: Request):
161
+ """Get current user information."""
162
+ if not should_enable_auth():
163
+ return {"message": "Authentication disabled in local development"}
164
+
165
+ user = getattr(request.state, "user", None) or request.session.get("user")
166
+ if not user:
167
+ raise HTTPException(status_code=401, detail="Not authenticated")
168
+
169
+ # Return user info without sensitive data
170
+ return {
171
+ "id": user.get("id"),
172
+ "name": user.get("name"),
173
+ "username": user.get("username"),
174
+ "email": user.get("email"),
175
+ "avatar_url": user.get("avatar_url"),
176
+ }
177
+
178
+
179
+ @router.get("/login-page")
180
+ async def login_page(request: Request):
181
+ """
182
+ Serve a simple login page for environments where auth is required.
183
+ """
184
+ if not should_enable_auth():
185
+ return RedirectResponse(url="/", status_code=302)
186
+
187
+ html_content = """
188
+ <!DOCTYPE html>
189
+ <html>
190
+ <head>
191
+ <title>AgentGraph - Login Required</title>
192
+ <style>
193
+ body { font-family: Arial, sans-serif; text-align: center; margin-top: 100px; }
194
+ .login-container { max-width: 400px; margin: 0 auto; padding: 20px; border: 1px solid #ddd; border-radius: 8px; }
195
+ .login-btn { background: #ff6b35; color: white; padding: 12px 24px; text-decoration: none; border-radius: 6px; font-weight: bold; }
196
+ </style>
197
+ </head>
198
+ <body>
199
+ <div class="login-container">
200
+ <h1>πŸ•ΈοΈ AgentGraph</h1>
201
+ <p>Please log in with your Hugging Face account to access AgentGraph.</p>
202
+ <a href="/auth/login" class="login-btn">Login with Hugging Face</a>
203
+ </div>
204
+ </body>
205
+ </html>
206
+ """
207
+
208
+ return HTMLResponse(content=html_content)
backend/routers/observability.py CHANGED
@@ -18,6 +18,7 @@ from datetime import datetime
18
  from typing import Dict, List, Optional, cast
19
 
20
  import psutil
 
21
  import requests
22
  from fastapi import APIRouter, Depends, HTTPException
23
  from fastapi.responses import JSONResponse
@@ -871,6 +872,17 @@ async def clean_up(session: Session = Depends(get_db)): # noqa: B008
871
  logger.error(f"Error cleaning up resources: {str(e)}")
872
  raise HTTPException(status_code=500, detail=f"Error cleaning up resources: {str(e)}") from e
873
 
 
 
 
 
 
 
 
 
 
 
 
874
  @router.get("/health-check")
875
  async def health_check():
876
  """Comprehensive health check for the system."""
 
18
  from typing import Dict, List, Optional, cast
19
 
20
  import psutil
21
+ from utils.environment import get_environment_info, debug_environment
22
  import requests
23
  from fastapi import APIRouter, Depends, HTTPException
24
  from fastapi.responses import JSONResponse
 
872
  logger.error(f"Error cleaning up resources: {str(e)}")
873
  raise HTTPException(status_code=500, detail=f"Error cleaning up resources: {str(e)}") from e
874
 
875
+ @router.get("/environment")
876
+ async def get_environment():
877
+ """Get environment information and authentication status."""
878
+ env_info = get_environment_info()
879
+
880
+ return {
881
+ "environment": env_info,
882
+ "timestamp": datetime.now().isoformat()
883
+ }
884
+
885
+
886
  @router.get("/health-check")
887
  async def health_check():
888
  """Comprehensive health check for the system."""
main.py CHANGED
@@ -10,6 +10,7 @@ from utils.fix_litellm_stop_param import * # This applies all the patches
10
 
11
  # Import configuration and debug utilities
12
  from utils.config import validate_config, debug_config
 
13
 
14
  # Continue with regular imports
15
  import argparse
@@ -224,6 +225,7 @@ def main():
224
  # Debug configuration on startup (but only if not just showing help)
225
  if len(sys.argv) > 1:
226
  debug_config()
 
227
  if not validate_config():
228
  logger.error("❌ Configuration validation failed. Please check your environment variables.")
229
  logger.error("πŸ’‘ Tip: Copy .env.example to .env and fill in your API keys")
 
10
 
11
  # Import configuration and debug utilities
12
  from utils.config import validate_config, debug_config
13
+ from utils.environment import debug_environment as debug_env_info
14
 
15
  # Continue with regular imports
16
  import argparse
 
225
  # Debug configuration on startup (but only if not just showing help)
226
  if len(sys.argv) > 1:
227
  debug_config()
228
+ debug_env_info() # Also show environment info
229
  if not validate_config():
230
  logger.error("❌ Configuration validation failed. Please check your environment variables.")
231
  logger.error("πŸ’‘ Tip: Copy .env.example to .env and fill in your API keys")
utils/environment.py ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Environment Detection Utilities
3
+
4
+ This module provides utilities to detect the deployment environment
5
+ and configure authentication accordingly.
6
+ """
7
+
8
+ import os
9
+ from typing import Dict, Any, Optional
10
+
11
+
12
+ def is_huggingface_space() -> bool:
13
+ """
14
+ Detect if the application is running in Hugging Face Spaces.
15
+
16
+ Returns:
17
+ bool: True if running in HF Spaces, False otherwise
18
+ """
19
+ # HF Spaces sets specific environment variables
20
+ hf_indicators = [
21
+ "SPACE_ID", # HF Space identifier
22
+ "SPACE_AUTHOR_NAME", # Space author
23
+ "SPACE_REPO_NAME", # Space repository name
24
+ "OAUTH_CLIENT_ID", # OAuth client ID (when oauth enabled)
25
+ ]
26
+
27
+ return any(os.getenv(indicator) for indicator in hf_indicators)
28
+
29
+
30
+ def is_local_development() -> bool:
31
+ """
32
+ Detect if the application is running in local development mode.
33
+
34
+ Returns:
35
+ bool: True if running locally, False otherwise
36
+ """
37
+ return not is_huggingface_space()
38
+
39
+
40
+ def get_environment_info() -> Dict[str, Any]:
41
+ """
42
+ Get comprehensive environment information.
43
+
44
+ Returns:
45
+ Dict containing environment details
46
+ """
47
+ env_info = {
48
+ "is_hf_space": is_huggingface_space(),
49
+ "is_local_dev": is_local_development(),
50
+ "space_id": os.getenv("SPACE_ID"),
51
+ "space_author": os.getenv("SPACE_AUTHOR_NAME"),
52
+ "space_repo": os.getenv("SPACE_REPO_NAME"),
53
+ "oauth_enabled": bool(os.getenv("OAUTH_CLIENT_ID")),
54
+ "host": os.getenv("HOST", "localhost"),
55
+ "port": os.getenv("PORT", "7860"),
56
+ }
57
+
58
+ return env_info
59
+
60
+
61
+ def should_enable_auth() -> bool:
62
+ """
63
+ Determine if authentication should be enabled based on environment.
64
+
65
+ Returns:
66
+ bool: True if auth should be enabled, False otherwise
67
+ """
68
+ return is_huggingface_space()
69
+
70
+
71
+ def get_oauth_config() -> Optional[Dict[str, str]]:
72
+ """
73
+ Get OAuth configuration if available.
74
+
75
+ Returns:
76
+ Dict with OAuth config or None if not available
77
+ """
78
+ if not should_enable_auth():
79
+ return None
80
+
81
+ oauth_config = {
82
+ "client_id": os.getenv("OAUTH_CLIENT_ID"),
83
+ "client_secret": os.getenv("OAUTH_CLIENT_SECRET"),
84
+ "scopes": os.getenv("OAUTH_SCOPES", "openid profile"),
85
+ "provider_url": os.getenv("OPENID_PROVIDER_URL", "https://huggingface.co"),
86
+ }
87
+
88
+ # Only return config if client_id is available
89
+ if oauth_config["client_id"]:
90
+ return oauth_config
91
+
92
+ return None
93
+
94
+
95
+ def debug_environment() -> None:
96
+ """
97
+ Print debug information about the current environment.
98
+ """
99
+ env_info = get_environment_info()
100
+ oauth_config = get_oauth_config()
101
+
102
+ print("🌍 Environment Debug Information:")
103
+ print("=" * 50)
104
+ print(f"πŸ—οΈ Environment Type: {'HF Spaces' if env_info['is_hf_space'] else 'Local Development'}")
105
+ print(f"πŸ” Authentication: {'Enabled' if should_enable_auth() else 'Disabled'}")
106
+
107
+ if env_info['is_hf_space']:
108
+ print(f"πŸ†” Space ID: {env_info['space_id']}")
109
+ print(f"πŸ‘€ Author: {env_info['space_author']}")
110
+ print(f"πŸ“¦ Repo: {env_info['space_repo']}")
111
+
112
+ if oauth_config:
113
+ print(f"πŸ”‘ OAuth Client ID: {oauth_config['client_id'][:8]}..." if oauth_config['client_id'] else "Not set")
114
+ print(f"πŸ”’ OAuth Scopes: {oauth_config['scopes']}")
115
+ else:
116
+ print("❌ OAuth not configured")
117
+ else:
118
+ print("🏠 Running in local development mode")
119
+ print("πŸ’‘ Authentication will be automatically enabled when deployed to HF Spaces")
120
+
121
+ print("=" * 50)