Spaces:
Running
on
CPU Upgrade
Running
on
CPU Upgrade
Commit
·
2ec4a9a
1
Parent(s):
b4ebdaf
new attempt
Browse files- app.py +62 -30
- index.html +6 -36
app.py
CHANGED
|
@@ -160,47 +160,84 @@ async def get_user_info(access_token: str) -> dict:
|
|
| 160 |
@app.get("/", response_class=HTMLResponse)
|
| 161 |
async def home(request: Request):
|
| 162 |
"""Home page - client-side auth with popup OAuth"""
|
| 163 |
-
# Dynamically detect origin from request
|
| 164 |
-
origin = get_origin_from_request(request)
|
| 165 |
-
redirect_uri = f"{origin}/oauth/callback"
|
| 166 |
-
|
| 167 |
# Return template - authentication will be handled client-side
|
| 168 |
return templates.TemplateResponse("index.html", {
|
| 169 |
"request": request,
|
| 170 |
-
"oauth_client_id": OAUTH_CLIENT_ID
|
| 171 |
-
"redirect_uri": redirect_uri
|
| 172 |
})
|
| 173 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 174 |
@app.get("/oauth/callback", response_class=HTMLResponse)
|
| 175 |
-
async def oauth_callback(request: Request, code: str, state: Optional[str] = None):
|
| 176 |
"""Handle OAuth callback - returns HTML that posts message to opener window"""
|
| 177 |
origin = get_origin_from_request(request)
|
| 178 |
redirect_uri = f"{origin}/oauth/callback"
|
| 179 |
|
| 180 |
-
|
| 181 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 182 |
error_html = f"""
|
| 183 |
<!DOCTYPE html>
|
| 184 |
<html>
|
| 185 |
<body>
|
| 186 |
<script>
|
| 187 |
(function() {{
|
| 188 |
-
console.log('OAuth callback
|
| 189 |
const target = window.opener || window.parent || window;
|
| 190 |
if (target) {{
|
| 191 |
target.postMessage({{
|
| 192 |
type: 'HF_OAUTH_ERROR',
|
| 193 |
-
payload: {{ message: '
|
| 194 |
-
}}, '
|
| 195 |
}}
|
| 196 |
-
setTimeout(function() {{ window.close(); }},
|
| 197 |
}})();
|
| 198 |
</script>
|
| 199 |
</body>
|
| 200 |
</html>
|
| 201 |
"""
|
| 202 |
-
|
|
|
|
|
|
|
| 203 |
|
|
|
|
| 204 |
try:
|
| 205 |
token_data = await exchange_code_for_token(code, redirect_uri)
|
| 206 |
access_token = token_data.get("access_token")
|
|
@@ -218,31 +255,25 @@ async def oauth_callback(request: Request, code: str, state: Optional[str] = Non
|
|
| 218 |
<body>
|
| 219 |
<script>
|
| 220 |
(function() {{
|
| 221 |
-
console.log('OAuth callback - sending success message');
|
| 222 |
const target = window.opener || window.parent || window;
|
| 223 |
if (target) {{
|
| 224 |
-
console.log('Posting message to target window');
|
| 225 |
target.postMessage({{
|
| 226 |
type: 'HF_OAUTH_SUCCESS',
|
| 227 |
payload: {{
|
| 228 |
token: {json.dumps(access_token)},
|
| 229 |
-
|
| 230 |
-
is_pro: {json.dumps(user_info["is_pro"])},
|
| 231 |
-
fullname: {json.dumps(user_info["fullname"])},
|
| 232 |
-
avatar: {json.dumps(user_info.get("avatar"))}
|
| 233 |
}}
|
| 234 |
-
}}, '
|
| 235 |
}}
|
| 236 |
-
setTimeout(function() {{
|
| 237 |
-
console.log('Closing popup window');
|
| 238 |
-
window.close();
|
| 239 |
-
}}, 300);
|
| 240 |
}})();
|
| 241 |
</script>
|
| 242 |
</body>
|
| 243 |
</html>
|
| 244 |
"""
|
| 245 |
-
|
|
|
|
|
|
|
| 246 |
|
| 247 |
except Exception as e:
|
| 248 |
print(f"OAuth callback error: {e}")
|
|
@@ -252,21 +283,22 @@ async def oauth_callback(request: Request, code: str, state: Optional[str] = Non
|
|
| 252 |
<body>
|
| 253 |
<script>
|
| 254 |
(function() {{
|
| 255 |
-
console.log('OAuth callback - error:', {json.dumps(str(e))});
|
| 256 |
const target = window.opener || window.parent || window;
|
| 257 |
if (target) {{
|
| 258 |
target.postMessage({{
|
| 259 |
type: 'HF_OAUTH_ERROR',
|
| 260 |
payload: {{ message: {json.dumps(str(e))} }}
|
| 261 |
-
}}, '
|
| 262 |
}}
|
| 263 |
-
setTimeout(function() {{ window.close(); }},
|
| 264 |
}})();
|
| 265 |
</script>
|
| 266 |
</body>
|
| 267 |
</html>
|
| 268 |
"""
|
| 269 |
-
|
|
|
|
|
|
|
| 270 |
|
| 271 |
@app.get("/api/whoami")
|
| 272 |
async def whoami_endpoint(authorization: Optional[str] = Header(None)):
|
|
|
|
| 160 |
@app.get("/", response_class=HTMLResponse)
|
| 161 |
async def home(request: Request):
|
| 162 |
"""Home page - client-side auth with popup OAuth"""
|
|
|
|
|
|
|
|
|
|
|
|
|
| 163 |
# Return template - authentication will be handled client-side
|
| 164 |
return templates.TemplateResponse("index.html", {
|
| 165 |
"request": request,
|
| 166 |
+
"oauth_client_id": OAUTH_CLIENT_ID
|
|
|
|
| 167 |
})
|
| 168 |
|
| 169 |
+
@app.get("/api/auth/login")
|
| 170 |
+
async def auth_login(request: Request, state: Optional[str] = None):
|
| 171 |
+
"""OAuth login - stores state in cookie and redirects to HF OAuth"""
|
| 172 |
+
# Dynamically detect origin from request
|
| 173 |
+
origin = get_origin_from_request(request)
|
| 174 |
+
redirect_uri = f"{origin}/oauth/callback"
|
| 175 |
+
|
| 176 |
+
# Generate or use provided state
|
| 177 |
+
oauth_state = state or os.urandom(16).hex()
|
| 178 |
+
|
| 179 |
+
# Build OAuth authorize URL
|
| 180 |
+
auth_url = f"https://huggingface.co/oauth/authorize"
|
| 181 |
+
auth_url += f"?response_type=code"
|
| 182 |
+
auth_url += f"&client_id={OAUTH_CLIENT_ID}"
|
| 183 |
+
auth_url += f"&redirect_uri={redirect_uri}"
|
| 184 |
+
auth_url += f"&scope=openid profile"
|
| 185 |
+
auth_url += f"&state={oauth_state}"
|
| 186 |
+
|
| 187 |
+
# Create response that redirects to HF OAuth
|
| 188 |
+
response = RedirectResponse(url=auth_url, status_code=302)
|
| 189 |
+
|
| 190 |
+
# Store state in cookie for validation in callback
|
| 191 |
+
if not state: # Only set cookie if state wasn't provided
|
| 192 |
+
response.set_cookie(
|
| 193 |
+
key="hf_oauth_state",
|
| 194 |
+
value=oauth_state,
|
| 195 |
+
httponly=True,
|
| 196 |
+
samesite="lax",
|
| 197 |
+
secure=True,
|
| 198 |
+
max_age=300, # 5 minutes
|
| 199 |
+
path="/"
|
| 200 |
+
)
|
| 201 |
+
|
| 202 |
+
return response
|
| 203 |
+
|
| 204 |
@app.get("/oauth/callback", response_class=HTMLResponse)
|
| 205 |
+
async def oauth_callback(request: Request, code: str = None, state: str = None, hf_oauth_state: Optional[str] = Cookie(None)):
|
| 206 |
"""Handle OAuth callback - returns HTML that posts message to opener window"""
|
| 207 |
origin = get_origin_from_request(request)
|
| 208 |
redirect_uri = f"{origin}/oauth/callback"
|
| 209 |
|
| 210 |
+
# Validate state from cookie
|
| 211 |
+
if not code or not state or not hf_oauth_state or state != hf_oauth_state:
|
| 212 |
+
error_msg = 'Invalid or expired OAuth state'
|
| 213 |
+
if not code:
|
| 214 |
+
error_msg = 'Missing authorization code'
|
| 215 |
+
|
| 216 |
error_html = f"""
|
| 217 |
<!DOCTYPE html>
|
| 218 |
<html>
|
| 219 |
<body>
|
| 220 |
<script>
|
| 221 |
(function() {{
|
| 222 |
+
console.log('OAuth callback error: {error_msg}');
|
| 223 |
const target = window.opener || window.parent || window;
|
| 224 |
if (target) {{
|
| 225 |
target.postMessage({{
|
| 226 |
type: 'HF_OAUTH_ERROR',
|
| 227 |
+
payload: {{ message: '{error_msg}' }}
|
| 228 |
+
}}, '{origin}');
|
| 229 |
}}
|
| 230 |
+
setTimeout(function() {{ window.close(); }}, 100);
|
| 231 |
}})();
|
| 232 |
</script>
|
| 233 |
</body>
|
| 234 |
</html>
|
| 235 |
"""
|
| 236 |
+
response = HTMLResponse(content=error_html)
|
| 237 |
+
response.delete_cookie("hf_oauth_state")
|
| 238 |
+
return response
|
| 239 |
|
| 240 |
+
# Delete state cookie
|
| 241 |
try:
|
| 242 |
token_data = await exchange_code_for_token(code, redirect_uri)
|
| 243 |
access_token = token_data.get("access_token")
|
|
|
|
| 255 |
<body>
|
| 256 |
<script>
|
| 257 |
(function() {{
|
|
|
|
| 258 |
const target = window.opener || window.parent || window;
|
| 259 |
if (target) {{
|
|
|
|
| 260 |
target.postMessage({{
|
| 261 |
type: 'HF_OAUTH_SUCCESS',
|
| 262 |
payload: {{
|
| 263 |
token: {json.dumps(access_token)},
|
| 264 |
+
namespace: {json.dumps(user_info["username"])}
|
|
|
|
|
|
|
|
|
|
| 265 |
}}
|
| 266 |
+
}}, '{origin}');
|
| 267 |
}}
|
| 268 |
+
setTimeout(function() {{ window.close(); }}, 100);
|
|
|
|
|
|
|
|
|
|
| 269 |
}})();
|
| 270 |
</script>
|
| 271 |
</body>
|
| 272 |
</html>
|
| 273 |
"""
|
| 274 |
+
response = HTMLResponse(content=success_html)
|
| 275 |
+
response.delete_cookie("hf_oauth_state")
|
| 276 |
+
return response
|
| 277 |
|
| 278 |
except Exception as e:
|
| 279 |
print(f"OAuth callback error: {e}")
|
|
|
|
| 283 |
<body>
|
| 284 |
<script>
|
| 285 |
(function() {{
|
|
|
|
| 286 |
const target = window.opener || window.parent || window;
|
| 287 |
if (target) {{
|
| 288 |
target.postMessage({{
|
| 289 |
type: 'HF_OAUTH_ERROR',
|
| 290 |
payload: {{ message: {json.dumps(str(e))} }}
|
| 291 |
+
}}, '{origin}');
|
| 292 |
}}
|
| 293 |
+
setTimeout(function() {{ window.close(); }}, 100);
|
| 294 |
}})();
|
| 295 |
</script>
|
| 296 |
</body>
|
| 297 |
</html>
|
| 298 |
"""
|
| 299 |
+
response = HTMLResponse(content=error_html)
|
| 300 |
+
response.delete_cookie("hf_oauth_state")
|
| 301 |
+
return response
|
| 302 |
|
| 303 |
@app.get("/api/whoami")
|
| 304 |
async def whoami_endpoint(authorization: Optional[str] = Header(None)):
|
index.html
CHANGED
|
@@ -717,7 +717,6 @@
|
|
| 717 |
|
| 718 |
<script>
|
| 719 |
const OAUTH_CLIENT_ID = "{{ oauth_client_id }}";
|
| 720 |
-
const REDIRECT_URI = "{{ redirect_uri }}";
|
| 721 |
const AUTH_STORAGE_KEY = 'HF_AUTH_STATE';
|
| 722 |
|
| 723 |
// Authentication Manager
|
|
@@ -782,35 +781,9 @@
|
|
| 782 |
},
|
| 783 |
|
| 784 |
loginWithOAuth() {
|
| 785 |
-
console.log('
|
| 786 |
-
|
| 787 |
-
|
| 788 |
-
|
| 789 |
-
const authUrl = new URL('https://huggingface.co/oauth/authorize');
|
| 790 |
-
authUrl.searchParams.set('response_type', 'code');
|
| 791 |
-
authUrl.searchParams.set('client_id', OAUTH_CLIENT_ID);
|
| 792 |
-
authUrl.searchParams.set('redirect_uri', REDIRECT_URI);
|
| 793 |
-
authUrl.searchParams.set('scope', 'openid profile');
|
| 794 |
-
authUrl.searchParams.set('state', state);
|
| 795 |
-
|
| 796 |
-
console.log('OAuth URL:', authUrl.toString());
|
| 797 |
-
console.log('Redirect URI:', REDIRECT_URI);
|
| 798 |
-
|
| 799 |
-
// Open popup
|
| 800 |
-
const width = 600;
|
| 801 |
-
const height = 700;
|
| 802 |
-
const left = (screen.width - width) / 2;
|
| 803 |
-
const top = (screen.height - height) / 2;
|
| 804 |
-
const popup = window.open(
|
| 805 |
-
authUrl.toString(),
|
| 806 |
-
'HF OAuth',
|
| 807 |
-
`width=${width},height=${height},left=${left},top=${top}`
|
| 808 |
-
);
|
| 809 |
-
|
| 810 |
-
if (!popup) {
|
| 811 |
-
console.error('Failed to open popup - may be blocked');
|
| 812 |
-
app.showError('Popup was blocked. Please allow popups for this site.');
|
| 813 |
-
}
|
| 814 |
},
|
| 815 |
|
| 816 |
async handleOAuthMessage(event) {
|
|
@@ -821,16 +794,15 @@
|
|
| 821 |
return;
|
| 822 |
}
|
| 823 |
|
| 824 |
-
// Verify origin for security
|
| 825 |
if (!event.origin.includes('hf.space') && !event.origin.includes('huggingface.co') && !event.origin.includes('localhost')) {
|
| 826 |
console.warn('Message from untrusted origin:', event.origin);
|
| 827 |
return;
|
| 828 |
}
|
| 829 |
|
| 830 |
if (event.data.type === 'HF_OAUTH_SUCCESS') {
|
| 831 |
-
console.log('OAuth success received');
|
| 832 |
-
const { token,
|
| 833 |
-
sessionStorage.removeItem('HF_OAUTH_STATE');
|
| 834 |
|
| 835 |
try {
|
| 836 |
await this.validateAndSetAuth(token);
|
|
@@ -841,7 +813,6 @@
|
|
| 841 |
}
|
| 842 |
} else if (event.data.type === 'HF_OAUTH_ERROR') {
|
| 843 |
console.error('OAuth error received:', event.data.payload);
|
| 844 |
-
sessionStorage.removeItem('HF_OAUTH_STATE');
|
| 845 |
app.showError(event.data.payload.message || 'Authentication failed');
|
| 846 |
this.showLoginStrip();
|
| 847 |
}
|
|
@@ -857,7 +828,6 @@
|
|
| 857 |
this.user = null;
|
| 858 |
this.canStart = false;
|
| 859 |
localStorage.removeItem(AUTH_STORAGE_KEY);
|
| 860 |
-
sessionStorage.removeItem('HF_OAUTH_STATE');
|
| 861 |
this.showLoginStrip();
|
| 862 |
},
|
| 863 |
|
|
|
|
| 717 |
|
| 718 |
<script>
|
| 719 |
const OAUTH_CLIENT_ID = "{{ oauth_client_id }}";
|
|
|
|
| 720 |
const AUTH_STORAGE_KEY = 'HF_AUTH_STATE';
|
| 721 |
|
| 722 |
// Authentication Manager
|
|
|
|
| 781 |
},
|
| 782 |
|
| 783 |
loginWithOAuth() {
|
| 784 |
+
console.log('Starting OAuth flow - navigating to /api/auth/login');
|
| 785 |
+
// Navigate to login endpoint which handles state and redirects to HF OAuth
|
| 786 |
+
window.location.href = '/api/auth/login';
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 787 |
},
|
| 788 |
|
| 789 |
async handleOAuthMessage(event) {
|
|
|
|
| 794 |
return;
|
| 795 |
}
|
| 796 |
|
| 797 |
+
// Verify origin for security
|
| 798 |
if (!event.origin.includes('hf.space') && !event.origin.includes('huggingface.co') && !event.origin.includes('localhost')) {
|
| 799 |
console.warn('Message from untrusted origin:', event.origin);
|
| 800 |
return;
|
| 801 |
}
|
| 802 |
|
| 803 |
if (event.data.type === 'HF_OAUTH_SUCCESS') {
|
| 804 |
+
console.log('OAuth success received', event.data.payload);
|
| 805 |
+
const { token, namespace } = event.data.payload;
|
|
|
|
| 806 |
|
| 807 |
try {
|
| 808 |
await this.validateAndSetAuth(token);
|
|
|
|
| 813 |
}
|
| 814 |
} else if (event.data.type === 'HF_OAUTH_ERROR') {
|
| 815 |
console.error('OAuth error received:', event.data.payload);
|
|
|
|
| 816 |
app.showError(event.data.payload.message || 'Authentication failed');
|
| 817 |
this.showLoginStrip();
|
| 818 |
}
|
|
|
|
| 828 |
this.user = null;
|
| 829 |
this.canStart = false;
|
| 830 |
localStorage.removeItem(AUTH_STORAGE_KEY);
|
|
|
|
| 831 |
this.showLoginStrip();
|
| 832 |
},
|
| 833 |
|