From 7ca80a3e1641002657713d9baf22eb9dc35b0487 Mon Sep 17 00:00:00 2001 From: Anthony Shew Date: Wed, 12 Feb 2025 13:16:06 -0700 Subject: [PATCH 1/5] feat: better error when not enough scopes for SSO login --- crates/turborepo-auth/src/lib.rs | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/crates/turborepo-auth/src/lib.rs b/crates/turborepo-auth/src/lib.rs index 2bb6652653cbf..48bd4f3245869 100644 --- a/crates/turborepo-auth/src/lib.rs +++ b/crates/turborepo-auth/src/lib.rs @@ -158,7 +158,33 @@ impl Token { Ok(true) } - (Err(e), _) | (_, Err(e)) => Err(Error::APIError(e)), + (Err(e), _) | (_, Err(e)) => match e { + turborepo_api_client::Error::ReqwestError(e) => { + if e.status() == Some(reqwest::StatusCode::FORBIDDEN) { + let metadata = self.fetch_metadata(client).await?; + if !metadata.token_type.is_empty() { + return Err(Error::APIError( + turborepo_api_client::Error::InvalidToken { + status: e + .status() + .unwrap_or(reqwest::StatusCode::FORBIDDEN) + .as_u16(), + url: e + .url() + .map(|u| u.to_string()) + .unwrap_or("Unknown url".to_string()), + message: e.to_string(), + }, + )); + } + } + + Err(Error::APIError(turborepo_api_client::Error::ReqwestError( + e, + ))) + } + e => Err(Error::APIError(e)), + }, } } From 3e3627d18d08aab62da0eb93752411d0f944140e Mon Sep 17 00:00:00 2001 From: Anthony Shew Date: Wed, 12 Feb 2025 14:23:29 -0700 Subject: [PATCH 2/5] WIP --- crates/turborepo-auth/src/lib.rs | 50 ++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/crates/turborepo-auth/src/lib.rs b/crates/turborepo-auth/src/lib.rs index 48bd4f3245869..4b0c5765460e9 100644 --- a/crates/turborepo-auth/src/lib.rs +++ b/crates/turborepo-auth/src/lib.rs @@ -111,6 +111,33 @@ impl Token { Ok(true) } + async fn handle_sso_token_error( + &self, + client: &T, + error: reqwest::Error, + ) -> Result { + if error.status() == Some(reqwest::StatusCode::FORBIDDEN) { + let metadata = self.fetch_metadata(client).await?; + if !metadata.token_type.is_empty() { + return Err(Error::APIError(turborepo_api_client::Error::InvalidToken { + status: error + .status() + .unwrap_or(reqwest::StatusCode::FORBIDDEN) + .as_u16(), + url: error + .url() + .map(|u| u.to_string()) + .unwrap_or("Unknown url".to_string()), + message: error.to_string(), + })); + } + } + + Err(Error::APIError(turborepo_api_client::Error::ReqwestError( + error, + ))) + } + /// This is the same as `is_valid`, but also checks if the token is valid /// for SSO. /// @@ -160,28 +187,7 @@ impl Token { } (Err(e), _) | (_, Err(e)) => match e { turborepo_api_client::Error::ReqwestError(e) => { - if e.status() == Some(reqwest::StatusCode::FORBIDDEN) { - let metadata = self.fetch_metadata(client).await?; - if !metadata.token_type.is_empty() { - return Err(Error::APIError( - turborepo_api_client::Error::InvalidToken { - status: e - .status() - .unwrap_or(reqwest::StatusCode::FORBIDDEN) - .as_u16(), - url: e - .url() - .map(|u| u.to_string()) - .unwrap_or("Unknown url".to_string()), - message: e.to_string(), - }, - )); - } - } - - Err(Error::APIError(turborepo_api_client::Error::ReqwestError( - e, - ))) + self.handle_sso_token_error(client, e).await } e => Err(Error::APIError(e)), }, From f5342e653e336c9daa9de3be8cee2a2ed8350a30 Mon Sep 17 00:00:00 2001 From: Anthony Shew Date: Thu, 13 Feb 2025 17:17:11 -0700 Subject: [PATCH 3/5] Update crates/turborepo-auth/src/lib.rs Co-authored-by: Chris Olszewski --- crates/turborepo-auth/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/turborepo-auth/src/lib.rs b/crates/turborepo-auth/src/lib.rs index 4b0c5765460e9..40edba2c3918b 100644 --- a/crates/turborepo-auth/src/lib.rs +++ b/crates/turborepo-auth/src/lib.rs @@ -111,7 +111,7 @@ impl Token { Ok(true) } - async fn handle_sso_token_error( + async fn handle_sso_token_error( &self, client: &T, error: reqwest::Error, From 8093c842a7690e6bbe7e07ab0a5ec9bbdbebc4c5 Mon Sep 17 00:00:00 2001 From: Anthony Shew Date: Thu, 13 Feb 2025 20:10:08 -0700 Subject: [PATCH 4/5] WIP --- Cargo.lock | 2 + crates/turborepo-auth/Cargo.toml | 2 + crates/turborepo-auth/src/lib.rs | 120 +++++++++++++++++++++++++++++++ 3 files changed, 124 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index e357084659f7e..fb16c0e1f59cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6314,6 +6314,8 @@ dependencies = [ "axum-server 0.7.1", "chrono", "hostname", + "http 1.1.0", + "httpmock", "lazy_static", "port_scanner", "reqwest", diff --git a/crates/turborepo-auth/Cargo.toml b/crates/turborepo-auth/Cargo.toml index 477a04ab73664..649968ab84e93 100644 --- a/crates/turborepo-auth/Cargo.toml +++ b/crates/turborepo-auth/Cargo.toml @@ -14,6 +14,8 @@ axum-server = { workspace = true } axum.workspace = true chrono.workspace = true hostname = "0.3.1" +http = "1.1.0" +httpmock = { workspace = true } lazy_static.workspace = true reqwest.workspace = true serde = { workspace = true, features = ["derive"] } diff --git a/crates/turborepo-auth/src/lib.rs b/crates/turborepo-auth/src/lib.rs index 40edba2c3918b..a6fb75905ce6e 100644 --- a/crates/turborepo-auth/src/lib.rs +++ b/crates/turborepo-auth/src/lib.rs @@ -707,4 +707,124 @@ mod tests { let result = Token::from_file(&file_path); assert!(matches!(result, Err(Error::TokenNotFound))); } + + struct MockSSOTokenClient { + metadata_response: Option, + } + + impl TokenClient for MockSSOTokenClient { + async fn get_metadata( + &self, + _token: &str, + ) -> turborepo_api_client::Result { + if let Some(metadata) = &self.metadata_response { + Ok(metadata.clone()) + } else { + Ok(ResponseTokenMetadata { + id: "test".to_string(), + name: "test".to_string(), + token_type: "".to_string(), + origin: "test".to_string(), + scopes: vec![], + active_at: current_unix_time() - 100, + created_at: 0, + }) + } + } + + async fn delete_token(&self, _token: &str) -> turborepo_api_client::Result<()> { + Ok(()) + } + } + + #[tokio::test] + async fn test_handle_sso_token_error_forbidden_with_invalid_token_error() { + let token = Token::new("test-token".to_string()); + let client = MockSSOTokenClient { + metadata_response: Some(ResponseTokenMetadata { + id: "test".to_string(), + name: "test".to_string(), + token_type: "sso".to_string(), + origin: "test".to_string(), + scopes: vec![], + active_at: current_unix_time() - 100, + created_at: 0, + }), + }; + + let errorful_response = reqwest::Response::from( + http::Response::builder() + .status(reqwest::StatusCode::FORBIDDEN) + .body("") + .unwrap(), + ); + + let result = token + .handle_sso_token_error(&client, errorful_response.error_for_status().unwrap_err()) + .await; + assert!(matches!( + result, + Err(Error::APIError( + turborepo_api_client::Error::InvalidToken { .. } + )) + )); + } + + #[tokio::test] + async fn test_handle_sso_token_error_forbidden_without_token_type() { + let token = Token::new("test-token".to_string()); + let client = MockSSOTokenClient { + metadata_response: Some(ResponseTokenMetadata { + id: "test".to_string(), + name: "test".to_string(), + token_type: "".to_string(), + origin: "test".to_string(), + scopes: vec![], + active_at: current_unix_time() - 100, + created_at: 0, + }), + }; + + let errorful_response = reqwest::Response::from( + http::Response::builder() + .status(reqwest::StatusCode::FORBIDDEN) + .body("") + .unwrap(), + ); + + let result = token + .handle_sso_token_error(&client, errorful_response.error_for_status().unwrap_err()) + .await; + assert!(matches!( + result, + Err(Error::APIError(turborepo_api_client::Error::ReqwestError( + _ + ))) + )); + } + + #[tokio::test] + async fn test_handle_sso_token_error_non_forbidden() { + let token = Token::new("test-token".to_string()); + let client = MockSSOTokenClient { + metadata_response: None, + }; + + let errorful_response = reqwest::Response::from( + http::Response::builder() + .status(reqwest::StatusCode::INTERNAL_SERVER_ERROR) + .body("") + .unwrap(), + ); + + let result = token + .handle_sso_token_error(&client, errorful_response.error_for_status().unwrap_err()) + .await; + assert!(matches!( + result, + Err(Error::APIError(turborepo_api_client::Error::ReqwestError( + _ + ))) + )); + } } From d17d1dd3c0c66ef9b465f7e1fb1c3aa7d3cf5dc9 Mon Sep 17 00:00:00 2001 From: Anthony Shew Date: Fri, 14 Feb 2025 15:43:01 -0700 Subject: [PATCH 5/5] WIP --- crates/turborepo-auth/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/turborepo-auth/Cargo.toml b/crates/turborepo-auth/Cargo.toml index 649968ab84e93..6f91a19def06a 100644 --- a/crates/turborepo-auth/Cargo.toml +++ b/crates/turborepo-auth/Cargo.toml @@ -14,8 +14,6 @@ axum-server = { workspace = true } axum.workspace = true chrono.workspace = true hostname = "0.3.1" -http = "1.1.0" -httpmock = { workspace = true } lazy_static.workspace = true reqwest.workspace = true serde = { workspace = true, features = ["derive"] } @@ -34,4 +32,6 @@ url = { workspace = true } webbrowser = { workspace = true } [dev-dependencies] +http = "1.1.0" +httpmock = { workspace = true } port_scanner = { workspace = true }