Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: Moving from anyhow to thiserror part 1 #6250

Merged
merged 4 commits into from
Oct 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/turborepo-auth/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ turborepo-api-client = { workspace = true }
turborepo-ui.workspace = true
turborepo-vercel-api = { workspace = true }
turborepo-vercel-api-mock = { workspace = true }
url = { workspace = true }
webbrowser = { workspace = true }

[dev-dependencies]
Expand Down
52 changes: 32 additions & 20 deletions crates/turborepo-auth/src/auth/login.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use std::{borrow::Cow, sync::Arc};

use anyhow::{anyhow, Result};
pub use error::Error;
use reqwest::Url;
use tokio::sync::OnceCell;
Expand All @@ -21,7 +20,7 @@ pub async fn login<'a>(
existing_token: Option<&'a str>,
login_url_configuration: &str,
login_server: &impl LoginServer,
) -> Result<Cow<'a, str>> {
) -> Result<Cow<'a, str>, Error> {
// Check if token exists first.
if let Some(token) = existing_token {
if let Ok(response) = api_client.get_user(token).await {
Expand Down Expand Up @@ -65,12 +64,13 @@ pub async fn login<'a>(

spinner.finish_and_clear();

let token = token_cell
.get()
.ok_or_else(|| anyhow!("Failed to get token"))?;
let token = token_cell.get().ok_or(Error::FailedToGetToken)?;

// TODO: make this a request to /teams endpoint instead?
let user_response = api_client.get_user(token.as_str()).await?;
let user_response = api_client
.get_user(token.as_str())
.await
.map_err(Error::FailedToFetchUser)?;

ui::print_cli_authorized(&user_response.user.email, ui);

Expand All @@ -83,7 +83,7 @@ mod tests {

use async_trait::async_trait;
use reqwest::{Method, RequestBuilder, Response};
use turborepo_api_client::{Client, Error, Result};
use turborepo_api_client::Client;
use turborepo_vercel_api::{
CachingStatusResponse, Membership, PreflightResponse, Role, SpacesResponse, Team,
TeamsResponse, User, UserResponse, VerifiedSsoUser,
Expand All @@ -103,7 +103,7 @@ mod tests {
_: u16,
_: String,
login_token: Arc<OnceCell<String>>,
) -> anyhow::Result<()> {
) -> Result<(), Error> {
self.hits.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
login_token
.set(turborepo_vercel_api_mock::EXPECTED_TOKEN.to_string())
Expand Down Expand Up @@ -144,7 +144,7 @@ mod tests {

#[async_trait]
impl Client for MockApiClient {
async fn get_user(&self, token: &str) -> Result<UserResponse> {
async fn get_user(&self, token: &str) -> turborepo_api_client::Result<UserResponse> {
if token.is_empty() {
return Err(MockApiError::EmptyToken.into());
}
Expand All @@ -159,7 +159,7 @@ mod tests {
},
})
}
async fn get_teams(&self, token: &str) -> Result<TeamsResponse> {
async fn get_teams(&self, token: &str) -> turborepo_api_client::Result<TeamsResponse> {
if token.is_empty() {
return Err(MockApiError::EmptyToken.into());
}
Expand All @@ -175,7 +175,11 @@ mod tests {
}],
})
}
async fn get_team(&self, _token: &str, _team_id: &str) -> Result<Option<Team>> {
async fn get_team(
&self,
_token: &str,
_team_id: &str,
) -> turborepo_api_client::Result<Option<Team>> {
unimplemented!("get_team")
}
fn add_ci_header(_request_builder: RequestBuilder) -> RequestBuilder {
Expand All @@ -193,13 +197,21 @@ mod tests {
_token: &str,
_team_id: &str,
_team_slug: Option<&str>,
) -> Result<CachingStatusResponse> {
) -> turborepo_api_client::Result<CachingStatusResponse> {
unimplemented!("get_caching_status")
}
async fn get_spaces(&self, _token: &str, _team_id: Option<&str>) -> Result<SpacesResponse> {
async fn get_spaces(
&self,
_token: &str,
_team_id: Option<&str>,
) -> turborepo_api_client::Result<SpacesResponse> {
unimplemented!("get_spaces")
}
async fn verify_sso_token(&self, token: &str, _: &str) -> Result<VerifiedSsoUser> {
async fn verify_sso_token(
&self,
token: &str,
_: &str,
) -> turborepo_api_client::Result<VerifiedSsoUser> {
Ok(VerifiedSsoUser {
token: token.to_string(),
team_id: Some("team_id".to_string()),
Expand All @@ -212,10 +224,10 @@ mod tests {
_duration: u64,
_tag: Option<&str>,
_token: &str,
) -> Result<()> {
) -> turborepo_api_client::Result<()> {
unimplemented!("put_artifact")
}
async fn handle_403(_response: Response) -> Error {
async fn handle_403(_response: Response) -> turborepo_api_client::Error {
unimplemented!("handle_403")
}
async fn fetch_artifact(
Expand All @@ -224,7 +236,7 @@ mod tests {
_token: &str,
_team_id: &str,
_team_slug: Option<&str>,
) -> Result<Response> {
) -> turborepo_api_client::Result<Response> {
unimplemented!("fetch_artifact")
}
async fn artifact_exists(
Expand All @@ -233,7 +245,7 @@ mod tests {
_token: &str,
_team_id: &str,
_team_slug: Option<&str>,
) -> Result<Response> {
) -> turborepo_api_client::Result<Response> {
unimplemented!("artifact_exists")
}
async fn get_artifact(
Expand All @@ -243,7 +255,7 @@ mod tests {
_team_id: &str,
_team_slug: Option<&str>,
_method: Method,
) -> Result<Response> {
) -> turborepo_api_client::Result<Response> {
unimplemented!("get_artifact")
}
async fn do_preflight(
Expand All @@ -252,7 +264,7 @@ mod tests {
_request_url: &str,
_request_method: &str,
_request_headers: &str,
) -> Result<PreflightResponse> {
) -> turborepo_api_client::Result<PreflightResponse> {
unimplemented!("do_preflight")
}
fn make_url(&self, endpoint: &str) -> String {
Expand Down
67 changes: 42 additions & 25 deletions crates/turborepo-auth/src/auth/sso.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
use std::{borrow::Cow, sync::Arc};

use anyhow::{anyhow, Context, Result};
use reqwest::Url;
use tokio::sync::OnceCell;
use tracing::warn;
use turborepo_api_client::Client;
use turborepo_ui::{start_spinner, BOLD, UI};

use crate::{error, server, ui};
use crate::{error, server, ui, Error};

const DEFAULT_HOST_NAME: &str = "127.0.0.1";
const DEFAULT_PORT: u16 = 9789;
const DEFAULT_SSO_PROVIDER: &str = "SAML/OIDC Single Sign-On";

fn make_token_name() -> Result<String> {
let host = hostname::get()?;
fn make_token_name() -> Result<String, Error> {
let host = hostname::get().map_err(Error::FailedToMakeSSOTokenName)?;

Ok(format!(
"Turbo CLI on {} via {DEFAULT_SSO_PROVIDER}",
Expand All @@ -31,7 +30,7 @@ pub async fn sso_login<'a>(
login_url_configuration: &str,
sso_team: &str,
login_server: &impl server::SSOLoginServer,
) -> Result<Cow<'a, str>> {
) -> Result<Cow<'a, str>, Error> {
// Check if token exists first. Must be there for the user and contain the
// sso_team passed into this function.
if let Some(token) = existing_token {
Expand Down Expand Up @@ -80,14 +79,19 @@ pub async fn sso_login<'a>(
login_server.run(DEFAULT_PORT, token_cell.clone()).await?;
spinner.finish_and_clear();

let token = token_cell
.get()
.ok_or_else(|| anyhow!("no token auth token found"))?;
let token = token_cell.get().ok_or(Error::FailedToGetToken)?;

let token_name = make_token_name().context("failed to make sso token name")?;
let token_name = make_token_name()?;

let verified_user = api_client.verify_sso_token(token, &token_name).await?;
let user_response = api_client.get_user(&verified_user.token).await?;
let verified_user = api_client
.verify_sso_token(token, &token_name)
.await
.map_err(Error::FailedToValidateSSOToken)?;

let user_response = api_client
.get_user(&verified_user.token)
.await
.map_err(Error::FailedToFetchUser)?;

ui::print_cli_authorized(&user_response.user.email, ui);

Expand All @@ -100,7 +104,7 @@ mod tests {

use async_trait::async_trait;
use reqwest::{Method, RequestBuilder, Response};
use turborepo_api_client::{Client, Error, Result};
use turborepo_api_client::Client;
use turborepo_vercel_api::{
CachingStatusResponse, Membership, PreflightResponse, Role, SpacesResponse, Team,
TeamsResponse, User, UserResponse, VerifiedSsoUser,
Expand Down Expand Up @@ -151,7 +155,7 @@ mod tests {

#[async_trait]
impl Client for MockApiClient {
async fn get_user(&self, token: &str) -> Result<UserResponse> {
async fn get_user(&self, token: &str) -> turborepo_api_client::Result<UserResponse> {
if token.is_empty() {
return Err(MockApiError::EmptyToken.into());
}
Expand All @@ -166,7 +170,7 @@ mod tests {
},
})
}
async fn get_teams(&self, token: &str) -> Result<TeamsResponse> {
async fn get_teams(&self, token: &str) -> turborepo_api_client::Result<TeamsResponse> {
if token.is_empty() {
return Err(MockApiError::EmptyToken.into());
}
Expand All @@ -182,7 +186,11 @@ mod tests {
}],
})
}
async fn get_team(&self, _token: &str, _team_id: &str) -> Result<Option<Team>> {
async fn get_team(
&self,
_token: &str,
_team_id: &str,
) -> turborepo_api_client::Result<Option<Team>> {
unimplemented!("get_team")
}
fn add_ci_header(_request_builder: RequestBuilder) -> RequestBuilder {
Expand All @@ -200,13 +208,21 @@ mod tests {
_token: &str,
_team_id: &str,
_team_slug: Option<&str>,
) -> Result<CachingStatusResponse> {
) -> turborepo_api_client::Result<CachingStatusResponse> {
unimplemented!("get_caching_status")
}
async fn get_spaces(&self, _token: &str, _team_id: Option<&str>) -> Result<SpacesResponse> {
async fn get_spaces(
&self,
_token: &str,
_team_id: Option<&str>,
) -> turborepo_api_client::Result<SpacesResponse> {
unimplemented!("get_spaces")
}
async fn verify_sso_token(&self, token: &str, _: &str) -> Result<VerifiedSsoUser> {
async fn verify_sso_token(
&self,
token: &str,
_: &str,
) -> turborepo_api_client::Result<VerifiedSsoUser> {
Ok(VerifiedSsoUser {
token: token.to_string(),
team_id: Some("team_id".to_string()),
Expand All @@ -219,10 +235,10 @@ mod tests {
_duration: u64,
_tag: Option<&str>,
_token: &str,
) -> Result<()> {
) -> turborepo_api_client::Result<()> {
unimplemented!("put_artifact")
}
async fn handle_403(_response: Response) -> Error {
async fn handle_403(_response: Response) -> turborepo_api_client::Error {
unimplemented!("handle_403")
}
async fn fetch_artifact(
Expand All @@ -231,7 +247,7 @@ mod tests {
_token: &str,
_team_id: &str,
_team_slug: Option<&str>,
) -> Result<Response> {
) -> turborepo_api_client::Result<Response> {
unimplemented!("fetch_artifact")
}
async fn artifact_exists(
Expand All @@ -240,7 +256,7 @@ mod tests {
_token: &str,
_team_id: &str,
_team_slug: Option<&str>,
) -> Result<Response> {
) -> turborepo_api_client::Result<Response> {
unimplemented!("artifact_exists")
}
async fn get_artifact(
Expand All @@ -250,7 +266,7 @@ mod tests {
_team_id: &str,
_team_slug: Option<&str>,
_method: Method,
) -> Result<Response> {
) -> turborepo_api_client::Result<Response> {
unimplemented!("get_artifact")
}
async fn do_preflight(
Expand All @@ -259,14 +275,15 @@ mod tests {
_request_url: &str,
_request_method: &str,
_request_headers: &str,
) -> Result<PreflightResponse> {
) -> turborepo_api_client::Result<PreflightResponse> {
unimplemented!("do_preflight")
}
fn make_url(&self, endpoint: &str) -> String {
format!("{}{}", self.base_url, endpoint)
}
}

#[derive(Clone)]
struct MockSSOLoginServer {
hits: Arc<AtomicUsize>,
}
Expand All @@ -277,7 +294,7 @@ mod tests {
&self,
_port: u16,
verification_token: Arc<OnceCell<String>>,
) -> anyhow::Result<()> {
) -> Result<(), Error> {
self.hits.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
verification_token
.set(EXPECTED_VERIFICATION_TOKEN.to_string())
Expand Down
12 changes: 12 additions & 0 deletions crates/turborepo-auth/src/error.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::io;

use thiserror::Error;

#[derive(Debug, Error)]
Expand All @@ -7,4 +9,14 @@ pub enum Error {
situations like using a `data:` URL."
)]
LoginUrlCannotBeABase { value: String },
#[error("failed to get token")]
FailedToGetToken,
#[error("failed to fetch user: {0}")]
FailedToFetchUser(turborepo_api_client::Error),
#[error("url is invalid: {0}")]
InvalidUrl(#[from] url::ParseError),
#[error("failed to validate sso token")]
FailedToValidateSSOToken(turborepo_api_client::Error),
#[error("failed to make sso token name")]
FailedToMakeSSOTokenName(io::Error),
}
Loading
Loading