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!: big rewrite #118

Merged
merged 63 commits into from
Aug 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
66f513e
refactor!: various things (work in progress)
beeb Aug 2, 2024
4c42649
refactor: more refactoring
beeb Aug 3, 2024
f0be0b6
fix: formatting as inline table
beeb Aug 3, 2024
9a00843
fix: error messages
beeb Aug 3, 2024
43ba871
fix: path sent to git command
beeb Aug 3, 2024
bd8d22d
fix: more git path
beeb Aug 3, 2024
781c67f
fix: error on bad http code
beeb Aug 3, 2024
78a39fb
refactor: tweaks
beeb Aug 3, 2024
e71a2f3
fix: only retrieve url if necessary
beeb Aug 3, 2024
0b0df07
refactor: make parsing not async
beeb Aug 3, 2024
2697728
test: remove unused
beeb Aug 3, 2024
f95d848
feat: validate that the version field is not empty
beeb Aug 3, 2024
64b3197
refactor(errors): start to migrate to thiserror
beeb Aug 3, 2024
dc06fa1
refactor(error): remove unused error
beeb Aug 3, 2024
a3c06eb
refactor(errors): move download errors to thiserror
beeb Aug 3, 2024
a6fc4f0
refactor(errors): improve IO errors with context
beeb Aug 3, 2024
65e00a5
refactor(errors): lock module errors
beeb Aug 3, 2024
d6eb0bc
refactor(errors): convert login errors to thiserror
beeb Aug 3, 2024
0135ed5
refactor(versioning): new error type and optimizations
beeb Aug 3, 2024
332dac7
refactor(errors): main soldeer error with thiserror
beeb Aug 3, 2024
78cc5bc
chore: merge branch 'main' into refactor-various
beeb Aug 3, 2024
0a35046
refactor: yansi invocations
beeb Aug 3, 2024
23c624a
refactor(utils): remove_empty_lines
beeb Aug 3, 2024
48cc638
refactor(utils): check_dotfiles
beeb Aug 3, 2024
1835ec9
refactor(utils): safer check_dotfiles
beeb Aug 3, 2024
a97fe52
refactor: rename struct and function
beeb Aug 3, 2024
bcf0338
refactor: take dependency as input to various helpers
beeb Aug 3, 2024
ff7a211
perf(dependency_downloader): improve download parallelism
beeb Aug 4, 2024
c0ad6c3
feat: error message for download error
beeb Aug 4, 2024
8f292e3
fix: race condition when creating the dependencies directory
beeb Aug 4, 2024
31809db
perf: avoid checking for dependencies directory if unnecessary
beeb Aug 4, 2024
e2d1c31
fix: flush file after write
beeb Aug 4, 2024
c5eafde
feat: commands
beeb Aug 4, 2024
c04faf8
refactor: remappings txt
beeb Aug 4, 2024
8e0ad4b
feat: remappings for foundry config
beeb Aug 4, 2024
6822df2
refactor(lib): remappings
beeb Aug 4, 2024
ee689db
refactor: rename config items and CLI arguments
beeb Aug 4, 2024
917c5cf
fix: minor things
beeb Aug 4, 2024
5551d7b
test: fix test
beeb Aug 4, 2024
57d9863
fix(config): fix remappings generation
beeb Aug 4, 2024
6618e07
test(config): add test for multiple profiles
beeb Aug 4, 2024
375795a
test(config): add test for existing remappings
beeb Aug 4, 2024
5afef38
test(config): keep custom remappings
beeb Aug 4, 2024
cdfd9fa
refactor(utils): more robust way to get security folder
beeb Aug 4, 2024
eed47dd
refactor: clippy suggestions
beeb Aug 4, 2024
e2467a7
docs(utils): document function
beeb Aug 4, 2024
8355a93
docs(utils): comment
beeb Aug 4, 2024
5e7b532
docs(commands): version command
beeb Aug 4, 2024
43dbb2f
test(config): fix env var problem
beeb Aug 4, 2024
20ac9c8
fix: clippy
beeb Aug 4, 2024
2260892
refactor(commands): rename version command
beeb Aug 4, 2024
b457576
docs: fix command usage notice
beeb Aug 4, 2024
7c703a7
refactor(commands): make flags boolean
beeb Aug 4, 2024
dcab083
fix(commands): value name for dependency name + version
beeb Aug 4, 2024
2879346
docs(commands): example for push
beeb Aug 4, 2024
c88fead
feat: handle remappings during uninstall
beeb Aug 4, 2024
9b3ac7c
fix(install): add to config before downloading
beeb Aug 4, 2024
ddb55a2
feat: export error type and subcommands in lib root
beeb Aug 4, 2024
f12fe0e
style: move method
beeb Aug 4, 2024
4a588f8
revert(dependency_downloader): make `clean_dependency_directory` sync…
beeb Aug 4, 2024
8bbd8bf
feat(config): change default value for remappings version
beeb Aug 5, 2024
d266ea1
test: mark env::set_var unsafe
beeb Aug 5, 2024
17f12d3
style: fix format
beeb Aug 5, 2024
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
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