diff --git a/dropbox_auth_manager.py b/dropbox_auth_manager.py index dd34fc4..1635e33 100644 --- a/dropbox_auth_manager.py +++ b/dropbox_auth_manager.py @@ -234,53 +234,57 @@ class DropboxAuthManager: def reset(self, revoke_token=True): """Clear stored tokens and optionally revoke authorization with Dropbox""" - # Load credentials from keyring first if we don't have them in memory - if revoke_token and not (self.refresh_token and self.app_key and self.app_secret): - print("[DropboxAuthManager] Loading credentials for token revocation...") - self._try_load_from_keyring() - - # First, revoke the token with Dropbox if requested and possible - if revoke_token and self.refresh_token and self.app_key and self.app_secret: - try: - print("[DropboxAuthManager] Revoking token with Dropbox...") - - # Get current access token to revoke - access_token = self.get_access_token() - - # Revoke the token with Dropbox - revoke_data = { - "token": access_token - } - headers = { - "Authorization": f"Bearer {access_token}", - "Content-Type": "application/json" - } - - response = requests.post( - "https://api.dropboxapi.com/2/auth/token/revoke", - headers=headers, - json=revoke_data - ) - - if response.status_code == 200: - print("[DropboxAuthManager] Token successfully revoked with Dropbox") - else: - print(f"[DropboxAuthManager] Token revocation failed: {response.status_code} - {response.text}") + if revoke_token: + # Load credentials from keyring first if we don't have them in memory + if not (self.refresh_token and self.app_key and self.app_secret): + print("[DropboxAuthManager] Loading credentials for token revocation...") + self._try_load_from_keyring() + + # First, revoke the token with Dropbox if requested and possible + if self.refresh_token and self.app_key and self.app_secret: + try: + print("[DropboxAuthManager] Revoking token with Dropbox...") - except Exception as e: - print(f"[DropboxAuthManager] Could not revoke token with Dropbox: {e}") - # Continue with local cleanup even if revocation fails + # Get current access token to revoke + access_token = self.get_access_token() + + # Revoke the token with Dropbox + revoke_data = { + "token": access_token + } + headers = { + "Authorization": f"Bearer {access_token}", + "Content-Type": "application/json" + } + + response = requests.post( + "https://api.dropboxapi.com/2/auth/token/revoke", + headers=headers, + json=revoke_data + ) + + if response.status_code == 200: + print("[DropboxAuthManager] Token successfully revoked with Dropbox") + else: + print(f"[DropboxAuthManager] Token revocation failed: {response.status_code} - {response.text}") + + except Exception as e: + print(f"[DropboxAuthManager] Could not revoke token with Dropbox: {e}") + # Continue with local cleanup even if revocation fails + else: + print("[DropboxAuthManager] Skipping token revocation (revoke_token=False)") - # Clear from keyring - but don't block if keyring setup fails - try: - if self._ensure_keyring_setup(): + # Clear from keyring - but only if keyring is already available (don't force setup) + if self.keyring_tested and self.keyring_available: + try: keyring.delete_password(DROPBOX_KEYRING_SERVICE, "app_key") keyring.delete_password(DROPBOX_KEYRING_SERVICE, "app_secret") keyring.delete_password(DROPBOX_KEYRING_SERVICE, "refresh_token") print("[DropboxAuthManager] Cleared credentials from keyring") - except Exception as e: - print(f"[DropboxAuthManager] Could not clear keyring (continuing): {e}") - # Don't fail the reset - just continue with local cleanup + except Exception as e: + print(f"[DropboxAuthManager] Could not clear keyring (continuing): {e}") + else: + print("[DropboxAuthManager] Keyring not accessed - will clear file-based keyring manually") # Clear instance variables regardless of keyring availability self.app_key = None diff --git a/setup_dropbox_node.py b/setup_dropbox_node.py index c6ff972..ae6ab09 100644 --- a/setup_dropbox_node.py +++ b/setup_dropbox_node.py @@ -12,49 +12,48 @@ from .oauth_handler import OAuthCallbackHandler, get_server_base_url class DropboxSetupNode: @classmethod - def INPUT_TYPES(cls): - # Check if credentials are already stored WITHOUT loading from keyring (non-blocking) + def _check_credentials_non_blocking(cls): + """Check for credentials without accessing keyring (non-blocking)""" try: - print(f"[DropboxSetup] INPUT_TYPES: Checking for credentials (non-blocking)...") - is_connected = False - keyring_available = True # Assume available for UI defaults - - # Check for .env file credentials first (no keyring access) node_dir = os.path.dirname(__file__) + + # Check .env file env_path = os.path.join(node_dir, ".env") if os.path.exists(env_path): from dotenv import dotenv_values env_vars = dotenv_values(env_path) if all([env_vars.get("DROPBOX_APP_KEY"), env_vars.get("DROPBOX_APP_SECRET"), env_vars.get("DROPBOX_REFRESH_TOKEN")]): - is_connected = True - print(f"[DropboxSetup] Found credentials in .env file") + return True - # Check for display_only completion marker with environment variables - if not is_connected: - display_marker_path = os.path.join(node_dir, ".dropbox_display_complete") - if os.path.exists(display_marker_path): - # For display_only, verify that the user actually set up environment variables - display_only_env_set = all([ - os.getenv("DROPBOX_APP_KEY"), - os.getenv("DROPBOX_APP_SECRET"), - os.getenv("DROPBOX_REFRESH_TOKEN") - ]) - if display_only_env_set: - is_connected = True - print(f"[DropboxSetup] Found display_only completion marker with valid environment variables") - else: - print(f"[DropboxSetup] Found display_only completion marker but environment variables not set yet") - - # Check for keyring credentials ONLY by looking for keyring files (no keyring access) - if not is_connected: - keyring_files = [".dropbox_keyring.cfg", ".dropbox_keyring_plaintext.cfg"] - for keyring_file in keyring_files: - keyring_path = os.path.join(node_dir, keyring_file) - if os.path.exists(keyring_path) and os.path.getsize(keyring_path) > 0: - is_connected = True - print(f"[DropboxSetup] Found keyring credential file: {keyring_file}") - break + # Check display_only with environment variables + display_marker_path = os.path.join(node_dir, ".dropbox_display_complete") + if os.path.exists(display_marker_path): + display_only_env_set = all([ + os.getenv("DROPBOX_APP_KEY"), + os.getenv("DROPBOX_APP_SECRET"), + os.getenv("DROPBOX_REFRESH_TOKEN") + ]) + if display_only_env_set: + return True + # Check keyring files existence + keyring_files = [".dropbox_keyring.cfg", ".dropbox_keyring_plaintext.cfg"] + for keyring_file in keyring_files: + keyring_path = os.path.join(node_dir, keyring_file) + if os.path.exists(keyring_path) and os.path.getsize(keyring_path) > 0: + return True + + return False + except Exception: + return False + + @classmethod + def INPUT_TYPES(cls): + # Check if credentials are already stored WITHOUT loading from keyring (non-blocking) + try: + print(f"[DropboxSetup] INPUT_TYPES: Checking for credentials (non-blocking)...") + is_connected = cls._check_credentials_non_blocking() + keyring_available = True # Assume available for UI defaults print(f"[DropboxSetup] INPUT_TYPES check - is_connected: {is_connected} (no keyring access)") except Exception as e: print(f"[DropboxSetup] INPUT_TYPES error: {e}") @@ -119,30 +118,41 @@ class DropboxSetupNode: print(f" auto_oauth: {auto_oauth}") print(f" storage_method: {storage_method}") - # Initialize auth manager + # Initialize auth manager (no keyring access yet) auth_manager = DropboxAuthManager() - print(f"[DropboxSetup] Auth manager initialized. Is connected: {auth_manager.is_connected()}") + print(f"[DropboxSetup] Auth manager initialized (keyring access deferred)") # Handle reconnect/reset request if reconnect: - print("[DropboxSetup] Reconnect requested - revoking token and clearing credentials") + print("[DropboxSetup] Reconnect requested - clearing all credentials") - # Clear keyring credentials AND revoke token with Dropbox - auth_manager.reset(revoke_token=True) + # Skip token revocation to avoid keyring access - just clear everything + print("[DropboxSetup] Skipping token revocation to avoid keyring prompts") + auth_manager.reset(revoke_token=False) - # Also clear .env file if it exists + # Manually clear all credential files node_dir = os.path.dirname(__file__) + + # Clear .env file if it exists env_path = os.path.join(node_dir, ".env") if os.path.exists(env_path): print(f"[DropboxSetup] Removing .env file: {env_path}") os.remove(env_path) - # Also clear display_only marker if it exists + # Clear display_only marker if it exists display_marker_path = os.path.join(node_dir, ".dropbox_display_complete") if os.path.exists(display_marker_path): print(f"[DropboxSetup] Removing display_only marker: {display_marker_path}") os.remove(display_marker_path) + # Clear keyring files manually (avoiding keyring API) + keyring_files = [".dropbox_keyring.cfg", ".dropbox_keyring_plaintext.cfg"] + for keyring_file in keyring_files: + keyring_path = os.path.join(node_dir, keyring_file) + if os.path.exists(keyring_path): + print(f"[DropboxSetup] Removing keyring file: {keyring_file}") + os.remove(keyring_path) + # Send WebSocket message to trigger ComfyUI refresh after clearing credentials try: from server import PromptServer @@ -163,13 +173,15 @@ class DropboxSetupNode: "result": (message,) } - # Check if already connected (keyring has credentials) - if auth_manager.is_connected(): + # Check if already connected using the same non-blocking method as INPUT_TYPES + is_connected = self._check_credentials_non_blocking() + if is_connected: print("[DropboxSetup] Already connected - testing stored credentials") - # Test the stored credentials by getting an access token + # Only access keyring now when we know we need to test credentials try: + # This will trigger keyring access but only when we know credentials exist access_token = auth_manager.get_access_token() - message = "✅ Dropbox already connected using stored keyring credentials. Ready to upload files." + message = "✅ Dropbox already connected using stored credentials. Ready to upload files." print(f"[DropboxSetup] {message}") return { "ui": {"text": [message]},