@@ -663,6 +663,62 @@ def exchange_code_for_token(self, code: str, code_verifier: str, redirect_uri: s
663663 return api_key
664664
665665
666+ def _handle_local_oauth_flow (
667+ oauth : OAuthHandler ,
668+ httpd : http .server .HTTPServer ,
669+ state : str ,
670+ code_verifier : str ,
671+ local_redirect_uri : str ,
672+ local_auth_url : str ,
673+ ) -> str | None :
674+ """Handle local OAuth flow with browser and server."""
675+ click .echo (f"\n 📋 If your browser didn't open, visit: { local_auth_url } \n " )
676+ click .echo ("⏳ Waiting for authentication..." )
677+
678+ success = oauth .wait_for_callback (httpd , timeout = 180 )
679+
680+ if not success :
681+ httpd .shutdown ()
682+ click .echo ("❌ Authentication timed out. Please try again." )
683+ return None
684+
685+ if oauth .error or not oauth .code or not oauth .state or oauth .state != state :
686+ httpd .shutdown ()
687+ click .echo ("❌ Unauthorized." )
688+ return None
689+
690+ api_key = oauth .exchange_code_for_token (oauth .code , code_verifier , local_redirect_uri )
691+
692+ # Wait for browser to poll status
693+ time .sleep (3 )
694+ httpd .shutdown ()
695+
696+ return api_key
697+
698+
699+ def _handle_remote_oauth_flow (code_verifier : str , remote_redirect_uri : str , remote_auth_url : str ) -> str | None :
700+ """Handle remote OAuth flow with manual code entry."""
701+ oauth = OAuthHandler ()
702+ click .echo ("⚠️ Browser could not be opened automatically." )
703+ click .echo ("\n 📋 Please visit this URL to authenticate:" )
704+ click .echo (f"\n { remote_auth_url } \n " )
705+
706+ # Prompt user to paste the code
707+ code = click .prompt ("Paste the authorization code here" , type = str ).strip ()
708+
709+ if not code :
710+ click .echo ("❌ No code provided." )
711+ return None
712+
713+ # Exchange code for token
714+ api_key = oauth .exchange_code_for_token (code , code_verifier , remote_redirect_uri )
715+
716+ if api_key :
717+ click .echo ("✅ Authentication successful!" )
718+
719+ return api_key
720+
721+
666722def perform_oauth_signin () -> str | None :
667723 """Perform OAuth PKCE flow and return API key if successful.
668724
@@ -672,60 +728,44 @@ def perform_oauth_signin() -> str | None:
672728
673729 # Setup PKCE
674730 port = oauth .get_free_port ()
675- redirect_uri = f"http://localhost:{ port } /callback"
676731 code_verifier , code_challenge = oauth .generate_pkce_pair ()
677732 state = "" .join (secrets .choice ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" ) for _ in range (16 ))
678733
679- # Build authorization URL
680- auth_url = (
734+ # Build authorization URLs for both local and remote
735+ local_redirect_uri = f"http://localhost:{ port } /callback"
736+ remote_redirect_uri = f"{ get_cfapi_base_urls ().cfwebapp_base_url } /codeflash/auth/callback"
737+
738+ local_auth_url = (
681739 f"{ get_cfapi_base_urls ().cfwebapp_base_url } /codeflash/auth?"
682740 f"response_type=code"
683741 f"&client_id=cf-cli-app"
684- f"&redirect_uri={ urllib .parse .quote (redirect_uri )} "
742+ f"&redirect_uri={ urllib .parse .quote (local_redirect_uri )} "
685743 f"&code_challenge={ code_challenge } "
686744 f"&code_challenge_method=sha256"
687745 f"&state={ state } "
688746 )
689747
690- # Start local server
691- httpd = oauth .start_local_server (port )
748+ remote_auth_url = (
749+ f"{ get_cfapi_base_urls ().cfwebapp_base_url } /codeflash/auth?"
750+ f"response_type=code"
751+ f"&client_id=cf-cli-app"
752+ f"&redirect_uri={ urllib .parse .quote (remote_redirect_uri )} "
753+ f"&code_challenge={ code_challenge } "
754+ f"&code_challenge_method=sha256"
755+ f"&state={ state } "
756+ )
692757
693- # Open browser
758+ # Try to open browser
694759 click .echo ("🌐 Opening browser to sign in to CodeFlash…" )
695- webbrowser .open (auth_url )
696-
697- click .echo (f"\n 📋 If your browser didn't open, visit: { auth_url } \n " )
698-
699- # Wait for callback
700- click .echo ("⏳ Waiting for authentication..." )
701- success = oauth .wait_for_callback (httpd , timeout = 180 )
702-
703- if not success :
704- httpd .shutdown ()
705- click .echo ("❌ Authentication timed out. Please try again." )
706- return None
707760
708- if oauth .error :
709- httpd .shutdown ()
710- click .echo ("❌ Authentication failed:" )
711- return None
712-
713- if not oauth .code or not oauth .state :
714- httpd .shutdown ()
715- click .echo ("❌ Unauthorized." )
716- return None
717-
718- if oauth .state != state :
719- httpd .shutdown ()
720- click .echo ("❌ Unauthorized." )
721- return None
722-
723- api_key = oauth .exchange_code_for_token (oauth .code , code_verifier , redirect_uri )
724-
725- # Wait for browser to poll status
726- time .sleep (3 )
761+ try :
762+ # Start local server first
763+ httpd = oauth .start_local_server (port )
764+ browser_opened = webbrowser .open (local_auth_url )
765+ except Exception :
766+ browser_opened = False
727767
728- # Shutdown server
768+ if browser_opened :
769+ return _handle_local_oauth_flow (oauth , httpd , state , code_verifier , local_redirect_uri , local_auth_url )
729770 httpd .shutdown ()
730-
731- return api_key
771+ return _handle_remote_oauth_flow (code_verifier , remote_redirect_uri , remote_auth_url )
0 commit comments