@@ -273,6 +273,16 @@ pub enum TokenType {
273273 IamIdentityCenter ,
274274}
275275
276+ impl From < Option < & str > > for TokenType {
277+ fn from ( start_url : Option < & str > ) -> Self {
278+ match start_url {
279+ Some ( url) if url == START_URL => TokenType :: BuilderId ,
280+ None => TokenType :: BuilderId ,
281+ Some ( _) => TokenType :: IamIdentityCenter ,
282+ }
283+ }
284+ }
285+
276286#[ derive( Debug , Clone , serde:: Serialize , serde:: Deserialize ) ]
277287pub struct BuilderIdToken {
278288 pub access_token : Secret ,
@@ -302,7 +312,10 @@ impl BuilderIdToken {
302312 }
303313
304314 /// Load the token from the keychain, refresh the token if it is expired and return it
305- pub async fn load ( database : & Database ) -> Result < Option < Self > , AuthError > {
315+ pub async fn load (
316+ database : & Database ,
317+ telemetry : Option < & crate :: telemetry:: TelemetryThread > ,
318+ ) -> Result < Option < Self > , AuthError > {
306319 // Can't use #[cfg(test)] without breaking lints, and we don't want to require
307320 // authentication in order to run ChatSession tests. Hence, adding this here with cfg!(test)
308321 if cfg ! ( test) {
@@ -328,7 +341,7 @@ impl BuilderIdToken {
328341
329342 if token. is_expired ( ) {
330343 trace ! ( "token is expired, refreshing" ) ;
331- token. refresh_token ( & client, database, & region) . await
344+ token. refresh_token ( & client, database, & region, telemetry ) . await
332345 } else {
333346 trace ! ( ?token, "found a valid token" ) ;
334347 Ok ( Some ( token) )
@@ -357,6 +370,7 @@ impl BuilderIdToken {
357370 client : & Client ,
358371 database : & Database ,
359372 region : & Region ,
373+ telemetry : Option < & crate :: telemetry:: TelemetryThread > ,
360374 ) -> Result < Option < Self > , AuthError > {
361375 let Some ( refresh_token) = & self . refresh_token else {
362376 warn ! ( "no refresh token was found" ) ;
@@ -416,6 +430,25 @@ impl BuilderIdToken {
416430 let display_err = DisplayErrorContext ( & err) ;
417431 error ! ( "Failed to refresh builder id access token: {}" , display_err) ;
418432
433+ // Send telemetry for refresh failure
434+ if let Some ( telemetry) = telemetry {
435+ let auth_method = match self . token_type ( ) {
436+ TokenType :: BuilderId => "BuilderId" ,
437+ TokenType :: IamIdentityCenter => "IdentityCenter" ,
438+ } ;
439+ let oauth_flow = match self . oauth_flow {
440+ OAuthFlow :: DeviceCode => "DeviceCode" ,
441+ OAuthFlow :: Pkce => "PKCE" ,
442+ } ;
443+ let error_code = match & err {
444+ SdkError :: ServiceError ( service_err) => service_err. err ( ) . meta ( ) . code ( ) . map ( |s| s. to_string ( ) ) ,
445+ _ => None ,
446+ } ;
447+ telemetry
448+ . send_auth_failed ( auth_method, oauth_flow, "TokenRefresh" , error_code)
449+ . ok ( ) ;
450+ }
451+
419452 // if the error is the client's fault, clear the token
420453 if let SdkError :: ServiceError ( service_err) = & err {
421454 if !service_err. err ( ) . is_slow_down_exception ( ) {
@@ -471,11 +504,7 @@ impl BuilderIdToken {
471504 }
472505
473506 pub fn token_type ( & self ) -> TokenType {
474- match & self . start_url {
475- Some ( url) if url == START_URL => TokenType :: BuilderId ,
476- None => TokenType :: BuilderId ,
477- Some ( _) => TokenType :: IamIdentityCenter ,
478- }
507+ TokenType :: from ( self . start_url . as_deref ( ) )
479508 }
480509
481510 /// Check if the token is for the internal amzn start URL (`https://amzn.awsapps.com/start`),
@@ -498,6 +527,7 @@ pub async fn poll_create_token(
498527 device_code : String ,
499528 start_url : Option < String > ,
500529 region : Option < String > ,
530+ telemetry : & crate :: telemetry:: TelemetryThread ,
501531) -> PollCreateToken {
502532 let region = region. clone ( ) . map_or ( OIDC_BUILDER_ID_REGION , Region :: new) ;
503533 let client = client ( region. clone ( ) ) ;
@@ -538,6 +568,20 @@ pub async fn poll_create_token(
538568 } ,
539569 Err ( err) => {
540570 error ! ( ?err, "Failed to poll for builder id token" ) ;
571+
572+ // Send telemetry for device code failure
573+ let auth_method = match TokenType :: from ( start_url. as_deref ( ) ) {
574+ TokenType :: BuilderId => "BuilderId" ,
575+ TokenType :: IamIdentityCenter => "IdentityCenter" ,
576+ } ;
577+ let error_code = match & err {
578+ SdkError :: ServiceError ( service_err) => service_err. err ( ) . meta ( ) . code ( ) . map ( |s| s. to_string ( ) ) ,
579+ _ => None ,
580+ } ;
581+ telemetry
582+ . send_auth_failed ( auth_method, "DeviceCode" , "NewLogin" , error_code)
583+ . ok ( ) ;
584+
541585 PollCreateToken :: Error ( err. into ( ) )
542586 } ,
543587 }
@@ -550,7 +594,7 @@ pub async fn is_logged_in(database: &mut Database) -> bool {
550594 return true ;
551595 }
552596
553- match BuilderIdToken :: load ( database) . await {
597+ match BuilderIdToken :: load ( database, None ) . await {
554598 Ok ( Some ( _) ) => true ,
555599 Ok ( None ) => {
556600 info ! ( "not logged in - no valid token found" ) ;
@@ -585,7 +629,7 @@ pub async fn logout(database: &mut Database) -> Result<(), AuthError> {
585629pub async fn get_start_url_and_region ( database : & Database ) -> ( Option < String > , Option < String > ) {
586630 // NOTE: Database provides direct methods to access the start_url and region, but they are not
587631 // guaranteed to be up to date in the chat session. Example: login is changed mid-chat session.
588- let token = BuilderIdToken :: load ( database) . await ;
632+ let token = BuilderIdToken :: load ( database, None ) . await ;
589633 match token {
590634 Ok ( Some ( t) ) => ( t. start_url , t. region ) ,
591635 _ => ( None , None ) ,
@@ -603,7 +647,7 @@ impl ResolveIdentity for BearerResolver {
603647 ) -> IdentityFuture < ' a > {
604648 IdentityFuture :: new_boxed ( Box :: pin ( async {
605649 let database = Database :: new ( ) . await ?;
606- match BuilderIdToken :: load ( & database) . await ? {
650+ match BuilderIdToken :: load ( & database, None ) . await ? {
607651 Some ( token) => Ok ( Identity :: new (
608652 Token :: new ( token. access_token . 0 . clone ( ) , Some ( token. expires_at . into ( ) ) ) ,
609653 Some ( token. expires_at . into ( ) ) ,
@@ -618,7 +662,7 @@ pub async fn is_idc_user(database: &Database) -> Result<bool> {
618662 if cfg ! ( test) {
619663 return Ok ( false ) ;
620664 }
621- if let Ok ( Some ( token) ) = BuilderIdToken :: load ( database) . await {
665+ if let Ok ( Some ( token) ) = BuilderIdToken :: load ( database, None ) . await {
622666 Ok ( token. token_type ( ) == TokenType :: IamIdentityCenter )
623667 } else {
624668 Err ( eyre ! ( "No auth token found - is the user signed in?" ) )
0 commit comments