@@ -10,23 +10,14 @@ use std::collections::HashMap;
1010use std:: fs;
1111use std:: io:: { self , BufRead , BufReader , IsTerminal , Write } ;
1212use std:: net:: { TcpListener , TcpStream } ;
13- use std:: path:: Path ;
14- use std:: process:: { Command , Stdio } ;
1513use std:: thread;
16- use tokio:: { signal, sync:: mpsc} ;
17-
18- #[ cfg( target_os = "macos" ) ]
19- const CLIPBOARD_COMMANDS : & [ ( & str , & [ & str ] ) ] = & [ ( "pbcopy" , & [ ] ) ] ;
20-
21- #[ cfg( target_os = "windows" ) ]
22- const CLIPBOARD_COMMANDS : & [ ( & str , & [ & str ] ) ] = & [ ( "cmd" , & [ "/C" , "clip" ] ) ] ;
14+ use tokio:: {
15+ signal,
16+ sync:: mpsc,
17+ time:: { sleep, Duration } ,
18+ } ;
2319
24- #[ cfg( all( unix, not( target_os = "macos" ) ) ) ]
25- const CLIPBOARD_COMMANDS : & [ ( & str , & [ & str ] ) ] = & [
26- ( "wl-copy" , & [ ] ) ,
27- ( "xclip" , & [ "-selection" , "clipboard" ] ) ,
28- ( "xsel" , & [ "--clipboard" , "--input" ] ) ,
29- ] ;
20+ const AUTH_CODE_PROMPT_DELAY : Duration = Duration :: from_secs ( 3 ) ;
3021
3122#[ derive( Serialize , Deserialize ) ]
3223struct Credentials {
@@ -188,74 +179,13 @@ fn pkce_challenge_from_verifier(verifier: &str) -> String {
188179 URL_SAFE_NO_PAD . encode ( hash. as_ref ( ) )
189180}
190181
191- fn command_exists ( program : & str ) -> bool {
192- let path = Path :: new ( program) ;
193- if path. is_absolute ( ) {
194- return path. is_file ( ) ;
195- }
196-
197- std:: env:: var_os ( "PATH" )
198- . map ( |paths| std:: env:: split_paths ( & paths) . any ( |dir| dir. join ( program) . is_file ( ) ) )
199- . unwrap_or ( false )
200- }
201-
202- fn can_copy_to_clipboard ( ) -> bool {
203- #[ cfg( target_os = "windows" ) ]
204- {
205- true
206- }
207- #[ cfg( not( target_os = "windows" ) ) ]
208- {
209- CLIPBOARD_COMMANDS
210- . iter ( )
211- . any ( |( program, _) | command_exists ( program) )
212- }
213- }
214-
215- fn copy_to_clipboard ( text : & str ) -> Result < ( ) > {
216- let mut last_error = None ;
217- for ( program, args) in CLIPBOARD_COMMANDS {
218- let mut child = match Command :: new ( program)
219- . args ( * args)
220- . stdin ( Stdio :: piped ( ) )
221- . spawn ( )
222- {
223- Ok ( child) => child,
224- Err ( error) => {
225- last_error = Some ( error) ;
226- continue ;
227- }
228- } ;
229-
230- let mut stdin = child
231- . stdin
232- . take ( )
233- . ok_or_else ( || anyhow:: anyhow!( "Failed to open clipboard command stdin" ) ) ?;
234- stdin. write_all ( text. as_bytes ( ) ) ?;
235- drop ( stdin) ;
236-
237- if child. wait ( ) ?. success ( ) {
238- return Ok ( ( ) ) ;
239- }
240- }
241-
242- match last_error {
243- Some ( error) => Err ( error. into ( ) ) ,
244- None => bail ! ( "No clipboard command available" ) ,
245- }
246- }
247-
248182fn print_opening_browser ( ) {
249183 eprintln ! ( "Opening browser to sign in…" ) ;
250184}
251185
252- fn print_auth_url ( auth_url : & str , can_copy_url : bool ) {
186+ fn print_auth_url ( auth_url : & str ) {
253187 eprintln ! ( ) ;
254- if can_copy_url {
255- eprintln ! ( "Browser didn't open? Use the url below to sign in (c to copy)" ) ;
256- } else {
257- eprintln ! ( "Browser didn't open? Use the url below to sign in" ) ;
258- }
188+ eprintln ! ( "Browser didn't open? Use the url below to sign in" ) ;
259189 eprintln ! ( ) ;
260190 eprintln ! ( "{auth_url}" ) ;
261191 eprintln ! ( ) ;
@@ -379,15 +309,9 @@ fn start_callback_server(
379309 Ok ( callback_url)
380310}
381311
382- fn read_auth_code (
383- stdin_is_terminal : bool ,
384- auth_url : & str ,
385- can_copy_url : bool ,
386- ) -> Result < Option < String > > {
387- if stdin_is_terminal {
388- eprint ! ( "Paste code here if prompted > " ) ;
389- let _ = io:: stderr ( ) . flush ( ) ;
390- }
312+ fn read_auth_code ( ) -> Result < Option < String > > {
313+ eprint ! ( "Paste code here if prompted > " ) ;
314+ let _ = io:: stderr ( ) . flush ( ) ;
391315
392316 let mut input = String :: new ( ) ;
393317 let bytes_read = io:: stdin ( ) . lock ( ) . read_line ( & mut input) ?;
@@ -397,30 +321,15 @@ fn read_auth_code(
397321
398322 let code = input. trim ( ) . to_string ( ) ;
399323 if code. is_empty ( ) {
400- if stdin_is_terminal {
401- return Ok ( None ) ;
402- }
403- bail ! ( "Authentication cancelled" ) ;
404- }
405- if stdin_is_terminal && can_copy_url && code. eq_ignore_ascii_case ( "c" ) {
406- match copy_to_clipboard ( auth_url) {
407- Ok ( ( ) ) => eprintln ! ( "Copied sign-in URL to clipboard." ) ,
408- Err ( error) => eprintln ! ( "Couldn't copy sign-in URL: {error}" ) ,
409- }
410324 return Ok ( None ) ;
411325 }
412326
413327 Ok ( Some ( code) )
414328}
415329
416- fn spawn_auth_code_reader (
417- stdin_is_terminal : bool ,
418- auth_url : String ,
419- can_copy_url : bool ,
420- tx : mpsc:: UnboundedSender < AuthInput > ,
421- ) {
330+ fn spawn_auth_code_reader ( tx : mpsc:: UnboundedSender < AuthInput > ) {
422331 thread:: spawn ( move || loop {
423- match read_auth_code ( stdin_is_terminal , & auth_url , can_copy_url ) {
332+ match read_auth_code ( ) {
424333 Ok ( Some ( code) ) => {
425334 let _ = tx. send ( AuthInput :: Code {
426335 code,
@@ -443,8 +352,6 @@ async fn handle_auth_input(
443352 state : & str ,
444353 code_verifier : & str ,
445354 stdin_is_terminal : bool ,
446- manual_auth_url : & str ,
447- can_copy_url : bool ,
448355 tx : & mpsc:: UnboundedSender < AuthInput > ,
449356 success_url : & str ,
450357) -> Result < Option < LoginResult > > {
@@ -465,12 +372,7 @@ async fn handle_auth_input(
465372 }
466373 Err ( error) if matches ! ( source, AuthCodeSource :: Prompt ) && stdin_is_terminal => {
467374 eprintln ! ( "Invalid auth code: {error}" ) ;
468- spawn_auth_code_reader (
469- stdin_is_terminal,
470- manual_auth_url. to_string ( ) ,
471- can_copy_url,
472- tx. clone ( ) ,
473- ) ;
375+ spawn_auth_code_reader ( tx. clone ( ) ) ;
474376 Ok ( None )
475377 }
476378 Err ( error) if matches ! ( source, AuthCodeSource :: Callback ) => {
@@ -494,16 +396,6 @@ async fn handle_auth_input(
494396 }
495397}
496398
497- async fn recv_auth_input ( rx : & mut mpsc:: UnboundedReceiver < AuthInput > ) -> Result < Option < AuthInput > > {
498- tokio:: select! {
499- input = rx. recv( ) => Ok ( input) ,
500- result = signal:: ctrl_c( ) => {
501- result?;
502- bail!( "Authentication cancelled" )
503- }
504- }
505- }
506-
507399async fn redeem_auth_code ( code : String , state : & str , code_verifier : & str ) -> Result < LoginResult > {
508400 let api_host = config:: get ( "api_host" ) ?;
509401 let url = format ! ( "{}/cli/auth/redeem" , api_host) ;
@@ -549,38 +441,40 @@ pub async fn login_with_browser() -> Result<LoginResult> {
549441 let success_url = format ! ( "{website_host}/auth/cli?success=true" ) ;
550442
551443 let stdin_is_terminal = io:: stdin ( ) . is_terminal ( ) ;
552- let can_copy_url = stdin_is_terminal && can_copy_to_clipboard ( ) ;
553444 print_opening_browser ( ) ;
554- print_auth_url ( & manual_auth_url, can_copy_url) ;
555- if stdin_is_terminal {
556- spawn_auth_code_reader (
557- stdin_is_terminal,
558- manual_auth_url. clone ( ) ,
559- can_copy_url,
560- tx. clone ( ) ,
561- ) ;
562- }
563-
445+ print_auth_url ( & manual_auth_url) ;
564446 let _ = open:: that ( & auto_auth_url) ;
447+ let mut auth_code_prompt_started = false ;
565448
566- while let Some ( input) = recv_auth_input ( & mut rx) . await ? {
567- if let Some ( result) = handle_auth_input (
568- input,
569- & state,
570- & code_verifier,
571- stdin_is_terminal,
572- & manual_auth_url,
573- can_copy_url,
574- & tx,
575- & success_url,
576- )
577- . await ?
578- {
579- return Ok ( result) ;
449+ loop {
450+ tokio:: select! {
451+ input = rx. recv( ) => {
452+ let Some ( input) = input else {
453+ bail!( "Authentication cancelled" ) ;
454+ } ;
455+ if let Some ( result) = handle_auth_input(
456+ input,
457+ & state,
458+ & code_verifier,
459+ stdin_is_terminal,
460+ & tx,
461+ & success_url,
462+ )
463+ . await ?
464+ {
465+ return Ok ( result) ;
466+ }
467+ }
468+ result = signal:: ctrl_c( ) => {
469+ result?;
470+ bail!( "Authentication cancelled" )
471+ }
472+ _ = sleep( AUTH_CODE_PROMPT_DELAY ) , if stdin_is_terminal && !auth_code_prompt_started => {
473+ spawn_auth_code_reader( tx. clone( ) ) ;
474+ auth_code_prompt_started = true ;
475+ }
580476 }
581477 }
582-
583- bail ! ( "Authentication cancelled" )
584478}
585479
586480#[ cfg( test) ]
0 commit comments