@@ -19,7 +19,7 @@ pub const TETRATE_DEFAULT_MODEL: &str = "claude-haiku-4-5";
1919// Auth endpoints are on the main web domain
2020const TETRATE_AUTH_URL : & str = "https://router.tetrate.ai/auth" ;
2121const TETRATE_TOKEN_URL : & str = "https://router.tetrate.ai/api/api-keys/verify" ;
22- const CALLBACK_URL : & str = "http://localhost:3000 " ;
22+ const CALLBACK_BASE : & str = "http://localhost" ;
2323const AUTH_TIMEOUT : Duration = Duration :: from_secs ( 180 ) ; // 3 minutes
2424
2525#[ derive( Debug ) ]
@@ -61,38 +61,16 @@ impl PkceAuthFlow {
6161 } )
6262 }
6363
64- pub fn get_auth_url ( & self ) -> String {
64+ pub fn get_auth_url ( & self , port : u16 ) -> String {
65+ let callback_url = format ! ( "{}:{}" , CALLBACK_BASE , port) ;
6566 format ! (
6667 "{}?callback={}&code_challenge={}" ,
6768 TETRATE_AUTH_URL ,
68- urlencoding:: encode( CALLBACK_URL ) ,
69+ urlencoding:: encode( & callback_url ) ,
6970 urlencoding:: encode( & self . code_challenge)
7071 )
7172 }
7273
73- /// Start local server and wait for callback
74- pub async fn start_server ( & mut self ) -> Result < String > {
75- let ( code_tx, code_rx) = oneshot:: channel :: < String > ( ) ;
76- let ( shutdown_tx, shutdown_rx) = oneshot:: channel :: < ( ) > ( ) ;
77-
78- // Store shutdown sender so we can stop the server later
79- self . server_shutdown_tx = Some ( shutdown_tx) ;
80-
81- // Start the server in a background task
82- tokio:: spawn ( async move {
83- if let Err ( e) = server:: run_callback_server ( code_tx, shutdown_rx) . await {
84- eprintln ! ( "Server error: {}" , e) ;
85- }
86- } ) ;
87-
88- // Wait for the authorization code with timeout
89- match timeout ( AUTH_TIMEOUT , code_rx) . await {
90- Ok ( Ok ( code) ) => Ok ( code) ,
91- Ok ( Err ( _) ) => Err ( anyhow ! ( "Failed to receive authorization code" ) ) ,
92- Err ( _) => Err ( anyhow ! ( "Authentication timeout - please try again" ) ) ,
93- }
94- }
95-
9674 pub async fn exchange_code ( & self , code : String ) -> Result < String > {
9775 let client = Client :: new ( ) ;
9876
@@ -131,9 +109,22 @@ impl PkceAuthFlow {
131109 Ok ( token_response. key )
132110 }
133111
134- /// Complete flow: open browser, wait for callback, exchange code
112+ /// Complete flow: start server, open browser, wait for callback, exchange code
135113 pub async fn complete_flow ( & mut self ) -> Result < String > {
136- let auth_url = self . get_auth_url ( ) ;
114+ let listener = tokio:: net:: TcpListener :: bind ( ( "127.0.0.1" , 0 ) ) . await ?;
115+ let port = listener. local_addr ( ) ?. port ( ) ;
116+
117+ let ( code_tx, code_rx) = oneshot:: channel :: < String > ( ) ;
118+ let ( shutdown_tx, shutdown_rx) = oneshot:: channel :: < ( ) > ( ) ;
119+ self . server_shutdown_tx = Some ( shutdown_tx) ;
120+
121+ tokio:: spawn ( async move {
122+ if let Err ( e) = server:: run_callback_server ( listener, code_tx, shutdown_rx) . await {
123+ eprintln ! ( "Server error: {}" , e) ;
124+ }
125+ } ) ;
126+
127+ let auth_url = self . get_auth_url ( port) ;
137128
138129 println ! ( "Opening browser for Tetrate Agent Router Service authentication..." ) ;
139130 eprintln ! ( "Auth URL: {}" , auth_url) ;
@@ -143,8 +134,13 @@ impl PkceAuthFlow {
143134 println ! ( "Please open this URL manually: {}" , auth_url) ;
144135 }
145136
146- println ! ( "Waiting for authentication callback..." ) ;
147- let code = self . start_server ( ) . await ?;
137+ println ! ( "Waiting for authentication callback on port {}..." , port) ;
138+
139+ let code = match timeout ( AUTH_TIMEOUT , code_rx) . await {
140+ Ok ( Ok ( code) ) => Ok ( code) ,
141+ Ok ( Err ( _) ) => Err ( anyhow ! ( "Failed to receive authorization code" ) ) ,
142+ Err ( _) => Err ( anyhow ! ( "Authentication timeout - please try again" ) ) ,
143+ } ?;
148144
149145 println ! ( "Authorization code received. Exchanging for API key..." ) ;
150146 eprintln ! ( "Received code: {}" , code) ;
0 commit comments