@@ -274,6 +274,16 @@ pub enum TokenType {
274274 IamIdentityCenter ,
275275}
276276
277+ impl From < Option < & str > > for TokenType {
278+ fn from ( start_url : Option < & str > ) -> Self {
279+ match start_url {
280+ Some ( url) if url == START_URL => TokenType :: BuilderId ,
281+ None => TokenType :: BuilderId ,
282+ Some ( _) => TokenType :: IamIdentityCenter ,
283+ }
284+ }
285+ }
286+
277287#[ derive( Debug , Clone , serde:: Serialize , serde:: Deserialize ) ]
278288pub struct BuilderIdToken {
279289 pub access_token : Secret ,
@@ -303,7 +313,10 @@ impl BuilderIdToken {
303313 }
304314
305315 /// Load the token from the keychain, refresh the token if it is expired and return it
306- pub async fn load ( database : & Database ) -> Result < Option < Self > , AuthError > {
316+ pub async fn load (
317+ database : & Database ,
318+ telemetry : Option < & crate :: telemetry:: TelemetryThread > ,
319+ ) -> Result < Option < Self > , AuthError > {
307320 // Can't use #[cfg(test)] without breaking lints, and we don't want to require
308321 // authentication in order to run ChatSession tests. Hence, adding this here with cfg!(test)
309322 if cfg ! ( test) {
@@ -329,7 +342,7 @@ impl BuilderIdToken {
329342
330343 if token. is_expired ( ) {
331344 trace ! ( "token is expired, refreshing" ) ;
332- token. refresh_token ( & client, database, & region) . await
345+ token. refresh_token ( & client, database, & region, telemetry ) . await
333346 } else {
334347 trace ! ( ?token, "found a valid token" ) ;
335348 Ok ( Some ( token) )
@@ -358,6 +371,7 @@ impl BuilderIdToken {
358371 client : & Client ,
359372 database : & Database ,
360373 region : & Region ,
374+ telemetry : Option < & crate :: telemetry:: TelemetryThread > ,
361375 ) -> Result < Option < Self > , AuthError > {
362376 let Some ( refresh_token) = & self . refresh_token else {
363377 warn ! ( "no refresh token was found" ) ;
@@ -417,6 +431,25 @@ impl BuilderIdToken {
417431 let display_err = DisplayErrorContext ( & err) ;
418432 error ! ( "Failed to refresh builder id access token: {}" , display_err) ;
419433
434+ // Send telemetry for refresh failure
435+ if let Some ( telemetry) = telemetry {
436+ let auth_method = match self . token_type ( ) {
437+ TokenType :: BuilderId => "BuilderId" ,
438+ TokenType :: IamIdentityCenter => "IdentityCenter" ,
439+ } ;
440+ let oauth_flow = match self . oauth_flow {
441+ OAuthFlow :: DeviceCode => "DeviceCode" ,
442+ OAuthFlow :: Pkce => "PKCE" ,
443+ } ;
444+ let error_code = match & err {
445+ SdkError :: ServiceError ( service_err) => service_err. err ( ) . meta ( ) . code ( ) . map ( |s| s. to_string ( ) ) ,
446+ _ => None ,
447+ } ;
448+ telemetry
449+ . send_auth_failed ( auth_method, oauth_flow, "TokenRefresh" , error_code)
450+ . ok ( ) ;
451+ }
452+
420453 // if the error is the client's fault, clear the token
421454 if let SdkError :: ServiceError ( service_err) = & err {
422455 if !service_err. err ( ) . is_slow_down_exception ( ) {
@@ -472,11 +505,7 @@ impl BuilderIdToken {
472505 }
473506
474507 pub fn token_type ( & self ) -> TokenType {
475- match & self . start_url {
476- Some ( url) if url == START_URL => TokenType :: BuilderId ,
477- None => TokenType :: BuilderId ,
478- Some ( _) => TokenType :: IamIdentityCenter ,
479- }
508+ TokenType :: from ( self . start_url . as_deref ( ) )
480509 }
481510
482511 /// Check if the token is for the internal amzn start URL (`https://amzn.awsapps.com/start`),
@@ -499,6 +528,7 @@ pub async fn poll_create_token(
499528 device_code : String ,
500529 start_url : Option < String > ,
501530 region : Option < String > ,
531+ telemetry : & crate :: telemetry:: TelemetryThread ,
502532) -> PollCreateToken {
503533 let region = region. clone ( ) . map_or ( OIDC_BUILDER_ID_REGION , Region :: new) ;
504534 let client = client ( region. clone ( ) ) ;
@@ -539,6 +569,20 @@ pub async fn poll_create_token(
539569 } ,
540570 Err ( err) => {
541571 error ! ( ?err, "Failed to poll for builder id token" ) ;
572+
573+ // Send telemetry for device code failure
574+ let auth_method = match TokenType :: from ( start_url. as_deref ( ) ) {
575+ TokenType :: BuilderId => "BuilderId" ,
576+ TokenType :: IamIdentityCenter => "IdentityCenter" ,
577+ } ;
578+ let error_code = match & err {
579+ SdkError :: ServiceError ( service_err) => service_err. err ( ) . meta ( ) . code ( ) . map ( |s| s. to_string ( ) ) ,
580+ _ => None ,
581+ } ;
582+ telemetry
583+ . send_auth_failed ( auth_method, "DeviceCode" , "NewLogin" , error_code)
584+ . ok ( ) ;
585+
542586 PollCreateToken :: Error ( err. into ( ) )
543587 } ,
544588 }
@@ -551,7 +595,7 @@ pub async fn is_logged_in(database: &mut Database) -> bool {
551595 return true ;
552596 }
553597
554- match BuilderIdToken :: load ( database) . await {
598+ match BuilderIdToken :: load ( database, None ) . await {
555599 Ok ( Some ( _) ) => true ,
556600 Ok ( None ) => {
557601 info ! ( "not logged in - no valid token found" ) ;
@@ -586,7 +630,7 @@ pub async fn logout(database: &mut Database) -> Result<(), AuthError> {
586630pub async fn get_start_url_and_region ( database : & Database ) -> ( Option < String > , Option < String > ) {
587631 // NOTE: Database provides direct methods to access the start_url and region, but they are not
588632 // guaranteed to be up to date in the chat session. Example: login is changed mid-chat session.
589- let token = BuilderIdToken :: load ( database) . await ;
633+ let token = BuilderIdToken :: load ( database, None ) . await ;
590634 match token {
591635 Ok ( Some ( t) ) => ( t. start_url , t. region ) ,
592636 _ => ( None , None ) ,
@@ -604,7 +648,7 @@ impl ResolveIdentity for BearerResolver {
604648 ) -> IdentityFuture < ' a > {
605649 IdentityFuture :: new_boxed ( Box :: pin ( async {
606650 let database = Database :: new ( ) . await ?;
607- match BuilderIdToken :: load ( & database) . await ? {
651+ match BuilderIdToken :: load ( & database, None ) . await ? {
608652 Some ( token) => Ok ( Identity :: new (
609653 Token :: new ( token. access_token . 0 . clone ( ) , Some ( token. expires_at . into ( ) ) ) ,
610654 Some ( token. expires_at . into ( ) ) ,
@@ -619,7 +663,7 @@ pub async fn is_idc_user(database: &Database) -> Result<bool> {
619663 if cfg ! ( test) {
620664 return Ok ( false ) ;
621665 }
622- if let Ok ( Some ( token) ) = BuilderIdToken :: load ( database) . await {
666+ if let Ok ( Some ( token) ) = BuilderIdToken :: load ( database, None ) . await {
623667 Ok ( token. token_type ( ) == TokenType :: IamIdentityCenter )
624668 } else {
625669 Err ( eyre ! ( "No auth token found - is the user signed in?" ) )
0 commit comments