Skip to content

Commit 6d1b3b8

Browse files
committed
Add remote-machine logic.
1 parent 76089d2 commit 6d1b3b8

File tree

1 file changed

+81
-41
lines changed

1 file changed

+81
-41
lines changed

codeflash/code_utils/oauth_handler.py

Lines changed: 81 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
666722
def 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

Comments
 (0)