From 4c8546f81eabb6b8bb72a67770bc2093f9c4800e Mon Sep 17 00:00:00 2001 From: EETagent Date: Sun, 30 Oct 2022 10:50:58 +0100 Subject: [PATCH 1/7] feat: init recovery CLI tool --- Cargo.toml | 2 +- recovery/Cargo.toml | 25 ++++++++++++++++++++ recovery/src/main.rs | 55 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 recovery/Cargo.toml create mode 100644 recovery/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index d159131..09361d3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" publish = false [workspace] -members = [".", "api", "core", "entity", "migration"] +members = [".", "api", "core", "entity", "migration", "recovery"] [dependencies] portfolio-api = { path = "api" } diff --git a/recovery/Cargo.toml b/recovery/Cargo.toml new file mode 100644 index 0000000..d2ecc52 --- /dev/null +++ b/recovery/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "portfolio-recovery" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +url = "^2.3" +clap = { version = "^4.0", features = ["cargo"] } + +portfolio-entity = { path = "../entity" } + +[dependencies.tokio] +version = "^1.21" +features = [ + "macros", +] + +[dependencies.sea-orm] +version = "^0.10" +features = [ + "sqlx-sqlite", + # TODO: Migrate to rustls for better compatibility with various OS + "runtime-tokio-native-tls" +] diff --git a/recovery/src/main.rs b/recovery/src/main.rs new file mode 100644 index 0000000..aa831b5 --- /dev/null +++ b/recovery/src/main.rs @@ -0,0 +1,55 @@ +use std::path::PathBuf; +use url::Url; + +use clap::{arg, command, value_parser}; +use sea_orm::{Database, DatabaseConnection}; + +use sea_orm::*; +use ::entity::candidate::Entity as Candidate; +use ::entity::parent::Entity as Parent; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let clap = command!() + .arg(arg!([name] "Path to the db .sql backup")) + .arg( + arg!( + -d --database "Path to the database SQL backup file" + ) + .required(true) + .value_parser(value_parser!(PathBuf)), + ) + .arg( + arg!( + -p --portfolio "Path to the portfolio root" + ) + .required(true) + .value_parser(value_parser!(PathBuf)), + ) + .arg( + arg!( + -k --key "AGE private key for decryption" + ) + .required(true), + ) + .get_matches(); + + let mut sqlite_url = Url::from_file_path(clap.get_one::("DATABASE").unwrap()).unwrap(); + sqlite_url.set_scheme("sqlite").unwrap(); + + let db: DatabaseConnection = Database::connect(sqlite_url.as_str()).await?; + + let entries = Candidate::find() + .join_rev( + JoinType::InnerJoin, + Parent::belongs_to(Candidate) + .from(::entity::parent::Column::Application) + .to(::entity::candidate::Column::Application) + .into(), + ) + .all(&db) + .await?; + + Ok(()) + +} From ee4d137a7d399c8cda645a72fd8b8c5cdd05cccd Mon Sep 17 00:00:00 2001 From: EETagent Date: Sun, 30 Oct 2022 12:52:09 +0100 Subject: [PATCH 2/7] refactor: require URL instead --- recovery/src/main.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/recovery/src/main.rs b/recovery/src/main.rs index aa831b5..1d49a5d 100644 --- a/recovery/src/main.rs +++ b/recovery/src/main.rs @@ -11,13 +11,13 @@ use ::entity::parent::Entity as Parent; #[tokio::main] async fn main() -> Result<(), Box> { let clap = command!() - .arg(arg!([name] "Path to the db .sql backup")) .arg( arg!( - -d --database "Path to the database SQL backup file" + -d --database "URL to the database or sql file with postgres:// or sqlite://" ) + .alias("url") .required(true) - .value_parser(value_parser!(PathBuf)), + .value_parser(value_parser!(Url)), ) .arg( arg!( @@ -34,8 +34,13 @@ async fn main() -> Result<(), Box> { ) .get_matches(); - let mut sqlite_url = Url::from_file_path(clap.get_one::("DATABASE").unwrap()).unwrap(); - sqlite_url.set_scheme("sqlite").unwrap(); + let sqlite_url = clap.get_one::("database").unwrap(); + + println!("Connecting to {:?}", sqlite_url); + + if sqlite_url.scheme() != "sqlite" && sqlite_url.scheme() != "postgres" { + return Err("URL scheme postgres:// or sqlite:// required")?; + } let db: DatabaseConnection = Database::connect(sqlite_url.as_str()).await?; From 693426f0411d780c15a6c9e9222533ab5f4b690f Mon Sep 17 00:00:00 2001 From: EETagent Date: Fri, 11 Nov 2022 20:20:48 +0100 Subject: [PATCH 3/7] feat: add useful utilities --- Cargo.lock | 41 ++++++++++++- recovery/Cargo.toml | 1 + recovery/src/main.rs | 143 +++++++++++++++++++++++++++++++------------ 3 files changed, 144 insertions(+), 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0bb73e5..78e68ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -409,7 +409,7 @@ dependencies = [ "atty", "bitflags", "clap_derive", - "clap_lex", + "clap_lex 0.2.4", "indexmap", "once_cell", "strsim", @@ -417,6 +417,20 @@ dependencies = [ "textwrap", ] +[[package]] +name = "clap" +version = "4.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eb41c13df48950b20eb4cd0eefa618819469df1bffc49d11e8487c4ba0037e5" +dependencies = [ + "atty", + "bitflags", + "clap_lex 0.3.0", + "once_cell", + "strsim", + "termcolor", +] + [[package]] name = "clap_derive" version = "3.2.18" @@ -439,6 +453,15 @@ dependencies = [ "os_str_bytes", ] +[[package]] +name = "clap_lex" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8" +dependencies = [ + "os_str_bytes", +] + [[package]] name = "codespan-reporting" version = "0.11.1" @@ -1893,6 +1916,18 @@ dependencies = [ "tokio", ] +[[package]] +name = "portfolio-recovery" +version = "0.1.0" +dependencies = [ + "clap 4.0.23", + "portfolio-core", + "portfolio-entity", + "sea-orm", + "tokio", + "url", +] + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -2339,7 +2374,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "882af0d4cd3d6cc2f427d14a48e9f468b37c563b405ea486fd314ba18ca334d0" dependencies = [ "chrono", - "clap", + "clap 3.2.23", "dotenvy", "regex", "sea-schema", @@ -2368,7 +2403,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86fe6e594b078712e1e797b951b9b56e55d3cfa04aac8ea76eb4bed7c94c5910" dependencies = [ "async-trait", - "clap", + "clap 3.2.23", "dotenvy", "sea-orm", "sea-orm-cli", diff --git a/recovery/Cargo.toml b/recovery/Cargo.toml index d2ecc52..278add3 100644 --- a/recovery/Cargo.toml +++ b/recovery/Cargo.toml @@ -9,6 +9,7 @@ url = "^2.3" clap = { version = "^4.0", features = ["cargo"] } portfolio-entity = { path = "../entity" } +portfolio-core = { path = "../core" } [dependencies.tokio] version = "^1.21" diff --git a/recovery/src/main.rs b/recovery/src/main.rs index 1d49a5d..357258c 100644 --- a/recovery/src/main.rs +++ b/recovery/src/main.rs @@ -1,60 +1,127 @@ use std::path::PathBuf; use url::Url; -use clap::{arg, command, value_parser}; +use clap::{arg, command, value_parser, Command, ArgAction}; use sea_orm::{Database, DatabaseConnection}; -use sea_orm::*; use ::entity::candidate::Entity as Candidate; use ::entity::parent::Entity as Parent; +use sea_orm::*; #[tokio::main] async fn main() -> Result<(), Box> { let clap = command!() - .arg( - arg!( - -d --database "URL to the database or sql file with postgres:// or sqlite://" - ) - .alias("url") - .required(true) - .value_parser(value_parser!(Url)), + .propagate_version(true) + .subcommand_required(true) + .arg_required_else_help(true) + .subcommand( + Command::new("portfolio") + .about("Database & Portfolio operations") + .arg( + arg!( + -d --database "URL to the database or sql file with postgres:// or sqlite://" + ) + .alias("url") + .required(true) + .value_parser(value_parser!(Url)), + ) + .arg( + arg!( + -p --portfolio "Path to the portfolio root" + ) + .required(true) + .value_parser(value_parser!(PathBuf)), + ) + .arg( + arg!( + -k --key "AGE private key for decryption" + ) + .required(true), + ) ) - .arg( - arg!( - -p --portfolio "Path to the portfolio root" - ) - .required(true) - .value_parser(value_parser!(PathBuf)), + .subcommand( + Command::new("hash") + .about("Hash operations") + .arg( + arg!( + -i --input "Plaintext to hash" + ) + .required(true) + ), ) - .arg( - arg!( - -k --key "AGE private key for decryption" - ) - .required(true), + .subcommand( + Command::new("encryption") + .about("Encryption operations") + .arg( + arg!( + -d --decrypt ... "Decrypt flag" + ) + .action(ArgAction::SetFalse) + .required(false) + ) + .arg( + arg!( + -i --input "Plaintext to encrypt/decrypt" + ) + .required(true) + ) + .arg( + arg!( + -k --key "Key for encryption/decryption" + ) + .required(true), + ) ) .get_matches(); - let sqlite_url = clap.get_one::("database").unwrap(); + match clap.subcommand() { + Some(("portfolio", sub_matches)) => { + let sqlite_url = sub_matches.get_one::("database").unwrap(); - println!("Connecting to {:?}", sqlite_url); + println!("Connecting to {:?}", sqlite_url); - if sqlite_url.scheme() != "sqlite" && sqlite_url.scheme() != "postgres" { - return Err("URL scheme postgres:// or sqlite:// required")?; + if sqlite_url.scheme() != "sqlite" && sqlite_url.scheme() != "postgres" { + return Err("URL scheme postgres:// or sqlite:// required")?; + } + + let db: DatabaseConnection = Database::connect(sqlite_url.as_str()).await?; + + let entries = Candidate::find() + .join_rev( + JoinType::InnerJoin, + Parent::belongs_to(Candidate) + .from(::entity::parent::Column::Application) + .to(::entity::candidate::Column::Application) + .into(), + ) + .all(&db) + .await?; + + println!("Found {} entries", entries.len()); + } + Some(("hash", sub_matches)) => { + let input = sub_matches.get_one::("input").unwrap(); + + let hash = portfolio_core::crypto::hash_password(input.to_string()).await?; + + println!("{}", hash); + } + Some(("encryption", sub_matches)) => { + let decrypt = sub_matches.get_one::("decrypt").unwrap(); + let input = sub_matches.get_one::("input").unwrap(); + let key = sub_matches.get_one::("key").unwrap(); + + + let result = if !*decrypt { + portfolio_core::crypto::decrypt_password(input.to_string(), key.to_string()).await? + } else { + portfolio_core::crypto::encrypt_password(input.to_string(), key.to_string()).await? + }; + + println!("{}", result); + } + _ => unreachable!("Exhausted list of subcommands and subcommand_required prevents `None`"), } - let db: DatabaseConnection = Database::connect(sqlite_url.as_str()).await?; - - let entries = Candidate::find() - .join_rev( - JoinType::InnerJoin, - Parent::belongs_to(Candidate) - .from(::entity::parent::Column::Application) - .to(::entity::candidate::Column::Application) - .into(), - ) - .all(&db) - .await?; - Ok(()) - } From 1039b80518ca40920513da7dc38eb9be81ace9f6 Mon Sep 17 00:00:00 2001 From: EETagent Date: Fri, 11 Nov 2022 20:41:40 +0100 Subject: [PATCH 4/7] feat: add asymetric & fix default value for decrypt flags --- recovery/src/main.rs | 45 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/recovery/src/main.rs b/recovery/src/main.rs index 357258c..d9a963e 100644 --- a/recovery/src/main.rs +++ b/recovery/src/main.rs @@ -1,7 +1,7 @@ use std::path::PathBuf; use url::Url; -use clap::{arg, command, value_parser, Command, ArgAction}; +use clap::{arg, command, value_parser, ArgAction, Command}; use sea_orm::{Database, DatabaseConnection}; use ::entity::candidate::Entity as Candidate; @@ -56,7 +56,7 @@ async fn main() -> Result<(), Box> { arg!( -d --decrypt ... "Decrypt flag" ) - .action(ArgAction::SetFalse) + .action(ArgAction::SetTrue) .required(false) ) .arg( @@ -72,6 +72,29 @@ async fn main() -> Result<(), Box> { .required(true), ) ) + .subcommand( + Command::new("asymetric") + .about("Asymetric encryption operations") + .arg( + arg!( + -d --decrypt ... "Decrypt flag" + ) + .action(ArgAction::SetTrue) + .required(false) + ) + .arg( + arg!( + -i --input "Plaintext to encrypt/decrypt" + ) + .required(true) + ) + .arg( + arg!( + -k --key "Public key / Private key" + ) + .required(true), + ) + ) .get_matches(); match clap.subcommand() { @@ -111,11 +134,25 @@ async fn main() -> Result<(), Box> { let input = sub_matches.get_one::("input").unwrap(); let key = sub_matches.get_one::("key").unwrap(); + let result = if !*decrypt { + portfolio_core::crypto::encrypt_password(input.to_string(), key.to_string()).await? + } else { + portfolio_core::crypto::decrypt_password(input.to_string(), key.to_string()).await? + }; + + println!("{}", result); + } + Some(("asymetric", sub_matches)) => { + let decrypt = sub_matches.get_one::("decrypt").unwrap(); + let input = sub_matches.get_one::("input").unwrap(); + let key = sub_matches.get_one::("key").unwrap(); + + println!("Decrypt: {}", decrypt); let result = if !*decrypt { - portfolio_core::crypto::decrypt_password(input.to_string(), key.to_string()).await? + portfolio_core::crypto::encrypt_password_with_recipients(input, &vec![key]).await? } else { - portfolio_core::crypto::encrypt_password(input.to_string(), key.to_string()).await? + portfolio_core::crypto::decrypt_password_with_private_key(input, key).await.map_err(|e| e.to_string())? }; println!("{}", result); From f220e133c5d3a7963e2962227772ea0688db6eeb Mon Sep 17 00:00:00 2001 From: EETagent Date: Fri, 11 Nov 2022 20:42:11 +0100 Subject: [PATCH 5/7] refactor: remove debug print --- recovery/src/main.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/recovery/src/main.rs b/recovery/src/main.rs index d9a963e..14a346c 100644 --- a/recovery/src/main.rs +++ b/recovery/src/main.rs @@ -147,8 +147,6 @@ async fn main() -> Result<(), Box> { let input = sub_matches.get_one::("input").unwrap(); let key = sub_matches.get_one::("key").unwrap(); - println!("Decrypt: {}", decrypt); - let result = if !*decrypt { portfolio_core::crypto::encrypt_password_with_recipients(input, &vec![key]).await? } else { From cb4b3ff08adb00bb502bb126df3aafa281498b7d Mon Sep 17 00:00:00 2001 From: EETagent Date: Fri, 11 Nov 2022 20:47:19 +0100 Subject: [PATCH 6/7] refactor: rename to portfolio-cl --- Cargo.lock | 24 ++++++++++++------------ Cargo.toml | 2 +- {recovery => cli}/Cargo.toml | 2 +- {recovery => cli}/src/main.rs | 0 4 files changed, 14 insertions(+), 14 deletions(-) rename {recovery => cli}/Cargo.toml (94%) rename {recovery => cli}/src/main.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index 78e68ad..4784185 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1875,6 +1875,18 @@ dependencies = [ "tokio", ] +[[package]] +name = "portfolio-cli" +version = "0.1.0" +dependencies = [ + "clap 4.0.23", + "portfolio-core", + "portfolio-entity", + "sea-orm", + "tokio", + "url", +] + [[package]] name = "portfolio-core" version = "0.1.0" @@ -1916,18 +1928,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "portfolio-recovery" -version = "0.1.0" -dependencies = [ - "clap 4.0.23", - "portfolio-core", - "portfolio-entity", - "sea-orm", - "tokio", - "url", -] - [[package]] name = "ppv-lite86" version = "0.2.17" diff --git a/Cargo.toml b/Cargo.toml index 09361d3..9044149 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" publish = false [workspace] -members = [".", "api", "core", "entity", "migration", "recovery"] +members = [".", "api", "core", "entity", "migration", "cli"] [dependencies] portfolio-api = { path = "api" } diff --git a/recovery/Cargo.toml b/cli/Cargo.toml similarity index 94% rename from recovery/Cargo.toml rename to cli/Cargo.toml index 278add3..2a327db 100644 --- a/recovery/Cargo.toml +++ b/cli/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "portfolio-recovery" +name = "portfolio-cli" version = "0.1.0" edition = "2021" publish = false diff --git a/recovery/src/main.rs b/cli/src/main.rs similarity index 100% rename from recovery/src/main.rs rename to cli/src/main.rs From 9c9cc5a73adff54e1046b308a28bd3378a555819 Mon Sep 17 00:00:00 2001 From: EETagent Date: Fri, 11 Nov 2022 20:48:14 +0100 Subject: [PATCH 7/7] fix: correct argument names --- cli/src/main.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/src/main.rs b/cli/src/main.rs index 14a346c..9c69d96 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -50,8 +50,8 @@ async fn main() -> Result<(), Box> { ), ) .subcommand( - Command::new("encryption") - .about("Encryption operations") + Command::new("symmetric") + .about("Symmetric encryption operations") .arg( arg!( -d --decrypt ... "Decrypt flag" @@ -73,8 +73,8 @@ async fn main() -> Result<(), Box> { ) ) .subcommand( - Command::new("asymetric") - .about("Asymetric encryption operations") + Command::new("asymmetric") + .about("Asymmetric encryption operations") .arg( arg!( -d --decrypt ... "Decrypt flag" @@ -129,7 +129,7 @@ async fn main() -> Result<(), Box> { println!("{}", hash); } - Some(("encryption", sub_matches)) => { + Some(("symmetric", sub_matches)) => { let decrypt = sub_matches.get_one::("decrypt").unwrap(); let input = sub_matches.get_one::("input").unwrap(); let key = sub_matches.get_one::("key").unwrap(); @@ -142,7 +142,7 @@ async fn main() -> Result<(), Box> { println!("{}", result); } - Some(("asymetric", sub_matches)) => { + Some(("asymmetric", sub_matches)) => { let decrypt = sub_matches.get_one::("decrypt").unwrap(); let input = sub_matches.get_one::("input").unwrap(); let key = sub_matches.get_one::("key").unwrap();