multimodalart HF Staff commited on
Commit
26495e8
·
verified ·
1 Parent(s): 2a8354a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +105 -54
app.py CHANGED
@@ -1,16 +1,27 @@
1
  import os
2
  import sqlite3
 
3
  from datetime import datetime, timedelta
4
  from typing import Optional
5
- from fastapi import FastAPI, Request, HTTPException, Cookie
6
  from fastapi.responses import HTMLResponse, RedirectResponse, JSONResponse
7
  from fastapi.templating import Jinja2Templates
 
8
  from huggingface_hub import whoami
9
  import secrets
 
10
 
11
  app = FastAPI()
12
  templates = Jinja2Templates(directory="templates")
13
 
 
 
 
 
 
 
 
 
14
  DB_PATH = "/data/usage.db"
15
 
16
  def init_db():
@@ -59,7 +70,7 @@ def record_session(username: str, is_pro: bool):
59
  )
60
  conn.commit()
61
  except sqlite3.IntegrityError:
62
- # Already recorded today, that's fine
63
  pass
64
  finally:
65
  conn.close()
@@ -70,39 +81,74 @@ def can_start_session(username: str, is_pro: bool) -> tuple[bool, int, int]:
70
  limit = 15 if is_pro else 1
71
  return used < limit, used, limit
72
 
