Skip to content

Commit ebaa5ce

Browse files
committed
Change the logic related to the remote machine.
1 parent b4e87a0 commit ebaa5ce

File tree

1 file changed

+76
-95
lines changed

1 file changed

+76
-95
lines changed

codeflash/code_utils/oauth_handler.py

Lines changed: 76 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from __future__ import annotations
22

33
import base64
4+
import contextlib
45
import hashlib
56
import http.server
67
import json
@@ -27,6 +28,8 @@ def __init__(self) -> None:
2728
self.theme: str | None = None
2829
self.is_complete = False
2930
self.token_error: str | None = None
31+
self.manual_code: str | None = None
32+
self.lock = threading.Lock()
3033

3134
def create_callback_handler(self) -> type[http.server.BaseHTTPRequestHandler]:
3235
"""Create HTTP handler for OAuth callback."""
@@ -57,10 +60,14 @@ def do_GET(self) -> None:
5760
return
5861

5962
params = urllib.parse.parse_qs(parsed.query)
60-
oauth_handler.code = params.get("code", [None])[0]
61-
oauth_handler.state = params.get("state", [None])[0]
62-
oauth_handler.error = params.get("error", [None])[0]
63-
oauth_handler.theme = params.get("theme", ["light"])[0]
63+
64+
with oauth_handler.lock:
65+
if not oauth_handler.is_complete:
66+
oauth_handler.code = params.get("code", [None])[0]
67+
oauth_handler.state = params.get("state", [None])[0]
68+
oauth_handler.error = params.get("error", [None])[0]
69+
oauth_handler.theme = params.get("theme", ["light"])[0]
70+
oauth_handler.is_complete = True
6471

6572
# Send HTML response
6673
self.send_response(200)
@@ -70,8 +77,6 @@ def do_GET(self) -> None:
7077
html_content = self._get_html_response()
7178
self.wfile.write(html_content.encode())
7279

73-
oauth_handler.is_complete = True
74-
7580
def _get_html_response(self) -> str:
7681
"""Return simple HTML response."""
7782
theme = oauth_handler.theme or "light"
@@ -613,15 +618,6 @@ def serve_forever_wrapper() -> None:
613618

614619
return httpd
615620

