Skip to content

Commit f9c676b

Browse files
committed
chore: add telemetry on auth failure
1 parent f59b224 commit f9c676b

File tree

13 files changed

+270
-68
lines changed

13 files changed

+270
-68
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/aws-toolkit-telemetry-definitions/def.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,21 @@
125125
"type": "string",
126126
"description": "The oauth authentication flow executed by the user, e.g. device code or PKCE"
127127
},
128+
{
129+
"name": "codewhispererterminal_authMethod",
130+
"type": "string",
131+
"description": "The authentication method used, e.g. BuilderId or IdentityCenter"
132+
},
133+
{
134+
"name": "codewhispererterminal_errorType",
135+
"type": "string",
136+
"description": "The type of error, currently only used for authentication errors: TokenRefresh or NewLogin"
137+
},
138+
{
139+
"name": "codewhispererterminal_errorCode",
140+
"type": "string",
141+
"description": "The specific error code from the backend service"
142+
},
128143
{
129144
"name": "result",
130145
"type": "string",
@@ -244,6 +259,19 @@
244259
{ "type": "codewhispererterminal_inCloudshell" }
245260
]
246261
},
262+
{
263+
"name": "codewhispererterminal_authFailed",
264+
"description": "Emitted when authentication fails",
265+
"passive": false,
266+
"metadata": [
267+
{ "type": "credentialStartUrl" },
268+
{ "type": "codewhispererterminal_inCloudshell" },
269+
{ "type": "codewhispererterminal_authMethod" },
270+
{ "type": "oauthFlow" },
271+
{ "type": "codewhispererterminal_errorType" },
272+
{ "type": "codewhispererterminal_errorCode", "required": false }
273+
]
274+
},
247275
{
248276
"name": "codewhispererterminal_refreshCredentials",
249277
"description": "Emitted when users refresh their credentials",

crates/fig_auth/src/builder_id.rs

Lines changed: 58 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,16 @@ use aws_types::request_id::RequestId;
4545
use aws_types::sdk_config::StalledStreamProtectionConfig;
4646
use fig_aws_common::app_name;
4747
use fig_telemetry_core::{
48+
AuthErrorType,
4849
Event,
4950
EventType,
5051
TelemetryResult,
5152
};
53+
use fig_util::auth::{
54+
OAuthFlow,
55+
START_URL,
56+
TokenType,
57+
};
5258
use time::OffsetDateTime;
5359
use tracing::{
5460
debug,
@@ -69,22 +75,6 @@ use crate::{
6975
Result,
7076
};
7177

72-
#[derive(Debug, Copy, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
73-
pub enum OAuthFlow {
74-
DeviceCode,
75-
#[serde(alias = "Pkce")]
76-
PKCE,
77-
}
78-
79-
impl std::fmt::Display for OAuthFlow {
80-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81-
match *self {
82-
OAuthFlow::DeviceCode => write!(f, "DeviceCode"),
83-
OAuthFlow::PKCE => write!(f, "PKCE"),
84-
}
85-
}
86-
}
87-
8878
/// Indicates if an expiration time has passed, there is a small 1 min window that is removed
8979
/// so the token will not expire in transit
9080
fn is_expired(expiration_time: &OffsetDateTime) -> bool {
@@ -254,30 +244,42 @@ pub async fn start_device_authorization(
254244
..
255245
} = DeviceRegistration::init_device_code_registration(&client, secret_store, &region).await?;
256246

247+
let start_url = start_url.as_deref().unwrap_or(START_URL);
248+
257249
let output = client
258250
.start_device_authorization()
259251
.client_id(&client_id)
260252
.client_secret(&client_secret.0)
261-
.start_url(start_url.as_deref().unwrap_or(START_URL))
253+
.start_url(start_url)
262254
.send()
263-
.await?;
264-
265-
Ok(StartDeviceAuthorizationResponse {
266-
device_code: output.device_code.unwrap_or_default(),
267-
user_code: output.user_code.unwrap_or_default(),
268-
verification_uri: output.verification_uri.unwrap_or_default(),
269-
verification_uri_complete: output.verification_uri_complete.unwrap_or_default(),
270-
expires_in: output.expires_in,
271-
interval: output.interval,
272-
region: region.to_string(),
273-
start_url: start_url.unwrap_or_else(|| START_URL.to_owned()),
274-
})
275-
}
276-
277-
#[derive(Debug, Clone, PartialEq, Eq)]
278-
pub enum TokenType {
279-
BuilderId,
280-
IamIdentityCenter,
255+
.await;
256+
257+
match output {
258+
Ok(output) => Ok(StartDeviceAuthorizationResponse {
259+
device_code: output.device_code.unwrap_or_default(),
260+
user_code: output.user_code.unwrap_or_default(),
261+
verification_uri: output.verification_uri.unwrap_or_default(),
262+
verification_uri_complete: output.verification_uri_complete.unwrap_or_default(),
263+
expires_in: output.expires_in,
264+
interval: output.interval,
265+
region: region.to_string(),
266+
start_url: start_url.to_string(),
267+
}),
268+
Err(err) => {
269+
let err: Error = err.into();
270+
fig_telemetry_core::send_event(
271+
Event::new(EventType::AuthFailed {
272+
auth_method: TokenType::from_start_url(Some(start_url)),
273+
oauth_flow: OAuthFlow::DeviceCode,
274+
error_type: AuthErrorType::NewLogin,
275+
error_code: err.service_error_code(),
276+
})
277+
.with_credential_start_url(start_url.to_string()),
278+
)
279+
.await;
280+
Err(err)
281+
},
282+
}
281283
}
282284

283285
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
@@ -512,6 +514,27 @@ pub async fn poll_create_token(
512514
device_code: String,
513515
start_url: Option<String>,
514516
region: Option<String>,
517+
) -> PollCreateToken {
518+
match poll_create_token_impl(secret_store, device_code, start_url.clone(), region).await {
519+
PollCreateToken::Error(err) => {
520+
fig_telemetry_core::send_event(Event::new(EventType::AuthFailed {
521+
auth_method: TokenType::from_start_url(start_url.as_deref()),
522+
oauth_flow: OAuthFlow::DeviceCode,
523+
error_type: AuthErrorType::NewLogin,
524+
error_code: err.service_error_code(),
525+
}))
526+
.await;
527+
PollCreateToken::Error(err)
528+
},
529+
other => other,
530+
}
531+
}
532+
533+
async fn poll_create_token_impl(
534+
secret_store: &SecretStore,
535+
device_code: String,
536+
start_url: Option<String>,
537+
region: Option<String>,
515538
) -> PollCreateToken {
516539
let region = region.clone().map_or(OIDC_BUILDER_ID_REGION, Region::new);
517540
let client = client(region.clone());
@@ -634,23 +657,6 @@ mod tests {
634657
const US_EAST_1: Region = Region::from_static("us-east-1");
635658
const US_WEST_2: Region = Region::from_static("us-west-2");
636659

637-
macro_rules! test_ser_deser {
638-
($ty:ident, $variant:expr, $text:expr) => {
639-
let quoted = format!("\"{}\"", $text);
640-
assert_eq!(quoted, serde_json::to_string(&$variant).unwrap());
641-
assert_eq!($variant, serde_json::from_str(&quoted).unwrap());
642-
643-
assert_eq!($text, format!("{}", $variant));
644-
};
645-
}
646-
647-
#[test]
648-
fn test_oauth_flow_ser_deser() {
649-
test_ser_deser!(OAuthFlow, OAuthFlow::DeviceCode, "DeviceCode");
650-
test_ser_deser!(OAuthFlow, OAuthFlow::PKCE, "PKCE");
651-
assert_eq!(OAuthFlow::PKCE, serde_json::from_str("\"Pkce\"").unwrap());
652-
}
653-
654660
#[test]
655661
fn test_client() {
656662
println!("{:?}", client(US_EAST_1));

crates/fig_auth/src/consts.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,6 @@ pub(crate) const SCOPES: &[&str] = &[
1818

1919
pub(crate) const CLIENT_TYPE: &str = "public";
2020

21-
// The start URL for public builder ID users
22-
pub const START_URL: &str = "https://view.awsapps.com/start";
23-
2421
// The start URL for internal amzn users
2522
pub const AMZN_START_URL: &str = "https://amzn.awsapps.com/start";
2623

crates/fig_auth/src/error.rs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ use thiserror::Error;
1010
#[allow(variant_size_differences)]
1111
#[derive(Debug, Error)]
1212
pub enum Error {
13-
#[error(transparent)]
14-
Ssooidc(#[from] Box<aws_sdk_ssooidc::Error>),
1513
#[error(transparent)]
1614
SdkRegisterClient(#[from] SdkError<RegisterClientError>),
1715
#[error(transparent)]
@@ -53,13 +51,27 @@ pub enum Error {
5351
impl Error {
5452
pub fn to_verbose_string(&self) -> String {
5553
match self {
56-
Error::Ssooidc(s) => DisplayErrorContext(s).to_string(),
5754
Error::SdkRegisterClient(s) => DisplayErrorContext(s).to_string(),
5855
Error::SdkCreateToken(s) => DisplayErrorContext(s).to_string(),
5956
Error::SdkStartDeviceAuthorization(s) => DisplayErrorContext(s).to_string(),
6057
other => other.to_string(),
6158
}
6259
}
60+
61+
pub fn service_error_code(&self) -> Option<String> {
62+
match self {
63+
Error::SdkRegisterClient(err) => err
64+
.as_service_error()
65+
.and_then(|e| e.meta().code().map(|s| s.to_string())),
66+
Error::SdkCreateToken(err) => err
67+
.as_service_error()
68+
.and_then(|e| e.meta().code().map(|s| s.to_string())),
69+
Error::SdkStartDeviceAuthorization(err) => err
70+
.as_service_error()
71+
.and_then(|e| e.meta().code().map(|s| s.to_string())),
72+
_ => None,
73+
}
74+
}
6375
}
6476

6577
pub(crate) type Result<T, E = Error> = std::result::Result<T, E>;

crates/fig_auth/src/lib.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,6 @@ pub use builder_id::{
1212
logout,
1313
refresh_token,
1414
};
15-
pub use consts::{
16-
AMZN_START_URL,
17-
START_URL,
18-
};
15+
pub use consts::AMZN_START_URL;
1916
pub use error::Error;
2017
pub(crate) use error::Result;

crates/fig_auth/src/pkce.rs

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,16 @@ pub use aws_types::region::Region;
3030
use base64::Engine;
3131
use base64::engine::general_purpose::URL_SAFE;
3232
use bytes::Bytes;
33+
use fig_telemetry_core::{
34+
AuthErrorType,
35+
Event,
36+
EventType,
37+
};
38+
use fig_util::auth::{
39+
OAuthFlow,
40+
START_URL,
41+
TokenType,
42+
};
3343
use http_body_util::Full;
3444
use hyper::body::Incoming;
3545
use hyper::server::conn::http1;
@@ -56,7 +66,6 @@ use crate::secret_store::SecretStore;
5666
use crate::{
5767
Error,
5868
Result,
59-
START_URL,
6069
};
6170

6271
const DEFAULT_AUTHORIZATION_TIMEOUT: Duration = Duration::from_secs(60 * 3);
@@ -70,8 +79,22 @@ pub async fn start_pkce_authorization(
7079
let issuer_url = start_url.as_deref().unwrap_or(START_URL);
7180
let region = region.clone().map_or(OIDC_BUILDER_ID_REGION, Region::new);
7281
let client = client(region.clone());
73-
let registration = PkceRegistration::register(&client, region, issuer_url.to_string(), None).await?;
74-
Ok((client, registration))
82+
match PkceRegistration::register(&client, region, issuer_url.to_string(), None).await {
83+
Ok(registration) => Ok((client, registration)),
84+
Err(err) => {
85+
fig_telemetry_core::send_event(
86+
Event::new(EventType::AuthFailed {
87+
auth_method: TokenType::from_start_url(Some(issuer_url)),
88+
oauth_flow: OAuthFlow::PKCE,
89+
error_type: AuthErrorType::NewLogin,
90+
error_code: err.service_error_code(),
91+
})
92+
.with_credential_start_url(issuer_url.to_string()),
93+
)
94+
.await;
95+
Err(err)
96+
},
97+
}
7598
}
7699

77100
/// Represents a client used for registering with AWS IAM OIDC.
@@ -242,11 +265,28 @@ impl PkceRegistration {
242265
code_verifier: self.code_verifier,
243266
code,
244267
})
245-
.await?;
268+
.await;
246269

247270
// Tokens are redacted in the log output.
248271
debug!(?response, "Received create_token response");
249272

273+
let response = match response {
274+
Ok(res) => res,
275+
Err(err) => {
276+
fig_telemetry_core::send_event(
277+
Event::new(EventType::AuthFailed {
278+
auth_method: TokenType::from_start_url(Some(&self.issuer_url)),
279+
oauth_flow: OAuthFlow::PKCE,
280+
error_type: AuthErrorType::NewLogin,
281+
error_code: err.service_error_code(),
282+
})
283+
.with_credential_start_url(self.issuer_url),
284+
)
285+
.await;
286+
return Err(err);
287+
},
288+
};
289+
250290
let token = BuilderIdToken::from_output(
251291
response.output,
252292
self.region.clone(),

crates/fig_desktop_api/src/requests/auth.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ use std::sync::{
66
use fig_auth::builder_id::{
77
PollCreateToken,
88
StartDeviceAuthorizationResponse,
9-
TokenType,
109
};
1110
use fig_auth::pkce::{
1211
Client,
@@ -31,6 +30,7 @@ use fig_proto::fig::{
3130
AuthStatusRequest,
3231
AuthStatusResponse,
3332
};
33+
use fig_util::auth::TokenType;
3434
use tokio::sync::Mutex;
3535
use tokio::sync::mpsc::{
3636
Receiver,

crates/fig_telemetry_core/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,6 @@ fig_util.workspace = true
1616
serde.workspace = true
1717
strum.workspace = true
1818
tokio.workspace = true
19+
20+
[dev-dependencies]
21+
serde_json.workspace = true

0 commit comments

Comments
 (0)