73
- def verify_token(token: str) -> Optional[dict]:
74
- """Verify HF token and get user info"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  try:
76
- user_info = whoami(token=token)
77
  return {
78
- "username": user_info.get("name"),
79
- "is_pro": user_info.get("isPro", False),
80
- "avatar": user_info.get("avatarUrl"),
81
- "fullname": user_info.get("fullname", user_info.get("name"))
 
82
  }
83
  except Exception as e:
84
- print(f"Token verification failed: {e}")
85
- return None
86
 
87
  @app.get("/", response_class=HTMLResponse)
88
- async def home(request: Request, hf_token: Optional[str] = Cookie(None)):
89
  """Home page - check auth and show app or login"""
90
 
91
- if not hf_token:
 
92
  return templates.TemplateResponse("index.html", {
93
  "request": request,
94
- "authenticated": False
 
 
 
95
  })
96
 
97
- user_info = verify_token(hf_token)
98
- if not user_info:
 
 
99
  # Invalid token, clear it
100
  response = templates.TemplateResponse("index.html", {
101
  "request": request,
102
  "authenticated": False,
 
 
 
103
  "error": "Session expired. Please login again."
104
  })
105
- response.delete_cookie("hf_token")
106
  return response
107
 
108
  # Check session limits
@@ -117,44 +163,48 @@ async def home(request: Request, hf_token: Optional[str] = Cookie(None)):
117
  "sessions_limit": limit
118
  })
119
 
120
- @app.post("/api/login")
121
- async def login(request: Request):
122
- """Login with HF token"""
123
- data = await request.json()
124
- token = data.get("token", "").strip()
125
-
126
- if not token:
127
- raise HTTPException(status_code=400, detail="Token is required")
128
 
129
- user_info = verify_token(token)
130
- if not user_info:
131
- raise HTTPException(status_code=401, detail="Invalid token")
132
 
133
- response = JSONResponse({
134
- "success": True,
135
- "user": user_info
136
- })
137
-
138
- # Set secure cookie with token (expires in 30 days)
139
- response.set_cookie(
140
- key="hf_token",
141
- value=token,
142
- httponly=True,
143
- secure=False, # Set to True in production with HTTPS
144
- samesite="lax",
145
- max_age=30 * 24 * 60 * 60
146
- )
147
-
148
- return response
 
 
 
 
 
 
 
 
149
 
150
  @app.post("/api/start-session")
151
- async def start_session(hf_token: Optional[str] = Cookie(None)):
152
  """Start a new generation session"""
153
- if not hf_token:
154
  raise HTTPException(status_code=401, detail="Not authenticated")
155
 
156
- user_info = verify_token(hf_token)
157
- if not user_info:
 
158
  raise HTTPException(status_code=401, detail="Invalid session")
159
 
160
  can_start, used, limit = can_start_session(user_info["username"], user_info["is_pro"])
@@ -175,13 +225,14 @@ async def start_session(hf_token: Optional[str] = Cookie(None)):
175
  }
176
 
177
  @app.get("/api/check-limits")
178
- async def check_limits(hf_token: Optional[str] = Cookie(None)):
179
  """Check current usage limits"""
180
- if not hf_token:
181
  raise HTTPException(status_code=401, detail="Not authenticated")
182
 
183
- user_info = verify_token(hf_token)
184
- if not user_info:
 
185
  raise HTTPException(status_code=401, detail="Invalid session")
186
 
187
  can_start, used, limit = can_start_session(user_info["username"], user_info["is_pro"])
@@ -197,10 +248,10 @@ async def check_limits(hf_token: Optional[str] = Cookie(None)):
197
  async def logout():
198
  """Logout user"""
199
  response = JSONResponse({"success": True})
200
- response.delete_cookie("hf_token")
201
  return response
202
 
203
  @app.get("/health")
204
  async def health():
205
  """Health check endpoint"""
206
- return {"status": "ok"}
 
1
  import os
2
  import sqlite3
3
+ import base64
4
  from datetime import datetime, timedelta
5
  from typing import Optional
6
+ from fastapi import FastAPI, Request, Response, Cookie, HTTPException
7
  from fastapi.responses import HTMLResponse, RedirectResponse, JSONResponse
8
  from fastapi.templating import Jinja2Templates
9
+ from fastapi.staticfiles import StaticFiles
10
  from huggingface_hub import whoami
11
  import secrets
12
+ import httpx
13
 
14
  app = FastAPI()
15
  templates = Jinja2Templates(directory="templates")
16
 
17
+ # OAuth configuration from HF Spaces environment
18
+ OAUTH_CLIENT_ID = os.getenv("OAUTH_CLIENT_ID")
19
+ OAUTH_CLIENT_SECRET = os.getenv("OAUTH_CLIENT_SECRET")
20
+ OAUTH_SCOPES = os.getenv("OAUTH_SCOPES", "openid profile")
21
+ SPACE_HOST = os.getenv("SPACE_HOST", "localhost:7860")
22
+ OPENID_PROVIDER_URL = os.getenv("OPENID_PROVIDER_URL", "https://huggingface.co")
23
+
24
+ # Database setup
25
  DB_PATH = "/data/usage.db"
26
 
27
  def init_db():
 
70
  )
71
  conn.commit()
72
  except sqlite3.IntegrityError:
73
+ # Already recorded today
74
  pass
75
  finally:
76
  conn.close()
 
81
  limit = 15 if is_pro else 1
82
  return used < limit, used, limit
83
 
84
+ async def exchange_code_for_token(code: str, redirect_uri: str) -> dict:
85
+ """Exchange OAuth code for access token"""
86
+ token_url = f"{OPENID_PROVIDER_URL}/oauth/token"
87
+
88
+ # Prepare Basic Auth header
89
+ credentials = f"{OAUTH_CLIENT_ID}:{OAUTH_CLIENT_SECRET}"
90
+ b64_credentials = base64.b64encode(credentials.encode()).decode()
91
+
92
+ headers = {
93
+ "Authorization": f"Basic {b64_credentials}",
94
+ "Content-Type": "application/x-www-form-urlencoded"
95
+ }
96
+
97
+ data = {
98
+ "grant_type": "authorization_code",
99
+ "code": code,
100
+ "redirect_uri": redirect_uri,
101
+ "client_id": OAUTH_CLIENT_ID
102
+ }
103
+
104
+ async with httpx.AsyncClient() as client:
105
+ response = await client.post(token_url, data=data, headers=headers)
106
+ response.raise_for_status()
107
+ return response.json()
108
+
109
+ async def get_user_info(access_token: str) -> dict:
110
+ """Get user info from access token using whoami"""
111
  try:
112
+ user_data = whoami(token=access_token)
113
  return {
114
+ "username": user_data.get("name"),
115
+ "is_pro": user_data.get("isPro", False),
116
+ "avatar": user_data.get("avatarUrl"),
117
+ "fullname": user_data.get("fullname", user_data.get("name")),
118
+ "email": user_data.get("email")
119
  }
120
  except Exception as e:
121
+ print(f"Failed to get user info: {e}")
122
+ raise HTTPException(status_code=401, detail="Failed to get user information")
123
 
124
  @app.get("/", response_class=HTMLResponse)
125
+ async def home(request: Request, access_token: Optional[str] = Cookie(None)):
126
  """Home page - check auth and show app or login"""
127
 
128
+ if not access_token:
129
+ # Not logged in - show login button
130
  return templates.TemplateResponse("index.html", {
131
  "request": request,
132
+ "authenticated": False,
133
+ "oauth_client_id": OAUTH_CLIENT_ID,
134
+ "redirect_uri": f"https://{SPACE_HOST}/oauth/callback",
135
+ "space_host": SPACE_HOST
136
  })
137
 
138
+ # Verify token and get user info
139
+ try:
140
+ user_info = await get_user_info(access_token)
141
+ except:
142
  # Invalid token, clear it
143
  response = templates.TemplateResponse("index.html", {
144
  "request": request,
145
  "authenticated": False,
146
+ "oauth_client_id": OAUTH_CLIENT_ID,
147
+ "redirect_uri": f"https://{SPACE_HOST}/oauth/callback",
148
+ "space_host": SPACE_HOST,
149
  "error": "Session expired. Please login again."
150
  })
151
+ response.delete_cookie("access_token")
152
  return response
153
 
154
  # Check session limits
 
163
  "sessions_limit": limit
164
  })
165
 
166
+ @app.get("/oauth/callback")
167
+ async def oauth_callback(code: str, state: Optional[str] = None):
168
+ """Handle OAuth callback from Hugging Face"""
169
+ if not code:
170
+ raise HTTPException(status_code=400, detail="Missing authorization code")
 
 
 
171
 
172
+ redirect_uri = f"https://{SPACE_HOST}/oauth/callback"
 
 
173
 
174
+ try:
175
+ # Exchange code for token
176
+ token_data = await exchange_code_for_token(code, redirect_uri)
177
+ access_token = token_data.get("access_token")
178
+
179
+ if not access_token:
180
+ raise HTTPException(status_code=400, detail="No access token received")
181
+
182
+ # Redirect to home with token as cookie
183
+ response = RedirectResponse(url="/", status_code=302)
184
+ response.set_cookie(
185
+ key="access_token",
186
+ value=access_token,
187
+ httponly=True,
188
+ secure=True,
189
+ samesite="lax",
190
+ max_age=30 * 24 * 60 * 60 # 30 days
191
+ )
192
+
193
+ return response
194
+
195
+ except Exception as e:
196
+ print(f"OAuth callback error: {e}")
197
+ raise HTTPException(status_code=400, detail=f"Authentication failed: {str(e)}")
198
 
199
  @app.post("/api/start-session")
200
+ async def start_session(access_token: Optional[str] = Cookie(None)):
201
  """Start a new generation session"""
202
+ if not access_token:
203
  raise HTTPException(status_code=401, detail="Not authenticated")
204
 
205
+ try:
206
+ user_info = await get_user_info(access_token)
207
+ except:
208
  raise HTTPException(status_code=401, detail="Invalid session")
209
 
210
  can_start, used, limit = can_start_session(user_info["username"], user_info["is_pro"])
 
225
  }
226
 
227
  @app.get("/api/check-limits")
228
+ async def check_limits(access_token: Optional[str] = Cookie(None)):
229
  """Check current usage limits"""
230
+ if not access_token:
231
  raise HTTPException(status_code=401, detail="Not authenticated")
232
 
233
+ try:
234
+ user_info = await get_user_info(access_token)
235
+ except:
236
  raise HTTPException(status_code=401, detail="Invalid session")
237
 
238
  can_start, used, limit = can_start_session(user_info["username"], user_info["is_pro"])
 
248
  async def logout():
249
  """Logout user"""
250
  response = JSONResponse({"success": True})
251
+ response.delete_cookie("access_token")
252
  return response
253
 
254
  @app.get("/health")
255
  async def health():
256
  """Health check endpoint"""
257
+ return {"status": "ok", "oauth_enabled": bool(OAUTH_CLIENT_ID)}