Spaces:
Running
Running
Fix deployment: Use OAuth session tokens properly
Browse filesKey changes:
- Update MockAuth to accept username parameter
- get_auth_from_header now looks up OAuth sessions to extract access_token
- Session tokens are properly resolved to HF OAuth access tokens
- Added detailed logging throughout deployment process
- Better error handling with user-friendly messages for 401/403 errors
- Added HfApi.whoami() call to verify token and get username
- Improved error messages for authentication and permission issues
This ensures the deploy endpoint uses the proper OAuth token from the
authenticated session rather than trying to use a session UUID directly.
- backend_api.py +67 -17
backend_api.py
CHANGED
|
@@ -104,25 +104,18 @@ class CodeGenerationResponse(BaseModel):
|
|
| 104 |
# Mock authentication for development
|
| 105 |
# In production, integrate with HuggingFace OAuth
|
| 106 |
class MockAuth:
|
| 107 |
-
def __init__(self, token: Optional[str] = None):
|
| 108 |
self.token = token
|
| 109 |
-
|
| 110 |
-
if token and token.startswith("dev_token_"):
|
| 111 |
-
# Extract username from dev token format: dev_token_<username>_<timestamp>
|
| 112 |
-
parts = token.split("_")
|
| 113 |
-
self.username = parts[2] if len(parts) > 2 else "user"
|
| 114 |
-
else:
|
| 115 |
-
self.username = "user" if token else None
|
| 116 |
|
| 117 |
def is_authenticated(self):
|
| 118 |
-
# Accept any token (for dev mode)
|
| 119 |
return bool(self.token)
|
| 120 |
|
| 121 |
|
| 122 |
def get_auth_from_header(authorization: Optional[str] = None):
|
| 123 |
-
"""Extract authentication from header"""
|
| 124 |
if not authorization:
|
| 125 |
-
return MockAuth(None)
|
| 126 |
|
| 127 |
# Handle "Bearer " prefix
|
| 128 |
if authorization.startswith("Bearer "):
|
|
@@ -130,7 +123,21 @@ def get_auth_from_header(authorization: Optional[str] = None):
|
|
| 130 |
else:
|
| 131 |
token = authorization
|
| 132 |
|
| 133 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
|
| 135 |
|
| 136 |
@app.get("/")
|
|
@@ -473,24 +480,43 @@ async def deploy(
|
|
| 473 |
"dev_mode": True
|
| 474 |
}
|
| 475 |
|
| 476 |
-
# Production mode with real token
|
| 477 |
try:
|
| 478 |
from huggingface_hub import HfApi
|
| 479 |
import tempfile
|
| 480 |
import uuid
|
| 481 |
|
| 482 |
-
# Get user token
|
| 483 |
user_token = auth.token if auth.token else os.getenv("HF_TOKEN")
|
| 484 |
|
| 485 |
if not user_token:
|
| 486 |
-
raise HTTPException(status_code=401, detail="No HuggingFace token available")
|
|
|
|
|
|
|
| 487 |
|
| 488 |
# Create API client
|
| 489 |
api = HfApi(token=user_token)
|
| 490 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 491 |
# Generate space name if not provided
|
| 492 |
space_name = request.space_name or f"anycoder-{uuid.uuid4().hex[:8]}"
|
| 493 |
-
repo_id = f"{
|
|
|
|
|
|
|
| 494 |
|
| 495 |
# Map language to SDK
|
| 496 |
language_to_sdk = {
|
|
@@ -565,8 +591,32 @@ async def deploy(
|
|
| 565 |
finally:
|
| 566 |
os.unlink(temp_path)
|
| 567 |
|
|
|
|
|
|
|
|
|
|
| 568 |
except Exception as e:
|
| 569 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 570 |
|
| 571 |
|
| 572 |
@app.websocket("/ws/generate")
|
|
|
|
| 104 |
# Mock authentication for development
|
| 105 |
# In production, integrate with HuggingFace OAuth
|
| 106 |
class MockAuth:
|
| 107 |
+
def __init__(self, token: Optional[str] = None, username: Optional[str] = None):
|
| 108 |
self.token = token
|
| 109 |
+
self.username = username
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 110 |
|
| 111 |
def is_authenticated(self):
|
|
|
|
| 112 |
return bool(self.token)
|
| 113 |
|
| 114 |
|
| 115 |
def get_auth_from_header(authorization: Optional[str] = None):
|
| 116 |
+
"""Extract authentication from header or session token"""
|
| 117 |
if not authorization:
|
| 118 |
+
return MockAuth(None, None)
|
| 119 |
|
| 120 |
# Handle "Bearer " prefix
|
| 121 |
if authorization.startswith("Bearer "):
|
|
|
|
| 123 |
else:
|
| 124 |
token = authorization
|
| 125 |
|
| 126 |
+
# Check if this is a session token (UUID format)
|
| 127 |
+
if token and "-" in token and len(token) > 20:
|
| 128 |
+
# Look up the session to get user info
|
| 129 |
+
if token in user_sessions:
|
| 130 |
+
session = user_sessions[token]
|
| 131 |
+
return MockAuth(session["access_token"], session["username"])
|
| 132 |
+
|
| 133 |
+
# Dev token format: dev_token_<username>_<timestamp>
|
| 134 |
+
if token and token.startswith("dev_token_"):
|
| 135 |
+
parts = token.split("_")
|
| 136 |
+
username = parts[2] if len(parts) > 2 else "user"
|
| 137 |
+
return MockAuth(token, username)
|
| 138 |
+
|
| 139 |
+
# Regular token (OAuth access token passed directly)
|
| 140 |
+
return MockAuth(token, None)
|
| 141 |
|
| 142 |
|
| 143 |
@app.get("/")
|
|
|
|
| 480 |
"dev_mode": True
|
| 481 |
}
|
| 482 |
|
| 483 |
+
# Production mode with real OAuth token
|
| 484 |
try:
|
| 485 |
from huggingface_hub import HfApi
|
| 486 |
import tempfile
|
| 487 |
import uuid
|
| 488 |
|
| 489 |
+
# Get user token - should be the access_token from OAuth session
|
| 490 |
user_token = auth.token if auth.token else os.getenv("HF_TOKEN")
|
| 491 |
|
| 492 |
if not user_token:
|
| 493 |
+
raise HTTPException(status_code=401, detail="No HuggingFace token available. Please sign in first.")
|
| 494 |
+
|
| 495 |
+
print(f"[Deploy] Attempting deployment with token (first 10 chars): {user_token[:10]}...")
|
| 496 |
|
| 497 |
# Create API client
|
| 498 |
api = HfApi(token=user_token)
|
| 499 |
|
| 500 |
+
# Get the actual username from HuggingFace API
|
| 501 |
+
try:
|
| 502 |
+
user_info = api.whoami()
|
| 503 |
+
print(f"[Deploy] User info from HF API: {user_info}")
|
| 504 |
+
username = user_info.get("name") or user_info.get("preferred_username") or auth.username or "user"
|
| 505 |
+
except Exception as e:
|
| 506 |
+
print(f"[Deploy] Failed to get user info from HF API: {e}")
|
| 507 |
+
# Fallback to auth username if available
|
| 508 |
+
username = auth.username
|
| 509 |
+
if not username:
|
| 510 |
+
raise HTTPException(
|
| 511 |
+
status_code=401,
|
| 512 |
+
detail="Failed to verify HuggingFace account. Please sign in again."
|
| 513 |
+
)
|
| 514 |
+
|
| 515 |
# Generate space name if not provided
|
| 516 |
space_name = request.space_name or f"anycoder-{uuid.uuid4().hex[:8]}"
|
| 517 |
+
repo_id = f"{username}/{space_name}"
|
| 518 |
+
|
| 519 |
+
print(f"[Deploy] Creating/updating space: {repo_id}")
|
| 520 |
|
| 521 |
# Map language to SDK
|
| 522 |
language_to_sdk = {
|
|
|
|
| 591 |
finally:
|
| 592 |
os.unlink(temp_path)
|
| 593 |
|
| 594 |
+
except HTTPException:
|
| 595 |
+
# Re-raise HTTP exceptions as-is
|
| 596 |
+
raise
|
| 597 |
except Exception as e:
|
| 598 |
+
# Log the full error for debugging
|
| 599 |
+
import traceback
|
| 600 |
+
error_details = traceback.format_exc()
|
| 601 |
+
print(f"[Deploy] Deployment error: {error_details}")
|
| 602 |
+
|
| 603 |
+
# Provide user-friendly error message
|
| 604 |
+
error_msg = str(e)
|
| 605 |
+
if "401" in error_msg or "Unauthorized" in error_msg:
|
| 606 |
+
raise HTTPException(
|
| 607 |
+
status_code=401,
|
| 608 |
+
detail="Authentication failed. Please sign in again with HuggingFace."
|
| 609 |
+
)
|
| 610 |
+
elif "403" in error_msg or "Forbidden" in error_msg:
|
| 611 |
+
raise HTTPException(
|
| 612 |
+
status_code=403,
|
| 613 |
+
detail="Permission denied. Your HuggingFace token may not have the required permissions (manage-repos scope)."
|
| 614 |
+
)
|
| 615 |
+
else:
|
| 616 |
+
raise HTTPException(
|
| 617 |
+
status_code=500,
|
| 618 |
+
detail=f"Deployment failed: {error_msg}"
|
| 619 |
+
)
|
| 620 |
|
| 621 |
|
| 622 |
@app.websocket("/ws/generate")
|