Da-123 commited on
Commit
8f7fd07
·
1 Parent(s): def32a7

remove local

Browse files
agentic_implementation/email_mcp_server_oauth.py CHANGED
@@ -120,6 +120,17 @@ def authenticate_user() -> str:
120
  try:
121
  logger.info("Starting OAuth authentication flow...")
122
 
 
 
 
 
 
 
 
 
 
 
 
123
  # Check if OAuth is configured
124
  if not oauth_manager.client_secrets_file.exists():
125
  return json.dumps({
@@ -144,16 +155,44 @@ def authenticate_user() -> str:
144
  ]
145
  }
146
  else:
147
- result = {
148
- "success": False,
149
- "error": "Authentication failed",
150
- "message": "Please try again or check your internet connection.",
151
- "instructions": [
152
- "Make sure you have internet connection",
153
- "Ensure you complete the authentication in the browser",
154
- "Try running 'python setup_oauth.py' if problems persist"
155
- ]
156
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
157
 
158
  return json.dumps(result, indent=2)
159
 
@@ -165,6 +204,79 @@ def authenticate_user() -> str:
165
  "message": "Authentication failed due to an error."
166
  }
167
  return json.dumps(error_result, indent=2)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
 
169
  def switch_account(target_email: str) -> str:
170
  """
 
120
  try:
121
  logger.info("Starting OAuth authentication flow...")
122
 
123
+ if oauth_manager.is_authenticated():
124
+ user_email = oauth_manager.get_current_account()
125
+ return json.dumps({
126
+ "success": True,
127
+ "message": "Already authenticated!",
128
+ "user_email": user_email,
129
+ "instructions": [
130
+ "You are already authenticated and ready to use email tools",
131
+ f"Currently authenticated as: {user_email}"
132
+ ]
133
+ }, indent=2)
134
  # Check if OAuth is configured
135
  if not oauth_manager.client_secrets_file.exists():
136
  return json.dumps({
 
155
  ]
156
  }
157
  else:
158
+ # Authentication not completed, provide manual instructions
159
+ auth_url = oauth_manager.get_pending_auth_url()
160
+ callback_url = oauth_manager.get_hf_redirect_uri()
161
+
162
+ if auth_url:
163
+ result = {
164
+ "success": False,
165
+ "message": "Manual authentication required",
166
+ "auth_url": auth_url,
167
+ "callback_url": callback_url,
168
+ "instructions": [
169
+ "Authentication URL has been generated",
170
+ "Please click the link below to authenticate:",
171
+ "1. Click the authentication URL",
172
+ "2. Sign in with your Google account",
173
+ "3. Grant Gmail access permissions",
174
+ "4. You'll be redirected back automatically",
175
+ "5. Try clicking 'Submit' again after completing authentication"
176
+ ],
177
+ "note": "After completing authentication in the popup, click Submit again to verify"
178
+ }
179
+ else:
180
+ result = {
181
+ "success": False,
182
+ "error": "Failed to generate authentication URL",
183
+ "message": "Could not start authentication process. Check your OAuth configuration."
184
+ }
185
+
186
+ # result = {
187
+ # "success": False,
188
+ # "error": "Authentication failed",
189
+ # "message": "Please try again or check your internet connection.",
190
+ # "instructions": [
191
+ # "Make sure you have internet connection",
192
+ # "Ensure you complete the authentication in the browser",
193
+ # "Try running 'python setup_oauth.py' if problems persist"
194
+ # ]
195
+ # }
196
 
197
  return json.dumps(result, indent=2)
198
 
 
204
  "message": "Authentication failed due to an error."
205
  }
206
  return json.dumps(error_result, indent=2)
207
+
208
+
209
+ def handle_oauth_callback(auth_code: str) -> str:
210
+ """Handle OAuth callback for Hugging Face Spaces
211
+
212
+ Args:
213
+ auth_code: Authorization code from OAuth callback
214
+
215
+ Returns:
216
+ HTML response string
217
+ """
218
+ try:
219
+ if not auth_code:
220
+ return """
221
+ <html>
222
+ <head><title>OAuth Error</title></head>
223
+ <body style="font-family: Arial, sans-serif; text-align: center; padding: 50px;">
224
+ <h1 style="color: #d32f2f;">Authentication Error</h1>
225
+ <p>No authorization code received.</p>
226
+ <button onclick="window.close()" style="padding: 10px 20px; margin: 20px; background: #1976d2; color: white; border: none; border-radius: 4px; cursor: pointer;">Close Window</button>
227
+ </body>
228
+ </html>
229
+ """
230
+
231
+ success = oauth_manager.complete_hf_spaces_auth(auth_code)
232
+
233
+ if success:
234
+ user_email = oauth_manager.get_current_account()
235
+ return f"""
236
+ <html>
237
+ <head><title>OAuth Success</title></head>
238
+ <body style="font-family: Arial, sans-serif; text-align: center; padding: 50px;">
239
+ <h1 style="color: #2e7d32;">🎉 Authentication Successful!</h1>
240
+ <p>You are now authenticated as:</p>
241
+ <p style="font-weight: bold; font-size: 18px; color: #1976d2;">{user_email}</p>
242
+ <p>You can now close this window and return to the main application.</p>
243
+ <p style="color: #666; font-size: 14px;">This window will close automatically in 5 seconds...</p>
244
+ <button onclick="window.close()" style="padding: 10px 20px; margin: 20px; background: #2e7d32; color: white; border: none; border-radius: 4px; cursor: pointer;">Close Window</button>
245
+ <script>
246
+ setTimeout(function() {{
247
+ window.close();
248
+ }}, 5000);
249
+ </script>
250
+ </body>
251
+ </html>
252
+ """
253
+ else:
254
+ return """
255
+ <html>
256
+ <head><title>OAuth Error</title></head>
257
+ <body style="font-family: Arial, sans-serif; text-align: center; padding: 50px;">
258
+ <h1 style="color: #d32f2f;">Authentication Failed</h1>
259
+ <p>Unable to complete authentication. Please try again.</p>
260
+ <p>Make sure you granted all required permissions.</p>
261
+ <button onclick="window.close()" style="padding: 10px 20px; margin: 20px; background: #d32f2f; color: white; border: none; border-radius: 4px; cursor: pointer;">Close Window</button>
262
+ </body>
263
+ </html>
264
+ """
265
+
266
+ except Exception as e:
267
+ logger.error(f"Error handling OAuth callback: {e}")
268
+ return f"""
269
+ <html>
270
+ <head><title>OAuth Error</title></head>
271
+ <body style="font-family: Arial, sans-serif; text-align: center; padding: 50px;">
272
+ <h1 style="color: #d32f2f;">Authentication Error</h1>
273
+ <p>An error occurred during authentication:</p>
274
+ <pre style="background: #f5f5f5; padding: 10px; border-radius: 4px; text-align: left; max-width: 500px; margin: 0 auto;">{str(e)}</pre>
275
+ <button onclick="window.close()" style="padding: 10px 20px; margin: 20px; background: #d32f2f; color: white; border: none; border-radius: 4px; cursor: pointer;">Close Window</button>
276
+ </body>
277
+ </html>
278
+ """
279
+
280
 
281
  def switch_account(target_email: str) -> str:
282
  """
agentic_implementation/oauth_manager.py CHANGED
@@ -14,7 +14,7 @@ import webbrowser
14
  import threading
15
  import time
16
  from http.server import HTTPServer, BaseHTTPRequestHandler
17
- import urllib.parse as urlparse
18
  from logger import logger
19
  from dotenv import load_dotenv
20
  load_dotenv()
@@ -205,41 +205,95 @@ class GmailOAuthManager:
205
  return auth_url
206
 
207
  def authenticate_interactive(self) -> bool:
208
- """Interactive authentication flow that opens browser
209
 
210
  Returns:
211
  True if authentication successful, False otherwise
212
  """
213
  try:
214
- # Start local HTTP server for OAuth callback
215
- server = HTTPServer(('localhost', 8080), OAuthCallbackHandler)
216
- server.auth_code = None
 
 
217
 
218
  # Get authorization URL
219
  auth_url = self.get_authorization_url()
220
 
221
- logger.info("Opening browser for authentication...")
222
- logger.info(f"If browser doesn't open, visit: {auth_url}")
 
223
 
224
- # Open browser
225
- webbrowser.open(auth_url)
 
226
 
227
- # Start server in background thread
228
- server_thread = threading.Thread(target=server.handle_request)
229
- server_thread.daemon = True
230
- server_thread.start()
 
231
 
232
- # Wait for callback (max 5 minutes)
233
- timeout = 300 # 5 minutes
 
234
  start_time = time.time()
235
 
236
- while server.auth_code is None and (time.time() - start_time) < timeout:
237
- time.sleep(1)
 
 
 
 
 
 
 
 
 
 
 
238
 
239
- if server.auth_code is None:
240
- logger.error("Authentication timed out")
241
- return False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
242
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
243
  # Exchange authorization code for credentials
244
  flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
245
  str(self.client_secrets_file),
@@ -247,7 +301,7 @@ class GmailOAuthManager:
247
  )
248
  flow.redirect_uri = self.redirect_uri
249
 
250
- flow.fetch_token(code=server.auth_code)
251
  credentials = flow.credentials
252
 
253
  # Get user email from credentials
@@ -266,8 +320,92 @@ class GmailOAuthManager:
266
  return True
267
 
268
  except Exception as e:
269
- logger.error(f"Authentication failed: {e}")
270
  return False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
271
 
272
  def _get_email_from_credentials(self, credentials: Credentials) -> Optional[str]:
273
  """Get email address from credentials"""
 
14
  import threading
15
  import time
16
  from http.server import HTTPServer, BaseHTTPRequestHandler
17
+ from urllib.parse import urlparse,parse_qs
18
  from logger import logger
19
  from dotenv import load_dotenv
20
  load_dotenv()
 
205
  return auth_url
206
 
207
  def authenticate_interactive(self) -> bool:
208
+ """Interactive authentication flow for Hugging Face Spaces
209
 
210
  Returns:
211
  True if authentication successful, False otherwise
212
  """
213
  try:
214
+ # Check if already authenticated
215
+ if self.is_authenticated():
216
+ logger.info("Already authenticated")
217
+ return True
218
+
219
 
220
  # Get authorization URL
221
  auth_url = self.get_authorization_url()
222
 
223
+ logger.info("Running on Hugging Face Spaces")
224
+ logger.info(f"Authentication URL generated: {auth_url}")
225
+ logger.info("User must visit the URL manually to complete authentication")
226
 
227
+ # Store the auth URL for the Gradio interface to use
228
+ self._pending_auth_url = auth_url
229
+ self._auth_completed = False
230
 
231
+ # For setup_oauth.py and testing contexts, we'll print the URL
232
+ # and wait briefly to see if authentication completes
233
+ print(f"\n🌐 Please visit this URL to authenticate:")
234
+ print(f" {auth_url}")
235
+ print("\n⏳ Waiting for authentication completion...")
236
 
237
+ # Wait for a reasonable time to see if auth completes
238
+ # This allows the callback to potentially complete the auth
239
+ timeout = 60 # 1 minute for manual completion
240
  start_time = time.time()
241
 
242
+ while (time.time() - start_time) < timeout:
243
+ # Check if authentication was completed via callback
244
+ if getattr(self, '_auth_completed', False):
245
+ logger.info("Authentication completed successfully!")
246
+ return True
247
+
248
+ # Check if user is now authenticated (credentials were saved)
249
+ if self.is_authenticated():
250
+ self._auth_completed = True
251
+ logger.info("Authentication verified successful!")
252
+ return True
253
+
254
+ time.sleep(2) # Check every 2 seconds
255
 
256
+ # Timeout reached - authentication not completed
257
+ logger.info("Authentication timeout. Please complete authentication via the provided URL.")
258
+ return False
259
+
260
+ except Exception as e:
261
+ logger.error(f"Authentication failed: {e}")
262
+ return False
263
+
264
+ def complete_hf_spaces_auth(self, auth_code: str) -> bool:
265
+ """Complete authentication for HF Spaces with received auth code
266
+
267
+ Args:
268
+ auth_code: Authorization code received from OAuth callback
269
+
270
+ Returns:
271
+ True if authentication successful, False otherwise
272
+ """
273
+ try:
274
+ success = self._exchange_code_for_credentials(auth_code)
275
 
276
+ if success:
277
+ # Mark authentication as completed
278
+ self._auth_completed = True
279
+ logger.info("HF Spaces authentication marked as completed")
280
+
281
+ return success
282
+
283
+ except Exception as e:
284
+ logger.error(f"Failed to complete HF Spaces authentication: {e}")
285
+ return False
286
+
287
+ def _exchange_code_for_credentials(self, auth_code: str) -> bool:
288
+ """Exchange authorization code for credentials
289
+
290
+ Args:
291
+ auth_code: Authorization code from OAuth flow
292
+
293
+ Returns:
294
+ True if successful, False otherwise
295
+ """
296
+ try:
297
  # Exchange authorization code for credentials
298
  flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
299
  str(self.client_secrets_file),
 
301
  )
302
  flow.redirect_uri = self.redirect_uri
303
 
304
+ flow.fetch_token(code=auth_code)
305
  credentials = flow.credentials
306
 
307
  # Get user email from credentials
 
320
  return True
321
 
322
  except Exception as e:
323
+ logger.error(f"Failed to exchange code for credentials: {e}")
324
  return False
325
+
326
+ def get_pending_auth_url(self) -> str:
327
+ """Get the pending authentication URL for manual completion
328
+
329
+ Returns:
330
+ Authentication URL string or None if not available
331
+ """
332
+ return getattr(self, '_pending_auth_url', None)
333
+
334
+ def get_hf_redirect_uri(self) -> str:
335
+ """Get the Hugging Face Spaces redirect URI
336
+
337
+ Returns:
338
+ Redirect URI string
339
+ """
340
+ space_id = os.getenv('SPACE_ID')
341
+ space_author = os.getenv('SPACE_AUTHOR', 'username')
342
+ return f"https://{space_author}-{space_id}.hf.space/oauth/callback"
343
+
344
+
345
+ # def authenticate_interactive(self) -> bool:
346
+ # """Interactive authentication flow that opens browser
347
+
348
+ # Returns:
349
+ # True if authentication successful, False otherwise
350
+ # """
351
+ # try:
352
+ # # Start local HTTP server for OAuth callback
353
+ # server = HTTPServer(('localhost', 8080), OAuthCallbackHandler)
354
+ # server.auth_code = None
355
+
356
+ # # Get authorization URL
357
+ # auth_url = self.get_authorization_url()
358
+
359
+ # logger.info("Opening browser for authentication...")
360
+ # logger.info(f"If browser doesn't open, visit: {auth_url}")
361
+
362
+ # # Open browser
363
+ # webbrowser.open(auth_url)
364
+
365
+ # # Start server in background thread
366
+ # server_thread = threading.Thread(target=server.handle_request)
367
+ # server_thread.daemon = True
368
+ # server_thread.start()
369
+
370
+ # # Wait for callback (max 5 minutes)
371
+ # timeout = 300 # 5 minutes
372
+ # start_time = time.time()
373
+
374
+ # while server.auth_code is None and (time.time() - start_time) < timeout:
375
+ # time.sleep(1)
376
+
377
+ # if server.auth_code is None:
378
+ # logger.error("Authentication timed out")
379
+ # return False
380
+
381
+ # # Exchange authorization code for credentials
382
+ # flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
383
+ # str(self.client_secrets_file),
384
+ # scopes=self.SCOPES
385
+ # )
386
+ # flow.redirect_uri = self.redirect_uri
387
+
388
+ # flow.fetch_token(code=server.auth_code)
389
+ # credentials = flow.credentials
390
+
391
+ # # Get user email from credentials
392
+ # user_email = self._get_email_from_credentials(credentials)
393
+ # if not user_email:
394
+ # logger.error("Failed to get user email from credentials")
395
+ # return False
396
+
397
+ # # Save encrypted credentials for this account
398
+ # self._save_credentials(user_email, credentials)
399
+
400
+ # # Set as current account
401
+ # self._save_current_account(user_email)
402
+
403
+ # logger.info("Authentication successful!")
404
+ # return True
405
+
406
+ # except Exception as e:
407
+ # logger.error(f"Authentication failed: {e}")
408
+ # return False
409
 
410
  def _get_email_from_credentials(self, credentials: Credentials) -> Optional[str]:
411
  """Get email address from credentials"""