616-
def wait_for_callback(self, httpd: http.server.HTTPServer, timeout: int = 120) -> bool: # noqa: ARG002
617-
"""Wait for OAuth callback with timeout."""
618-
waited = 0
619-
while not self.is_complete and waited < timeout:
620-
time.sleep(0.5)
621-
waited += 0.5
622-
623-
return self.is_complete
624-
625621
def exchange_code_for_token(self, code: str, code_verifier: str, redirect_uri: str) -> str | None:
626622
"""Exchange authorization code for API token."""
627623
token_url = f"{get_cfapi_base_urls().cfwebapp_base_url}/codeflash/auth/oauth/token"
@@ -648,73 +644,26 @@ def exchange_code_for_token(self, code: str, code_verifier: str, redirect_uri: s
648644
try:
649645
error_data = e.response.json()
650646
error_msg = error_data.get("error_description", error_data.get("error", error_msg))
651-
except Exception: # noqa: S110
652-
pass
653-
self.token_error = "Unauthorized" # noqa: S105
654-
click.echo(f"❌ {self.token_error}")
647+
except Exception:
648+
self.token_error = "Unauthorized" # noqa: S105
655649
return None
656650
except Exception:
657651
self.token_error = "Unauthorized" # noqa: S105
658-
click.echo(f"❌ {self.token_error}")
659652
return None
660653
else:
661654
return api_key
662655

663656

664-
def _handle_local_oauth_flow(
665-
oauth: OAuthHandler,
666-
httpd: http.server.HTTPServer,
667-
state: str,
668-
code_verifier: str,
669-
local_redirect_uri: str,
670-
local_auth_url: str,
671-
) -> str | None:
672-
"""Handle local OAuth flow with browser and server."""
673-
click.echo(f"\n📋 If your browser didn't open, visit: {local_auth_url}\n")
674-
click.echo("⏳ Waiting for authentication...")
675-
676-
success = oauth.wait_for_callback(httpd, timeout=180)
677-
678-
if not success:
679-
httpd.shutdown()
680-
click.echo("❌ Authentication timed out. Please try again.")
681-
return None
682-
683-
if oauth.error or not oauth.code or not oauth.state or oauth.state != state:
684-
httpd.shutdown()
685-
click.echo("❌ Unauthorized.")
686-
return None
687-
688-
api_key = oauth.exchange_code_for_token(oauth.code, code_verifier, local_redirect_uri)
689-
690-
# Wait for browser to poll status
691-
time.sleep(3)
692-
httpd.shutdown()
693-
694-
return api_key
695-
696-
697-
def _handle_remote_oauth_flow(code_verifier: str, remote_redirect_uri: str, remote_auth_url: str) -> str | None:
698-
"""Handle remote OAuth flow with manual code entry."""
699-
oauth = OAuthHandler()
700-
click.echo("⚠️ Browser could not be opened automatically.")
701-
click.echo("\n📋 Please visit this URL to authenticate:")
702-
click.echo(f"\n{remote_auth_url}\n")
703-
704-
# Prompt user to paste the code
705-
code = click.prompt("Paste the authorization code here", type=str).strip()
706-
707-
if not code:
708-
click.echo("❌ No code provided.")
709-
return None
710-
711-
# Exchange code for token
712-
api_key = oauth.exchange_code_for_token(code, code_verifier, remote_redirect_uri)
713-
714-
if api_key:
715-
click.echo("✅ Authentication successful!")
716-
717-
return api_key
657+
def _wait_for_manual_code_input(oauth: OAuthHandler) -> None:
658+
"""Thread function to wait for manual code input."""
659+
try:
660+
code = input()
661+
with oauth.lock:
662+
if not oauth.is_complete:
663+
oauth.manual_code = code.strip()
664+
oauth.is_complete = True
665+
except Exception: # noqa: S110
666+
pass
718667

719668

720669
def perform_oauth_signin() -> str | None:
@@ -733,37 +682,69 @@ def perform_oauth_signin() -> str | None:
733682
local_redirect_uri = f"http://localhost:{port}/callback"
734683
remote_redirect_uri = f"{get_cfapi_base_urls().cfwebapp_base_url}/codeflash/auth/callback"
735684

736-
local_auth_url = (
737-
f"{get_cfapi_base_urls().cfwebapp_base_url}/codeflash/auth?"
685+
base_url = f"{get_cfapi_base_urls().cfwebapp_base_url}/codeflash/auth"
686+
params = (
738687
f"response_type=code"
739688
f"&client_id=cf-cli-app"
740-
f"&redirect_uri={urllib.parse.quote(local_redirect_uri)}"
741689
f"&code_challenge={code_challenge}"
742690
f"&code_challenge_method=sha256"
743691
f"&state={state}"
744692
)
693+
local_auth_url = f"{base_url}?{params}&redirect_uri={urllib.parse.quote(local_redirect_uri)}"
694+
remote_auth_url = f"{base_url}?{params}&redirect_uri={urllib.parse.quote(remote_redirect_uri)}"
745695

746-
remote_auth_url = (
747-
f"{get_cfapi_base_urls().cfwebapp_base_url}/codeflash/auth?"
748-
f"response_type=code"
749-
f"&client_id=cf-cli-app"
750-
f"&redirect_uri={urllib.parse.quote(remote_redirect_uri)}"
751-
f"&code_challenge={code_challenge}"
752-
f"&code_challenge_method=sha256"
753-
f"&state={state}"
754-
)
696+
# Start local server
697+
try:
698+
httpd = oauth.start_local_server(port)
699+
except Exception:
700+
click.echo("❌ Failed to start local server.")
701+
return None
755702

756703
# Try to open browser
757704
click.echo("🌐 Opening browser to sign in to CodeFlash…")
705+
with contextlib.suppress(Exception):
706+
webbrowser.open(local_auth_url)
758707

759-
try:
760-
# Start local server first
761-
httpd = oauth.start_local_server(port)
762-
browser_opened = webbrowser.open(local_auth_url)
763-
except Exception:
764-
browser_opened = False
708+
# Show remote URL and start input thread
709+
click.echo("\n📋 If browser didn't open, visit this URL:")
710+
click.echo(f"\n{remote_auth_url}\n")
711+
click.echo("Paste code here if prompted > ", nl=False)
712+
713+
# Start thread to wait for manual input
714+
input_thread = threading.Thread(target=_wait_for_manual_code_input, args=(oauth,))
715+
input_thread.daemon = True
716+
input_thread.start()
717+
718+
waited = 0
719+
while not oauth.is_complete and waited < 180:
720+
time.sleep(0.5)
721+
waited += 0.5
722+
723+
if not oauth.is_complete:
724+
httpd.shutdown()
725+
click.echo("\n❌ Authentication timed out.")
726+
return None
765727

766-
if browser_opened:
767-
return _handle_local_oauth_flow(oauth, httpd, state, code_verifier, local_redirect_uri, local_auth_url)
728+
# Check which method completed
729+
api_key = None
730+
731+
if oauth.manual_code:
732+
# Manual code was entered
733+
api_key = oauth.exchange_code_for_token(oauth.manual_code, code_verifier, remote_redirect_uri)
734+
elif oauth.code:
735+
# Browser callback received
736+
if oauth.error or not oauth.state or oauth.state != state:
737+
httpd.shutdown()
738+
click.echo("\n❌ Unauthorized.")
739+
return None
740+
741+
api_key = oauth.exchange_code_for_token(oauth.code, code_verifier, local_redirect_uri)
742+
743+
# Cleanup
744+
time.sleep(3)
768745
httpd.shutdown()
769-
return _handle_remote_oauth_flow(code_verifier, remote_redirect_uri, remote_auth_url)
746+
747+
if not api_key:
748+
click.echo("❌ Authentication failed.")
749+
750+
return api_key

0 commit comments

Comments
 (0)