From 5aca3d96ae74616edf8419b47ba3bdcf24508abb Mon Sep 17 00:00:00 2001 From: David Haynes Date: Thu, 16 Apr 2020 14:07:24 -0700 Subject: [PATCH 1/3] Add support for printing api token email on `whoami` - query the same api endpoint as we do with the global token, but now with the api token - return errors, print helpful statement if the api token doesn't have the right permission Closes #863 --- src/commands/whoami/mod.rs | 41 ++++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/src/commands/whoami/mod.rs b/src/commands/whoami/mod.rs index 741ed9480..7a1956ec8 100644 --- a/src/commands/whoami/mod.rs +++ b/src/commands/whoami/mod.rs @@ -1,19 +1,32 @@ use crate::http; use crate::settings::global_user::GlobalUser; -use crate::terminal::emoji; +use crate::terminal::{emoji, message}; use cloudflare::endpoints::account::{self, Account}; +use cloudflare::endpoints::user::GetUserDetails; use cloudflare::framework::apiclient::ApiClient; +use cloudflare::framework::response::ApiFailure; use prettytable::{Cell, Row, Table}; pub fn whoami(user: &GlobalUser) -> Result<(), failure::Error> { - // If using email + API key for auth, simply prints out email from config file. + // Attempt to print email for both GlobalKeyAuth and TokenAuth users let auth: String = match user { GlobalUser::GlobalKeyAuth { email, .. } => { format!("a Global API Key, associated with the email '{}'", email,) } - GlobalUser::TokenAuth { .. } => "an API Token".to_string(), + GlobalUser::TokenAuth { .. } => { + let token_auth_email: String = fetch_api_token_email(user)?; + + if token_auth_email == "" { + "an API Token".to_string() + } else { + format!( + "an API Token, associated with the email '{}'", + token_auth_email, + ) + } + } }; println!("\n{} You are logged in with {}.\n", emoji::WAVING, auth,); @@ -23,6 +36,26 @@ pub fn whoami(user: &GlobalUser) -> Result<(), failure::Error> { Ok(()) } +fn fetch_api_token_email(user: &GlobalUser) -> Result { + let client = http::cf_v4_client(user)?; + let response = client.request(&GetUserDetails {}); + match response { + Ok(res) => Ok(res.result.email), + Err(e) => match e { + ApiFailure::Error(_, api_errors) => { + let error = &api_errors.errors[0]; + if error.code == 9109 { + message::info("Your token is missing the 'User Details: Read' permission.\n\nPlease generate and auth with a new token that has these perms to be able to identify this token.\n"); + Ok("".to_string()) + } else { + Ok("".to_string()) + } + } + ApiFailure::Invalid(_) => failure::bail!(http::format_error(e, None)), + }, + } +} + fn fetch_accounts(user: &GlobalUser) -> Result, failure::Error> { let client = http::cf_v4_client(user)?; let response = client.request(&account::ListAccounts { params: None }); @@ -39,7 +72,7 @@ fn format_accounts(user: &GlobalUser, accounts: Vec) -> Table { if let GlobalUser::TokenAuth { .. } = user { if accounts.is_empty() { - println!("Your token is missing the 'Account Settings: Read' permission.\n\nPlease generate and auth with a new token that has these perms to be able to list your accounts.\n"); + message::info("Your token is missing the 'Account Settings: Read' permission.\n\nPlease generate and auth with a new token that has these perms to be able to list your accounts.\n"); } } From 7d0649f21379ab6fed9d63931d181ad6f6dc0f4c Mon Sep 17 00:00:00 2001 From: David Haynes Date: Mon, 20 Apr 2020 11:48:23 -0700 Subject: [PATCH 2/3] Update to use idiomatic Rust error handling - headspace was in go lang with using "" for handling errors :P --- src/commands/whoami/mod.rs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/commands/whoami/mod.rs b/src/commands/whoami/mod.rs index 7a1956ec8..4f0a23af3 100644 --- a/src/commands/whoami/mod.rs +++ b/src/commands/whoami/mod.rs @@ -16,15 +16,15 @@ pub fn whoami(user: &GlobalUser) -> Result<(), failure::Error> { format!("a Global API Key, associated with the email '{}'", email,) } GlobalUser::TokenAuth { .. } => { - let token_auth_email: String = fetch_api_token_email(user)?; + let token_auth_email = fetch_api_token_email(user)?; - if token_auth_email == "" { - "an API Token".to_string() - } else { + if let Some(token_auth_email) = token_auth_email { format!( "an API Token, associated with the email '{}'", token_auth_email, ) + } else { + "an API Token".to_string() } } }; @@ -36,20 +36,18 @@ pub fn whoami(user: &GlobalUser) -> Result<(), failure::Error> { Ok(()) } -fn fetch_api_token_email(user: &GlobalUser) -> Result { +fn fetch_api_token_email(user: &GlobalUser) -> Result, failure::Error> { let client = http::cf_v4_client(user)?; let response = client.request(&GetUserDetails {}); match response { - Ok(res) => Ok(res.result.email), + Ok(res) => Ok(Some(res.result.email)), Err(e) => match e { ApiFailure::Error(_, api_errors) => { let error = &api_errors.errors[0]; if error.code == 9109 { - message::info("Your token is missing the 'User Details: Read' permission.\n\nPlease generate and auth with a new token that has these perms to be able to identify this token.\n"); - Ok("".to_string()) - } else { - Ok("".to_string()) + message::billboard("Your token is missing the 'User Details: Read' permission.\n\nPlease generate and auth with a new token that has these perms to be able to identify this token.\n"); } + Ok(None) } ApiFailure::Invalid(_) => failure::bail!(http::format_error(e, None)), }, @@ -72,7 +70,7 @@ fn format_accounts(user: &GlobalUser, accounts: Vec) -> Table { if let GlobalUser::TokenAuth { .. } = user { if accounts.is_empty() { - message::info("Your token is missing the 'Account Settings: Read' permission.\n\nPlease generate and auth with a new token that has these perms to be able to list your accounts.\n"); + message::billboard("Your token is missing the 'Account Settings: Read' permission.\n\nPlease generate and auth with a new token that has these perms to be able to list your accounts."); } } From fcf11fb6e86632604dc4d580dcce1453c762ab90 Mon Sep 17 00:00:00 2001 From: Avery Harnish Date: Mon, 20 Apr 2020 14:51:56 -0500 Subject: [PATCH 3/3] smush whoami info into one billboard --- src/commands/whoami/mod.rs | 47 +++++++++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/src/commands/whoami/mod.rs b/src/commands/whoami/mod.rs index 4f0a23af3..3473356d9 100644 --- a/src/commands/whoami/mod.rs +++ b/src/commands/whoami/mod.rs @@ -7,16 +7,18 @@ use cloudflare::endpoints::user::GetUserDetails; use cloudflare::framework::apiclient::ApiClient; use cloudflare::framework::response::ApiFailure; +use console::Style; use prettytable::{Cell, Row, Table}; pub fn whoami(user: &GlobalUser) -> Result<(), failure::Error> { + let mut missing_permissions: Vec = Vec::with_capacity(2); // Attempt to print email for both GlobalKeyAuth and TokenAuth users let auth: String = match user { GlobalUser::GlobalKeyAuth { email, .. } => { format!("a Global API Key, associated with the email '{}'", email,) } GlobalUser::TokenAuth { .. } => { - let token_auth_email = fetch_api_token_email(user)?; + let token_auth_email = fetch_api_token_email(user, &mut missing_permissions)?; if let Some(token_auth_email) = token_auth_email { format!( @@ -29,14 +31,39 @@ pub fn whoami(user: &GlobalUser) -> Result<(), failure::Error> { } }; - println!("\n{} You are logged in with {}.\n", emoji::WAVING, auth,); let accounts = fetch_accounts(user)?; - let table = format_accounts(user, accounts); - println!("{}", &table); + let table = format_accounts(user, accounts, &mut missing_permissions); + let mut msg = format!("{} You are logged in with {}!\n", emoji::WAVING, auth); + let num_permissions_missing = missing_permissions.len(); + if num_permissions_missing > 0 { + let bold_yellow = Style::new().bold().yellow(); + let config_msg = bold_yellow.apply_to("`wrangler config`"); + let whoami_msg = bold_yellow.apply_to("`wrangler whoami`"); + if missing_permissions.len() == 1 { + msg.push_str(&format!( + "\nYour token is missing the '{}' permission.", + bold_yellow.apply_to(missing_permissions.get(0).unwrap()) + )); + } else if missing_permissions.len() == 2 { + msg.push_str(&format!( + "\nYour token is missing the '{}' and '{}' permissions.", + bold_yellow.apply_to(missing_permissions.get(0).unwrap()), + bold_yellow.apply_to(missing_permissions.get(1).unwrap()) + )); + } + msg.push_str(&format!("\n\nPlease generate a new token and authenticate with {}\nfor more information when running {}", config_msg, whoami_msg)); + } + message::billboard(&msg); + if table.len() > 1 { + println!("{}", &table); + } Ok(()) } -fn fetch_api_token_email(user: &GlobalUser) -> Result, failure::Error> { +fn fetch_api_token_email( + user: &GlobalUser, + missing_permissions: &mut Vec, +) -> Result, failure::Error> { let client = http::cf_v4_client(user)?; let response = client.request(&GetUserDetails {}); match response { @@ -45,7 +72,7 @@ fn fetch_api_token_email(user: &GlobalUser) -> Result, failure::E ApiFailure::Error(_, api_errors) => { let error = &api_errors.errors[0]; if error.code == 9109 { - message::billboard("Your token is missing the 'User Details: Read' permission.\n\nPlease generate and auth with a new token that has these perms to be able to identify this token.\n"); + missing_permissions.push("User Details: Read".to_string()); } Ok(None) } @@ -63,14 +90,18 @@ fn fetch_accounts(user: &GlobalUser) -> Result, failure::Error> { } } -fn format_accounts(user: &GlobalUser, accounts: Vec) -> Table { +fn format_accounts( + user: &GlobalUser, + accounts: Vec, + missing_permissions: &mut Vec, +) -> Table { let mut table = Table::new(); let table_head = Row::new(vec![Cell::new("Account Name"), Cell::new("Account ID")]); table.add_row(table_head); if let GlobalUser::TokenAuth { .. } = user { if accounts.is_empty() { - message::billboard("Your token is missing the 'Account Settings: Read' permission.\n\nPlease generate and auth with a new token that has these perms to be able to list your accounts."); + missing_permissions.push("Account Settings: Read".to_string()); } }