apolinario commited on
Commit
2ec4a9a
·
1 Parent(s): b4ebdaf

new attempt

Browse files
Files changed (2) hide show
  1. app.py +62 -30
  2. 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
- if not code:
181
- # Return error HTML that closes popup
 
 
 
 
182
  error_html = f"""
183
  <!DOCTYPE html>
184
  <html>
185
  <body>
186
  <script>
187
  (function() {{
188
- console.log('OAuth callback - missing code error');
189
  const target = window.opener || window.parent || window;
190
  if (target) {{
191
  target.postMessage({{
192
  type: 'HF_OAUTH_ERROR',
193
- payload: {{ message: 'Missing authorization code' }}
194
- }}, '*');
195
  }}
196
- setTimeout(function() {{ window.close(); }}, 300);
197
  }})();
198
  </script>
199
  </body>
200
  </html>
201
  """
202
- return HTMLResponse(content=error_html)
 
 
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
- username: {json.dumps(user_info["username"])},
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
- return HTMLResponse(content=success_html)
 
 
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(); }}, 300);
264
  }})();
265
  </script>
266
  </body>
267
  </html>
268
  """
269
- return HTMLResponse(content=error_html)
 
 
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('Opening OAuth popup');
786
- const state = crypto.randomUUID();
787
- sessionStorage.setItem('HF_OAUTH_STATE', state);
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 (allow HF domains and localhost for testing)
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, username, is_pro, fullname, avatar } = event.data.payload;
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