@@ -273,85 +273,38 @@ pub async fn login_interactive(args: LoginArgs) -> Result<()> {
273273 ] ) ;
274274
275275 // Try unified portal, fallback to device flow on any error
276- let init = match init_unified_portal ( ) . await {
277- Ok ( v) => v,
278- Err ( err) => {
279- pre_portal_spinner. stop ( ) ;
280- error ! ( %err, "Unified portal init failed" ) ;
281- eprintln ! ( "Failed to initialize auth portal." ) ;
282- eprintln ! ( "Please try again with: q login --use-device-flow" ) ;
283- bail ! ( "Auth portal initialization failed" ) ;
284- } ,
285- } ;
276+ let init = init_unified_portal ( ) . await . map_err ( |err| {
277+ pre_portal_spinner. stop ( ) ;
278+ error ! ( %err, "Unified portal init failed" ) ;
279+ eprintln ! ( "Failed to initialize auth portal.\n Please try again with: kiro-cli login --use-device-flow" ) ;
280+ err
281+ } ) ?;
286282
287- if let Err ( err ) = fig_util:: open_url_async ( & init. auth_url ) . await {
283+ fig_util:: open_url_async ( & init. auth_url ) . await . map_err ( |err| {
288284 pre_portal_spinner. stop ( ) ;
289285 error ! ( %err, "Failed to open portal URL" ) ;
290- eprintln ! ( "Failed to open browser for authentication." ) ;
291- eprintln ! ( "Please try again with: q login --use-device-flow" ) ;
292- bail ! ( "Failed to open auth portal URL" ) ;
293- }
286+ eprintln ! (
287+ "Failed to open browser for authentication.\n Please try again with: kiro-cli login --use-device-flow"
288+ ) ;
289+ err
290+ } ) ?;
294291
295- match finish_unified_portal ( init, & secret_store) . await {
296- Ok ( PortalResult :: Social ( provider) ) => {
292+ match finish_unified_portal ( init, & secret_store) . await ? {
293+ PortalResult :: Social ( provider) => {
297294 pre_portal_spinner. stop_with_message ( format ! ( "Logged in with {}" , provider) ) ;
298- fig_telemetry:: send_user_logged_in ( ) . await ;
299- if let Err ( err) = login_command ( ) . await {
300- error ! ( %err, "Failed to send login command." ) ;
301- }
302295 Some ( AuthMethod :: Social ( provider) )
303296 } ,
304- Ok (
305- PortalResult :: Internal { issuer_url, idc_region }
306- | PortalResult :: BuilderId { issuer_url, idc_region }
307- | PortalResult :: AwsIdc { issuer_url, idc_region } ,
308- ) => {
309- pre_portal_spinner. stop ( ) ;
310-
311- let ( client, registration) =
312- start_pkce_authorization ( Some ( issuer_url. clone ( ) ) , Some ( idc_region. clone ( ) ) ) . await ?;
313-
314- match fig_util:: open_url_async ( & registration. url ) . await {
315- // If it succeeded, finish PKCE.
316- Ok ( ( ) ) => {
317- let mut spinner = Spinner :: new ( vec ! [
318- SpinnerComponent :: Spinner ,
319- SpinnerComponent :: Text ( " Logging in..." . into( ) ) ,
320- ] ) ;
321- let mut ctrl_c_stream = signal ( SignalKind :: interrupt ( ) ) ?;
322- tokio:: select! {
323- res = registration. finish( & client, Some ( & secret_store) ) => res?,
324- Some ( _) = ctrl_c_stream. recv( ) => {
325- #[ allow( clippy:: exit) ]
326- exit( 1 ) ;
327- } ,
328- }
329- fig_telemetry:: send_user_logged_in ( ) . await ;
330- spinner. stop_with_message ( "Device authorized" . into ( ) ) ;
331- } ,
332- // If we are unable to open the link with the browser, then fallback to
333- // the device code flow.
334- Err ( err) => {
335- error ! ( %err, "Failed to open URL with browser, falling back to device code flow" ) ;
336-
337- // Try device code flow.
338- try_device_authorization ( & secret_store, Some ( issuer_url) , Some ( idc_region) ) . await ?;
339- } ,
340- }
297+ PortalResult :: BuilderId { issuer_url, idc_region } => {
298+ pre_portal_spinner. stop_with_message ( "" . into ( ) ) ;
299+ complete_sso_auth ( & secret_store, issuer_url, idc_region) . await ?;
300+ Some ( AuthMethod :: BuilderId )
301+ } ,
341302
303+ PortalResult :: Internal { issuer_url, idc_region } | PortalResult :: AwsIdc { issuer_url, idc_region } => {
304+ pre_portal_spinner. stop_with_message ( "" . into ( ) ) ;
305+ complete_sso_auth ( & secret_store, issuer_url, idc_region) . await ?;
342306 Some ( AuthMethod :: IdentityCenter )
343307 } ,
344- Err ( err) => {
345- pre_portal_spinner. stop ( ) ;
346- error ! ( %err, "Unified portal failed, falling back to device code flow" ) ;
347- try_device_authorization ( & secret_store, None , None ) . await ?;
348- fig_telemetry:: send_user_logged_in ( ) . await ;
349- if let Err ( err) = login_command ( ) . await {
350- error ! ( %err, "Failed to send login command." ) ;
351- }
352- eprintln ! ( "Logged in successfully" ) ;
353- return Ok ( ( ) ) ;
354- } ,
355308 }
356309 } else {
357310 // Remote environment: Use device flow only
@@ -415,6 +368,8 @@ pub async fn login_interactive(args: LoginArgs) -> Result<()> {
415368 Some ( method)
416369 } ;
417370
371+ fig_telemetry:: send_user_logged_in ( ) . await ;
372+
418373 if let Err ( err) = login_command ( ) . await {
419374 error ! ( %err, "Failed to send login command." ) ;
420375 }
@@ -474,7 +429,6 @@ async fn try_device_authorization(
474429 {
475430 PollCreateToken :: Pending => { } ,
476431 PollCreateToken :: Complete ( _) => {
477- fig_telemetry:: send_user_logged_in ( ) . await ;
478432 spinner. stop_with_message ( "Device authorized" . into ( ) ) ;
479433 break ;
480434 } ,
@@ -487,6 +441,42 @@ async fn try_device_authorization(
487441 Ok ( ( ) )
488442}
489443
444+ /// Complete SSO authentication (BuilderID, IdC, or Internal) after portal selection
445+ async fn complete_sso_auth ( secret_store : & SecretStore , issuer_url : String , idc_region : String ) -> Result < ( ) > {
446+ let ( client, registration) = start_pkce_authorization ( Some ( issuer_url. clone ( ) ) , Some ( idc_region. clone ( ) ) ) . await ?;
447+
448+ match fig_util:: open_url_async ( & registration. url ) . await {
449+ Ok ( ( ) ) => {
450+ // Browser opened successfully, wait for PKCE flow to complete
451+ let mut spinner = Spinner :: new ( vec ! [
452+ SpinnerComponent :: Spinner ,
453+ SpinnerComponent :: Text ( " Logging in..." . into( ) ) ,
454+ ] ) ;
455+
456+ let mut ctrl_c_stream = signal ( SignalKind :: interrupt ( ) ) ?;
457+ tokio:: select! {
458+ res = registration. finish( & client, Some ( secret_store) ) => res?,
459+ Some ( _) = ctrl_c_stream. recv( ) => {
460+ #[ allow( clippy:: exit) ]
461+ exit( 1 ) ;
462+ } ,
463+ }
464+
465+ let _ = fig_settings:: state:: set_value ( "auth.idc.start-url" , issuer_url. clone ( ) ) ;
466+ let _ = fig_settings:: state:: set_value ( "auth.idc.region" , idc_region. clone ( ) ) ;
467+
468+ spinner. stop_with_message ( "Device authorized" . into ( ) ) ;
469+ } ,
470+ Err ( err) => {
471+ // Failed to open browser, fallback to device code flow
472+ error ! ( %err, "Failed to open URL, falling back to device code flow" ) ;
473+ try_device_authorization ( secret_store, Some ( issuer_url) , Some ( idc_region) ) . await ?;
474+ } ,
475+ }
476+
477+ Ok ( ( ) )
478+ }
479+
490480async fn select_profile_interactive ( whoami : bool ) -> Result < ( ) > {
491481 let mut spinner = Spinner :: new ( vec ! [
492482 SpinnerComponent :: Spinner ,
0 commit comments