Skip to content
Snippets Groups Projects
Commit da7c790d authored by Nicolas80's avatar Nicolas80
Browse files

* Added some logic between arguments of `vault import`; can't provide both...

* Added some logic between arguments of `vault import`; can't provide both `password` and `no-password`
** Added extra validation of non-interactive `name` argument value (same validation as when interactive: no '<', '>', '/' characters)
* Added possibility to make a non-interactive derivation (given proper arguments are given and there is no issue found during the process)
** Added same validation for non-interactive `derivation_path` argument as when interactive
** If the same resulting address is already in the vault; interaction is still mandatory to make a choice
* Had to change the name of argument AddressOrVaultNameGroup.name => vault_name to avoid conflict in `vault derive`
** Not changing the `-v` shortcut so no impact on existing commands
* Allowing to pass "" empty string as non-interactive `name` argument and considering it as None (does a trim before checking empty; so only spaces will be considered as None as well)
parent 44073020
No related branches found
No related tags found
1 merge request!44feat: Can choose between ed25519 ans sr25519
...@@ -3,6 +3,7 @@ mod display; ...@@ -3,6 +3,7 @@ mod display;
use crate::commands::cesium::compute_g1v1_public_key; use crate::commands::cesium::compute_g1v1_public_key;
use crate::entities::vault_account; use crate::entities::vault_account;
use crate::entities::vault_account::{AccountTreeNode, ActiveModel, DbAccountId}; use crate::entities::vault_account::{AccountTreeNode, ActiveModel, DbAccountId};
use crate::inputs::{trim_and_reduce_empty_as_none, validate_derivation_path, validate_vault_name};
use crate::keys::seed_from_cesium; use crate::keys::seed_from_cesium;
use crate::*; use crate::*;
use age::secrecy::Secret; use age::secrecy::Secret;
...@@ -60,14 +61,14 @@ pub enum Subcommand { ...@@ -60,14 +61,14 @@ pub enum Subcommand {
g1v1_password: Option<String>, g1v1_password: Option<String>,
/// Password for encrypting the key (non-interactive mode) /// Password for encrypting the key (non-interactive mode)
#[clap(short = 'p', long, required = false)] #[clap(short = 'p', long, required = false, conflicts_with_all=["no_password"])]
password: Option<String>, password: Option<String>,
/// Use empty password (non-interactive mode) /// Use empty password for encrypting the key (non-interactive mode)
#[clap(long, required = false)] #[clap(long, required = false)]
no_password: bool, no_password: bool,
/// Name for the wallet entry (non-interactive mode) /// Name for the wallet entry (non-interactive mode) - "" empty string will be considered as None
#[clap(short = 'n', long, required = false)] #[clap(short = 'n', long, required = false)]
name: Option<String>, name: Option<String>,
}, },
...@@ -83,6 +84,22 @@ pub enum Subcommand { ...@@ -83,6 +84,22 @@ pub enum Subcommand {
Derive { Derive {
#[clap(flatten)] #[clap(flatten)]
address_or_vault_name: AddressOrVaultNameGroup, address_or_vault_name: AddressOrVaultNameGroup,
/// Derivation path (non-interactive mode)
#[clap(short = 'd', long, required = false)]
derivation_path: Option<String>,
/// Password to decrypt the <Base> account key (non-interactive mode)
#[clap(short = 'p', long, required = false, requires = "derivation_path", conflicts_with_all=["no_password"])]
password: Option<String>,
/// Use empty password to decrypt the <Base> account key (non-interactive mode)
#[clap(long, required = false, requires = "derivation_path")]
no_password: bool,
/// Name for the wallet entry (non-interactive mode) - "" empty string will be considered as None
#[clap(short = 'n', long, required = false, requires = "derivation_path")]
name: Option<String>,
}, },
/// Give a meaningful name to an SS58 Address in the vault /// Give a meaningful name to an SS58 Address in the vault
Rename { Rename {
...@@ -170,7 +187,7 @@ pub struct AddressOrVaultNameGroup { ...@@ -170,7 +187,7 @@ pub struct AddressOrVaultNameGroup {
address: Option<AccountId>, address: Option<AccountId>,
/// Name of an SS58 Address in the vault /// Name of an SS58 Address in the vault
#[clap(short = 'v')] #[clap(short = 'v')]
name: Option<String>, vault_name: Option<String>,
} }
pub struct VaultDataToImport { pub struct VaultDataToImport {
...@@ -390,6 +407,10 @@ pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliE ...@@ -390,6 +407,10 @@ pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliE
} }
Subcommand::Derive { Subcommand::Derive {
address_or_vault_name, address_or_vault_name,
derivation_path,
password,
no_password,
name,
} => { } => {
let account_tree_node_to_derive = let account_tree_node_to_derive =
retrieve_account_tree_node(db, address_or_vault_name).await?; retrieve_account_tree_node(db, address_or_vault_name).await?;
...@@ -420,8 +441,15 @@ pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliE ...@@ -420,8 +441,15 @@ pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliE
println!("The linked <Base> account is {base_account}"); println!("The linked <Base> account is {base_account}");
println!("Enter password to decrypt the <Base> account key"); // Handle password from non-interactive mode or ask for it
let password = inputs::prompt_password()?; let password = if no_password {
String::new()
} else if let Some(password) = password {
password
} else {
println!("Enter password to decrypt the <Base> account key");
inputs::prompt_password()?
};
let account_to_derive_secret_suri = vault_account::compute_suri_account_tree_node( let account_to_derive_secret_suri = vault_account::compute_suri_account_tree_node(
&account_tree_node_to_derive, &account_tree_node_to_derive,
...@@ -429,7 +457,14 @@ pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliE ...@@ -429,7 +457,14 @@ pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliE
)?; )?;
println!(); println!();
let derivation_path = inputs::prompt_vault_derivation_path()?;
// Handle derivation_path from non-interactive mode or ask for it
let derivation_path = if let Some(derivation_path) = derivation_path {
validate_derivation_path(derivation_path.clone())?;
derivation_path
} else {
inputs::prompt_vault_derivation_path()?
};
let derivation_secret_suri = let derivation_secret_suri =
format!("{account_to_derive_secret_suri}{derivation_path}"); format!("{account_to_derive_secret_suri}{derivation_path}");
...@@ -451,6 +486,7 @@ pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliE ...@@ -451,6 +486,7 @@ pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliE
&derivation_address, &derivation_address,
&derivation_path, &derivation_path,
&account_to_derive.address.to_string(), &account_to_derive.address.to_string(),
name,
) )
.await?; .await?;
...@@ -742,7 +778,7 @@ pub async fn retrieve_account_tree_node<C>( ...@@ -742,7 +778,7 @@ pub async fn retrieve_account_tree_node<C>(
where where
C: ConnectionTrait, C: ConnectionTrait,
{ {
let account_tree_node = if let Some(name_input) = &address_or_vault_name.name { let account_tree_node = if let Some(name_input) = &address_or_vault_name.vault_name {
retrieve_account_tree_node_for_name(db, name_input).await? retrieve_account_tree_node_for_name(db, name_input).await?
} else if let Some(address) = &address_or_vault_name.address { } else if let Some(address) = &address_or_vault_name.address {
let base_account_tree_node = let base_account_tree_node =
...@@ -909,7 +945,8 @@ where ...@@ -909,7 +945,8 @@ where
))? { ))? {
true => { true => {
let name = if let Some(name) = name_opt { let name = if let Some(name) = name_opt {
Some(name) validate_vault_name(&name)?;
trim_and_reduce_empty_as_none(name)
} else { } else {
println!("(Optional) Enter a name for the vault entry (leave empty to remove the name)"); println!("(Optional) Enter a name for the vault entry (leave empty to remove the name)");
inputs::prompt_vault_name_and_check_availability( inputs::prompt_vault_name_and_check_availability(
...@@ -945,7 +982,8 @@ where ...@@ -945,7 +982,8 @@ where
let encrypted_suri = compute_encrypted_suri(password, vault_data.secret_suri.clone())?; let encrypted_suri = compute_encrypted_suri(password, vault_data.secret_suri.clone())?;
let name = if let Some(name) = name_opt { let name = if let Some(name) = name_opt {
Some(name) validate_vault_name(&name)?;
trim_and_reduce_empty_as_none(name)
} else { } else {
println!("(Optional) Enter a name for the vault entry"); println!("(Optional) Enter a name for the vault entry");
inputs::prompt_vault_name_and_check_availability(db_tx, None).await? inputs::prompt_vault_name_and_check_availability(db_tx, None).await?
...@@ -980,6 +1018,7 @@ pub async fn create_derivation_account<C>( ...@@ -980,6 +1018,7 @@ pub async fn create_derivation_account<C>(
derivation_address: &String, derivation_address: &String,
derivation_path: &String, derivation_path: &String,
parent_address: &String, parent_address: &String,
name_opt: Option<String>,
) -> Result<vault_account::Model, GcliError> ) -> Result<vault_account::Model, GcliError>
where where
C: ConnectionTrait, C: ConnectionTrait,
...@@ -1044,14 +1083,17 @@ where ...@@ -1044,14 +1083,17 @@ where
let result = inputs::select_action("Your choice?", vec!["1", "2"])?; let result = inputs::select_action("Your choice?", vec!["1", "2"])?;
match result { match result {
"2" => { "2" => {
println!( let name = if let Some(name) = name_opt {
"(Optional) Enter a name for the vault entry (leave empty to remove the name)" validate_vault_name(&name)?;
); trim_and_reduce_empty_as_none(name)
let name = inputs::prompt_vault_name_and_check_availability( } else {
db_tx, println!("(Optional) Enter a name for the vault entry (leave empty to remove the name)");
existing_vault_account.name.as_ref(), inputs::prompt_vault_name_and_check_availability(
) db_tx,
.await?; existing_vault_account.name.as_ref(),
)
.await?
};
// Since links are made based on address / parent(address) we can just edit the existing entry and it should be fine // Since links are made based on address / parent(address) we can just edit the existing entry and it should be fine
let mut vault_account: ActiveModel = existing_vault_account.into(); let mut vault_account: ActiveModel = existing_vault_account.into();
...@@ -1071,8 +1113,13 @@ where ...@@ -1071,8 +1113,13 @@ where
} }
} }
} else { } else {
println!("(Optional) Enter a name for the vault entry"); let name = if let Some(name) = name_opt {
let name = inputs::prompt_vault_name_and_check_availability(db_tx, None).await?; validate_vault_name(&name)?;
trim_and_reduce_empty_as_none(name)
} else {
println!("(Optional) Enter a name for the vault entry");
inputs::prompt_vault_name_and_check_availability(db_tx, None).await?
};
let derivation = vault_account::create_derivation_account( let derivation = vault_account::create_derivation_account(
db_tx, db_tx,
......
...@@ -46,15 +46,16 @@ where ...@@ -46,15 +46,16 @@ where
C: ConnectionTrait, C: ConnectionTrait,
{ {
loop { loop {
let mut text_inquire = inquire::Text::new("Name:").with_validator({ let mut text_inquire = inquire::Text::new("Name:").with_validator(|input: &str| {
|input: &str| { match validate_vault_name(input) {
if input.contains('<') || input.contains('>') || input.contains('/') { Ok(_) => Ok(Validation::Valid),
return Ok(Validation::Invalid( Err(error) => {
"Name cannot contain characters '<', '>', '/'".into(), if let GcliError::Input(message) = error {
)); Ok(Validation::Invalid(ErrorMessage::from(message)))
} else {
Ok(Validation::Invalid("Unknown error".into()))
}
} }
Ok(Validation::Valid)
} }
}); });
...@@ -66,11 +67,7 @@ where ...@@ -66,11 +67,7 @@ where
.prompt() .prompt()
.map_err(|e| GcliError::Input(e.to_string()))?; .map_err(|e| GcliError::Input(e.to_string()))?;
let name = if name.trim().is_empty() { let name = trim_and_reduce_empty_as_none(name);
None
} else {
Some(name.trim().to_string())
};
let available = let available =
vault_account::check_name_available(db, initial_name, name.as_ref()).await?; vault_account::check_name_available(db, initial_name, name.as_ref()).await?;
...@@ -86,31 +83,56 @@ where ...@@ -86,31 +83,56 @@ where
} }
} }
pub fn trim_and_reduce_empty_as_none(name: String) -> Option<String> {
if name.trim().is_empty() {
None
} else {
Some(name.trim().to_string())
}
}
pub fn validate_vault_name(vault_name: &str) -> Result<(), GcliError> {
if vault_name.contains('<') || vault_name.contains('>') || vault_name.contains('/') {
return Err(GcliError::Input(
"Name cannot contain characters '<', '>', '/'".into(),
));
}
Ok(())
}
/// Prompt for a derivation path /// Prompt for a derivation path
pub fn prompt_vault_derivation_path() -> Result<String, GcliError> { pub fn prompt_vault_derivation_path() -> Result<String, GcliError> {
inquire::Text::new("Derivation path:") inquire::Text::new("Derivation path:")
.with_validator(|input: &str| { .with_validator(
if !input.starts_with("/") { |input: &str| match validate_derivation_path(input.to_string()) {
Ok(Validation::Invalid( Ok(_) => Ok(Validation::Valid),
"derivation path needs to start with one or more '/'".into(), Err(error) => {
)) if let GcliError::Input(message) = error {
} else { Ok(Validation::Invalid(ErrorMessage::from(message)))
match vault::parse_prefix_and_derivation_path_from_suri(input.to_string()) { } else {
Ok(_) => Ok(Validation::Valid), Ok(Validation::Invalid("Unknown error".into()))
Err(error) => {
if let GcliError::Input(message) = error {
Ok(Validation::Invalid(ErrorMessage::from(message)))
} else {
Ok(Validation::Invalid("Unknown error".into()))
}
} }
} }
} },
}) )
.prompt() .prompt()
.map_err(|e| GcliError::Input(e.to_string())) .map_err(|e| GcliError::Input(e.to_string()))
} }
pub fn validate_derivation_path(derivation_path: String) -> Result<(), GcliError> {
if !derivation_path.starts_with("/") {
Err(GcliError::Input(
"derivation path needs to start with one or more '/'".into(),
))
} else {
match vault::parse_prefix_and_derivation_path_from_suri(derivation_path.to_string()) {
Ok(_) => Ok(()),
Err(error) => Err(error),
}
}
}
pub fn confirm_action(query: impl ToString) -> Result<bool, GcliError> { pub fn confirm_action(query: impl ToString) -> Result<bool, GcliError> {
inquire::Confirm::new(query.to_string().as_str()) inquire::Confirm::new(query.to_string().as_str())
.prompt() .prompt()
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment