# oauth_handler.py - OAuth callback handling for DropSendNode import asyncio from aiohttp import web from .dropbox_auth_manager import DropboxAuthManager # Global state to track pending OAuth flows pending_oauth_sessions = {} class OAuthCallbackHandler: def __init__(self): self.session_id = None self.app_key = None self.app_secret = None self.completion_callback = None def start_oauth_session(self, session_id, app_key, app_secret, completion_callback=None, storage_method="keyring", dropbox_folder="/Apps/ComfyUI_Output_Files", original_redirect_uri=None): """Start a new OAuth session and store the credentials for callback processing""" print(f"[OAuthHandler] Starting OAuth session: {session_id}") self.session_id = session_id self.app_key = app_key self.app_secret = app_secret self.completion_callback = completion_callback self.storage_method = storage_method self.dropbox_folder = dropbox_folder # Store in global pending sessions pending_oauth_sessions[session_id] = { 'app_key': app_key, 'app_secret': app_secret, 'completion_callback': completion_callback, 'storage_method': storage_method, 'dropbox_folder': dropbox_folder, 'original_redirect_uri': original_redirect_uri, # Store the original redirect URI 'handler': self } print(f"[OAuthHandler] OAuth session stored for: {session_id} with storage method: {storage_method}") async def process_callback(self, auth_code, redirect_uri=None): """Process the OAuth callback with the received auth code""" try: print(f"[OAuthHandler] Processing callback for session: {self.session_id}") print(f"[OAuthHandler] Auth code received: {auth_code[:10]}...") print(f"[OAuthHandler] Redirect URI: {redirect_uri}") # Use DropboxAuthManager to exchange the code print(f"[OAuthHandler] Creating DropboxAuthManager for token exchange...") auth_manager = DropboxAuthManager(self.app_key, self.app_secret) print(f"[OAuthHandler] Exchanging auth code for tokens...") result = auth_manager.exchange_auth_code_raw(auth_code, redirect_uri=redirect_uri) refresh_token = result.get("refresh_token") print(f"[OAuthHandler] Auth code exchange successful - got refresh token: {bool(refresh_token)}") print(f"[OAuthHandler] Using storage method: {self.storage_method}") # Handle different storage methods success_message = None if self.storage_method == "env_file": # Store in .env file import os node_dir = os.path.dirname(__file__) env_path = os.path.join(node_dir, ".env") with open(env_path, "w") as f: f.write(f"DROPBOX_APP_KEY={self.app_key}\n") f.write(f"DROPBOX_APP_SECRET={self.app_secret}\n") f.write(f"DROPBOX_REFRESH_TOKEN={refresh_token}\n") f.write(f"DROPBOX_FOLDER={self.dropbox_folder}\n") success_message = "Dropbox connected successfully! Credentials saved to .env file." elif self.storage_method == "display_only": # Display credentials for manual copying and create completion marker import os node_dir = os.path.dirname(__file__) display_marker_path = os.path.join(node_dir, ".dropbox_display_complete") with open(display_marker_path, "w") as f: f.write("display_only_setup_completed") # Print credentials to console for easy copy/paste print("\n" + "=" * 80) print("DROPBOX CREDENTIALS READY FOR PRODUCTION - COPY FROM CONSOLE") print("=" * 80) print(f"DROPBOX_APP_KEY={self.app_key}") print(f"DROPBOX_APP_SECRET={self.app_secret}") print(f"DROPBOX_REFRESH_TOKEN={refresh_token}") print(f"DROPBOX_FOLDER={self.dropbox_folder}") print("=" * 80) print("Copy the lines above to your environment variables!") print("Perfect for RunPod, Docker, and production environments!") print("=" * 80 + "\n") success_message = f"""Dropbox Connected Successfully! ===================================================================== ENVIRONMENT VARIABLES - Copy & Paste Ready ===================================================================== DROPBOX_APP_KEY={self.app_key} DROPBOX_APP_SECRET={self.app_secret} DROPBOX_REFRESH_TOKEN={refresh_token} DROPBOX_FOLDER={self.dropbox_folder} ===================================================================== Perfect for RunPod, Docker, and production environments! These credentials are ready to use immediately. =====================================================================""" else: # Fallback to env_file import os node_dir = os.path.dirname(__file__) env_path = os.path.join(node_dir, ".env") with open(env_path, "w") as f: f.write(f"DROPBOX_APP_KEY={self.app_key}\n") f.write(f"DROPBOX_APP_SECRET={self.app_secret}\n") f.write(f"DROPBOX_REFRESH_TOKEN={refresh_token}\n") f.write(f"DROPBOX_FOLDER={self.dropbox_folder}\n") success_message = "Dropbox connected successfully! Credentials saved to .env file." # Send WebSocket message to trigger ComfyUI refresh (except for display_only) if self.storage_method != "display_only": try: from server import PromptServer message_data = { "type": "dropbox_oauth_complete", "session_id": self.session_id, "success": True, "message": success_message } PromptServer.instance.send_sync("dropbox_oauth_complete", message_data) print(f"[OAuthHandler] Sent WebSocket notification for OAuth completion") except Exception as e: print(f"[OAuthHandler] Warning: Could not send WebSocket notification: {e}") else: print(f"[OAuthHandler] Skipping WebSocket notification for display_only method - no auto-refresh") # Call completion callback if provided if self.completion_callback: await self.completion_callback(True, success_message) return True, success_message except Exception as e: error_msg = f"Error: OAuth callback failed: {e}" print(f"[OAuthHandler] ERROR: {error_msg}") if self.completion_callback: await self.completion_callback(False, error_msg) return False, error_msg def create_display_only_success_page(session_id, app_key, app_secret, refresh_token, dropbox_folder): """Create a success page with textarea fields for display_only storage method""" from aiohttp import web return web.Response(text=f"""
Your credentials are ready for production use. Copy them to your environment variables.
{error_msg}
You can close this window and try again in ComfyUI.
""", content_type='text/html', status=400) if not auth_code: print(f"[OAuthCallback] ERROR: No authorization code received") return web.Response(text="""No authorization code was received. Please try again.
You can close this window and try again in ComfyUI.
""", content_type='text/html', status=400) # Find the pending OAuth session if session_id and session_id in pending_oauth_sessions: session_data = pending_oauth_sessions[session_id] handler = session_data['handler'] # Use the original redirect URI that was used for authorization (not the detected one) original_callback_url = session_data.get('original_redirect_uri') if original_callback_url: print(f"[OAuthCallback] Using stored redirect URI: {original_callback_url}") callback_url = original_callback_url else: # Fallback to dynamic detection base_url = get_server_base_url(request) callback_url = f"{base_url}/oauth/dropbox/callback" print(f"[OAuthCallback] Using detected redirect URI: {callback_url}") success, message = await handler.process_callback(auth_code, redirect_uri=callback_url) # Clean up the session del pending_oauth_sessions[session_id] if success: # Check if this is a display_only flow and show appropriate success page storage_method = session_data.get('storage_method', 'keyring') if storage_method == 'display_only': # Extract refresh token from the success message (token exchange already happened in process_callback) if 'DROPBOX_REFRESH_TOKEN=' in message: import re refresh_token_match = re.search(r'DROPBOX_REFRESH_TOKEN=([^\s\n]+)', message) if refresh_token_match: refresh_token = refresh_token_match.group(1) app_key = session_data['app_key'] app_secret = session_data['app_secret'] dropbox_folder = session_data['dropbox_folder'] # Special success page with textarea fields for credentials (stays open for copying) return create_display_only_success_page(session_id, app_key, app_secret, refresh_token, dropbox_folder) # If we couldn't extract the refresh token, fall back to error handling return web.Response(text=f"""Could not extract refresh token from OAuth response.
Please close this window and try again in ComfyUI.
Debug info: {message[:200]}...
""", content_type='text/html', status=500) else: # Standard success page for env_file storage method (auto-closes quickly) return web.Response(text=f"""{message}
{message}
You can close this window and try again in ComfyUI.
Please return to ComfyUI and manually enter this code:
📋 Select the code above and copy it to ComfyUI
An unexpected error occurred: {e}
You can close this window and try again in ComfyUI.
""", content_type='text/html', status=500)