diff --git a/Cargo.lock b/Cargo.lock index 9716a676e2..9afb491ea6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1588,6 +1588,7 @@ dependencies = [ "anyhow", "chrono", "graphql_client", + "houston", "http", "online", "reqwest", diff --git a/crates/houston/src/lib.rs b/crates/houston/src/lib.rs index 22b6a42b27..f220ee2155 100644 --- a/crates/houston/src/lib.rs +++ b/crates/houston/src/lib.rs @@ -11,5 +11,4 @@ pub use error::HoustonProblem; pub use profile::mask_key; /// Utilites for saving, loading, and deleting configuration profiles. -pub use profile::LoadOpts; -pub use profile::Profile; +pub use profile::{Credential, CredentialOrigin, LoadOpts, Profile}; diff --git a/crates/houston/src/profile/mod.rs b/crates/houston/src/profile/mod.rs index 7ff1297322..0e4919368b 100644 --- a/crates/houston/src/profile/mod.rs +++ b/crates/houston/src/profile/mod.rs @@ -26,6 +26,25 @@ pub struct Opts { pub api_key: Option, } +/// Struct containing info about an API Key +pub struct Credential { + /// Apollo API Key + pub api_key: String, + + /// The origin of the credential + pub origin: CredentialOrigin, +} + +/// Info about where the API key was retrieved +#[derive(Debug, Clone, PartialEq)] +pub enum CredentialOrigin { + /// The credential is from an environment variable + EnvVar, + + /// The credential is from a configuration file + ConfigFile(PathBuf), +} + impl Profile { fn base_dir(config: &Config) -> PathBuf { config.home.join("profiles") @@ -50,21 +69,25 @@ impl Profile { /// if it finds it. Otherwise looks for credentials on the file system. /// /// Takes an optional `profile` argument. Defaults to `"default"`. - pub fn get_api_key(name: &str, config: &Config) -> Result { - let api_key: Result = match &config.override_api_key { - Some(api_key) => Ok(api_key.to_string()), + pub fn get_credential(name: &str, config: &Config) -> Result { + let credential = match &config.override_api_key { + Some(api_key) => Credential { + api_key: api_key.to_string(), + origin: CredentialOrigin::EnvVar, + }, None => { let opts = LoadOpts { sensitive: true }; let profile = Profile::load(name, config, opts)?; - Ok(profile.sensitive.api_key) + Credential { + api_key: profile.sensitive.api_key, + origin: CredentialOrigin::ConfigFile(Profile::dir(name, config)), + } } }; - let api_key = api_key?; - - tracing::debug!("using API key {}", mask_key(&api_key)); + tracing::debug!("using API key {}", mask_key(&credential.api_key)); - Ok(api_key) + Ok(credential) } /// Saves configuration options for a specific profile to the file system, diff --git a/crates/houston/tests/auth.rs b/crates/houston/tests/auth.rs index e68a6b6d05..197b0436a4 100644 --- a/crates/houston/tests/auth.rs +++ b/crates/houston/tests/auth.rs @@ -20,8 +20,9 @@ fn it_can_set_and_get_an_api_key_via_creds_file() { assert!(sensitive_file.exists()); assert_eq!( - config::Profile::get_api_key(profile, &config) - .expect("retreiving api key for default profile failed"), + config::Profile::get_credential(profile, &config) + .expect("retreiving api key for default profile failed") + .api_key, String::from(api_key) ); @@ -41,8 +42,9 @@ fn it_can_get_an_api_key_via_env_var() { let config = get_config(Some(api_key.to_string())); assert_eq!( - config::Profile::get_api_key(profile, &config) - .expect("retreiving api key for default profile failed"), + config::Profile::get_credential(profile, &config) + .expect("retreiving api key for default profile failed") + .api_key, String::from(api_key) ); } diff --git a/crates/rover-client/Cargo.toml b/crates/rover-client/Cargo.toml index bb6e133604..5acd9ba6ae 100644 --- a/crates/rover-client/Cargo.toml +++ b/crates/rover-client/Cargo.toml @@ -6,6 +6,11 @@ authors = ["Apollo Developers "] edition = "2018" [dependencies] + +# workspace deps +houston = { path = "../houston" } + +# crates.io deps anyhow = "1" graphql_client = "0.9" http = "0.2" diff --git a/crates/rover-client/src/blocking/studio_client.rs b/crates/rover-client/src/blocking/studio_client.rs index df4ee1c655..988b9485f6 100644 --- a/crates/rover-client/src/blocking/studio_client.rs +++ b/crates/rover-client/src/blocking/studio_client.rs @@ -1,11 +1,13 @@ use crate::blocking::Client; use crate::headers; use crate::RoverClientError; +use houston::Credential; + use graphql_client::GraphQLQuery; /// Represents a client for making GraphQL requests to Apollo Studio. pub struct StudioClient { - api_key: String, + pub credential: Credential, client: reqwest::blocking::Client, uri: String, version: String, @@ -14,9 +16,9 @@ pub struct StudioClient { impl StudioClient { /// Construct a new [StudioClient] from an `api_key`, a `uri`, and a `version`. /// For use in Rover, the `uri` is usually going to be to Apollo Studio - pub fn new(api_key: &str, uri: &str, version: &str) -> StudioClient { + pub fn new(credential: Credential, uri: &str, version: &str) -> StudioClient { StudioClient { - api_key: api_key.to_string(), + credential, client: reqwest::blocking::Client::new(), uri: uri.to_string(), version: version.to_string(), @@ -30,7 +32,7 @@ impl StudioClient { &self, variables: Q::Variables, ) -> Result { - let h = headers::build_studio_headers(&self.api_key, &self.version)?; + let h = headers::build_studio_headers(&self.credential.api_key, &self.version)?; let body = Q::build_query(variables); tracing::trace!(request_headers = ?h); tracing::trace!("Request Body: {}", serde_json::to_string(&body)?); diff --git a/crates/rover-client/src/query/config/whoami.rs b/crates/rover-client/src/query/config/whoami.rs index 20fa59fdbb..238440eb18 100644 --- a/crates/rover-client/src/query/config/whoami.rs +++ b/crates/rover-client/src/query/config/whoami.rs @@ -1,5 +1,7 @@ use crate::blocking::StudioClient; use crate::RoverClientError; +use houston::CredentialOrigin; + use graphql_client::*; #[derive(GraphQLQuery)] @@ -21,6 +23,7 @@ pub struct RegistryIdentity { pub id: String, pub graph_title: Option, pub key_actor_type: Actor, + pub credential_origin: CredentialOrigin, } #[derive(Debug, PartialEq)] @@ -37,11 +40,12 @@ pub fn run( client: &StudioClient, ) -> Result { let response_data = client.post::(variables)?; - get_identity_from_response_data(response_data) + get_identity_from_response_data(response_data, client.credential.origin.clone()) } fn get_identity_from_response_data( response_data: who_am_i_query::ResponseData, + credential_origin: CredentialOrigin, ) -> Result { if let Some(me) = response_data.me { // I believe for the purposes of the CLI, we only care about users and @@ -64,6 +68,7 @@ fn get_identity_from_response_data( id: me.id, graph_title, key_actor_type, + credential_origin, }) } else { Err(RoverClientError::InvalidKey) @@ -87,12 +92,13 @@ mod tests { } }); let data: who_am_i_query::ResponseData = serde_json::from_value(json_response).unwrap(); - let output = get_identity_from_response_data(data); + let output = get_identity_from_response_data(data, CredentialOrigin::EnvVar); let expected_identity = RegistryIdentity { id: "gh.nobodydefinitelyhasthisusernamelol".to_string(), graph_title: None, key_actor_type: Actor::USER, + credential_origin: CredentialOrigin::EnvVar, }; assert!(output.is_ok()); assert_eq!(output.unwrap(), expected_identity); @@ -111,12 +117,13 @@ mod tests { } }); let data: who_am_i_query::ResponseData = serde_json::from_value(json_response).unwrap(); - let output = get_identity_from_response_data(data); + let output = get_identity_from_response_data(data, CredentialOrigin::EnvVar); let expected_identity = RegistryIdentity { id: "big-ol-graph-key-lolol".to_string(), graph_title: Some("GraphKeyService".to_string()), key_actor_type: Actor::GRAPH, + credential_origin: CredentialOrigin::EnvVar, }; assert!(output.is_ok()); assert_eq!(output.unwrap(), expected_identity); diff --git a/src/command/config/auth.rs b/src/command/config/auth.rs index d80a07ffa1..4bf393399f 100644 --- a/src/command/config/auth.rs +++ b/src/command/config/auth.rs @@ -29,7 +29,7 @@ impl Auth { pub fn run(&self, config: config::Config) -> Result { let api_key = api_key_prompt()?; Profile::set_api_key(&self.profile_name, &config, &api_key)?; - Profile::get_api_key(&self.profile_name, &config).map(|_| { + Profile::get_credential(&self.profile_name, &config).map(|_| { eprintln!("Successfully saved API key."); })?; Ok(RoverStdout::None) diff --git a/src/command/config/whoami.rs b/src/command/config/whoami.rs index 2f64794fb3..99645d3daf 100644 --- a/src/command/config/whoami.rs +++ b/src/command/config/whoami.rs @@ -2,11 +2,13 @@ use ansi_term::Colour::Green; use serde::Serialize; use structopt::StructOpt; +use houston::CredentialOrigin; use rover_client::query::config::whoami; use crate::anyhow; use crate::command::RoverStdout; use crate::utils::client::StudioClientConfig; +use crate::utils::env::RoverEnvKey; use crate::Result; #[derive(Debug, Serialize, StructOpt)] @@ -24,28 +26,48 @@ impl WhoAmI { let identity = whoami::run(whoami::who_am_i_query::Variables {}, &client)?; - let message = match identity.key_actor_type { - whoami::Actor::GRAPH => Ok(format!( - "Key Info\n{}: {}\n{}: {}\n{}: {:?}", - Green.normal().paint("Graph Title"), - identity.graph_title.unwrap(), - Green.normal().paint("Unique Graph ID"), - identity.id, - Green.normal().paint("Key Type"), - identity.key_actor_type - )), - whoami::Actor::USER => Ok(format!( - "Key Info\n{}: {}\n{}: {:?}", - Green.normal().paint("User ID"), - identity.id, - Green.normal().paint("Key Type"), - identity.key_actor_type - )), + let mut message = format!( + "{}: {:?}\n", + Green.normal().paint("Key Type"), + identity.key_actor_type + ); + + match identity.key_actor_type { + whoami::Actor::GRAPH => { + if let Some(graph_title) = identity.graph_title { + message.push_str(&format!( + "{}: {}\n", + Green.normal().paint("Graph Title"), + &graph_title + )); + } + message.push_str(&format!( + "{}: {}\n", + Green.normal().paint("Unique Graph ID"), + identity.id + )); + Ok(()) + } + whoami::Actor::USER => { + message.push_str(&format!( + "{}: {}\n", + Green.normal().paint("User ID"), + identity.id + )); + Ok(()) + } _ => Err(anyhow!( "The key provided is invalid. Rover only accepts personal and graph API keys" )), }?; + let origin = match client.credential.origin { + CredentialOrigin::ConfigFile(path) => path.to_string_lossy().to_string(), + CredentialOrigin::EnvVar => format!("${}", &RoverEnvKey::Key), + }; + + message.push_str(&format!("{}: {}", Green.normal().paint("Origin"), &origin)); + eprintln!("{}", message); Ok(RoverStdout::None) diff --git a/src/utils/client.rs b/src/utils/client.rs index 11ddb5e35d..e1f216decf 100644 --- a/src/utils/client.rs +++ b/src/utils/client.rs @@ -31,7 +31,7 @@ impl StudioClientConfig { } pub fn get_client(&self, profile_name: &str) -> Result { - let api_key = config::Profile::get_api_key(profile_name, &self.config)?; - Ok(StudioClient::new(&api_key, &self.uri, &self.version)) + let credential = config::Profile::get_credential(profile_name, &self.config)?; + Ok(StudioClient::new(credential, &self.uri, &self.version)) } }