Skip to content

Commit

Permalink
refactor!: big rewrite (#118)
Browse files Browse the repository at this point in the history
Closes #106
Closes #107
Closes #117
Closes #119
Closes #121
  • Loading branch information
beeb committed Aug 5, 2024
1 parent e123379 commit eb20e25
Show file tree
Hide file tree
Showing 16 changed files with 2,473 additions and 2,135 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ test/*
!emptyfile
!emptyfile2
test_push_sensitive
test_push_skip_sensitive
test_push_skip_sensitive
.soldeer/
26 changes: 7 additions & 19 deletions Cargo.lock

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

22 changes: 11 additions & 11 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,35 +13,35 @@ repository = "https://github.com/mario-eth/soldeer"
version = "0.2.19"

[dependencies]
chrono = {version = "0.4.38", default-features = false, features = [
chrono = { version = "0.4.38", default-features = false, features = [
"std",
"serde",
]}
clap = {version = "4.5.9", features = ["derive"]}
] }
clap = { version = "4.5.9", features = ["derive"] }
email-address-parser = "2.0.0"
futures = "0.3.30"
once_cell = "1.19"
regex = "1.10.5"
reqwest = {version = "0.12.5", features = [
reqwest = { version = "0.12.5", features = [
"blocking",
"json",
"multipart",
"stream",
], default-features = false}
], default-features = false }
rpassword = "7.3.1"
serde = "1.0.204"
serde_derive = "1.0.204"
serde_json = "1.0.120"
sha256 = "1.5.0"
simple-home-dir = "0.3.5"
tokio = {version = "1.38.0", features = ["rt-multi-thread", "macros"]}
toml = "0.8.14"
toml_edit = "0.22.15"
uuid = {version = "1.10.0", features = ["serde", "v4"]}
simple-home-dir = "0.4.0"
thiserror = "1.0.63"
tokio = { version = "1.38.0", features = ["rt-multi-thread", "macros"] }
toml_edit = { version = "0.22.15", features = ["serde"] }
uuid = { version = "1.10.0", features = ["serde", "v4"] }
walkdir = "2.5.0"
yansi = "1.0.1"
yash-fnmatch = "1.1.1"
zip = {version = "2.1.3", default-features = false, features = ["deflate"]}
zip = { version = "2.1.3", default-features = false, features = ["deflate"] }
zip-extract = "0.1.3"

[dev-dependencies]
Expand Down
127 changes: 54 additions & 73 deletions src/auth.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
use crate::{
errors::LoginError,
errors::AuthError,
utils::{define_security_file_location, get_base_url, read_file},
};
use email_address_parser::{EmailAddress, ParsingOptions};
use reqwest::Client;
use reqwest::{Client, StatusCode};
use rpassword::read_password;
use serde_derive::{Deserialize, Serialize};
use std::{
fs::OpenOptions,
io::{self, Write},
};
use yansi::Paint;
use yansi::Paint as _;

pub type Result<T> = std::result::Result<T, AuthError>;

#[derive(Debug, Serialize, Deserialize)]
pub struct Login {
Expand All @@ -24,12 +26,12 @@ pub struct LoginResponse {
pub token: String,
}

pub async fn login() -> Result<(), LoginError> {
pub async fn login() -> Result<()> {
print!("ℹ️ If you do not have an account, please go to soldeer.xyz to create one.\n📧 Please enter your email: ");
std::io::stdout().flush().unwrap();
let mut email = String::new();
if io::stdin().read_line(&mut email).is_err() {
return Err(LoginError { cause: "Invalid email".to_string() });
return Err(AuthError::InvalidEmail);
}
email = match check_email(email) {
Ok(e) => e,
Expand All @@ -46,40 +48,40 @@ pub async fn login() -> Result<(), LoginError> {
Ok(())
}

pub fn get_token() -> Result<String, LoginError> {
let security_file = define_security_file_location();
pub fn get_token() -> Result<String> {
let security_file = define_security_file_location()?;
let jwt = read_file(security_file);
match jwt {
Ok(token) => Ok(String::from_utf8(token)
.expect("You are not logged in. Please login using the 'soldeer login' command")),
Err(_) => Err(LoginError {
cause: "You are not logged in. Please login using the 'login' command".to_string(),
}),
Err(_) => Err(AuthError::MissingToken),
}
}

fn check_email(email_str: String) -> Result<String, LoginError> {
fn check_email(email_str: String) -> Result<String> {
let email_str = email_str.trim().to_string().to_ascii_lowercase();

let email: Option<EmailAddress> =
EmailAddress::parse(&email_str, Some(ParsingOptions::default()));
if email.is_none() {
Err(LoginError { cause: "Invalid email".to_string() })
Err(AuthError::InvalidEmail)
} else {
Ok(email_str)
}
}

async fn execute_login(login: Login) -> Result<(), LoginError> {
async fn execute_login(login: Login) -> Result<()> {
let url = format!("{}/api/v1/auth/login", get_base_url());
let req = Client::new().post(url).json(&login);

let login_response = req.send().await;

let security_file = define_security_file_location();
if let Ok(response) = login_response {
if response.status().is_success() {
println!("{}", Paint::green("Login successful"));
let security_file = define_security_file_location()?;
let response = login_response?;

match response.status() {
s if s.is_success() => {
println!("{}", "Login successful".green());
let jwt = serde_json::from_str::<LoginResponse>(&response.text().await.unwrap())
.unwrap()
.token;
Expand All @@ -90,32 +92,13 @@ async fn execute_login(login: Login) -> Result<(), LoginError> {
.append(false)
.open(&security_file)
.unwrap();
if let Err(err) = write!(file, "{}", &jwt) {
return Err(LoginError {
cause: format!(
"Couldn't write to the security file {}: {}",
&security_file, err
),
});
}
println!("{}", Paint::green(&format!("Login details saved in: {:?}", &security_file)));

return Ok(());
} else if response.status().as_u16() == 401 {
return Err(LoginError {
cause: "Authentication failed. Invalid email or password".to_string(),
});
} else {
return Err(LoginError {
cause: format!(
"Authentication failed. Server response: {}",
response.status().as_u16()
),
});
write!(file, "{}", &jwt)?;
println!("{}", format!("Login details saved in: {:?}", &security_file).green());
Ok(())
}
StatusCode::UNAUTHORIZED => Err(AuthError::InvalidCredentials),
_ => Err(AuthError::HttpError(response.error_for_status().unwrap_err())),
}

Err(LoginError { cause: format!("Authentication failed. Unknown error.{:?}", login_response) })
}

#[cfg(test)]
Expand All @@ -133,8 +116,7 @@ mod tests {

assert_eq!(check_email(valid_email.clone()).unwrap(), valid_email);

let expected_error = LoginError { cause: "Invalid email".to_string() };
assert_eq!(check_email(invalid_email).err().unwrap(), expected_error);
assert!(matches!(check_email(invalid_email), Err(AuthError::InvalidEmail)));
}

#[tokio::test]
Expand All @@ -148,7 +130,10 @@ mod tests {

// Request a new server from the pool
let mut server = mockito::Server::new_async().await;
env::set_var("base_url", format!("http://{}", server.host_with_port()));
unsafe {
// became unsafe in Rust 1.80
env::set_var("base_url", format!("http://{}", server.host_with_port()));
}

// Create a mock
let _ = server
Expand Down Expand Up @@ -179,7 +164,10 @@ mod tests {
#[serial]
async fn login_401() {
let mut server = mockito::Server::new_async().await;
env::set_var("base_url", format!("http://{}", server.host_with_port()));
unsafe {
// became unsafe in Rust 1.80
env::set_var("base_url", format!("http://{}", server.host_with_port()));
}

let data = r#"
{
Expand All @@ -193,27 +181,25 @@ mod tests {
.with_body(data)
.create();

match execute_login(Login {
email: "[email protected]".to_string(),
password: "1234".to_string(),
})
.await
{
Ok(_) => {}
Err(err) => {
let expected_error = LoginError {
cause: "Authentication failed. Invalid email or password".to_string(),
};
assert_eq!(err, expected_error);
}
};
assert!(matches!(
execute_login(Login {
email: "[email protected]".to_string(),
password: "1234".to_string(),
})
.await,
Err(AuthError::InvalidCredentials)
));
}

#[tokio::test]
#[serial]
async fn login_500() {
let mut server = mockito::Server::new_async().await;
env::set_var("base_url", format!("http://{}", server.host_with_port()));

unsafe {
// became unsafe in Rust 1.80
env::set_var("base_url", format!("http://{}", server.host_with_port()));
}

let data = r#"
{
Expand All @@ -227,18 +213,13 @@ mod tests {
.with_body(data)
.create();

match execute_login(Login {
email: "[email protected]".to_string(),
password: "1234".to_string(),
})
.await
{
Ok(_) => {}
Err(err) => {
let expected_error =
LoginError { cause: "Authentication failed. Server response: 500".to_string() };
assert_eq!(err, expected_error);
}
};
assert!(matches!(
execute_login(Login {
email: "[email protected]".to_string(),
password: "1234".to_string(),
})
.await,
Err(AuthError::HttpError(_))
));
}
}
Loading

0 comments on commit eb20e25

Please sign in to comment.