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;
use crate::commands::cesium::compute_g1v1_public_key;
use crate::entities::vault_account;
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::*;
use age::secrecy::Secret;
......@@ -60,14 +61,14 @@ pub enum Subcommand {
g1v1_password: Option<String>,
/// 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>,
/// Use empty password (non-interactive mode)
/// Use empty password for encrypting the key (non-interactive mode)
#[clap(long, required = false)]
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)]
name: Option<String>,
},
......@@ -83,6 +84,22 @@ pub enum Subcommand {
Derive {
#[clap(flatten)]
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
Rename {
......@@ -170,7 +187,7 @@ pub struct AddressOrVaultNameGroup {
address: Option<AccountId>,
/// Name of an SS58 Address in the vault
#[clap(short = 'v')]
name: Option<String>,
vault_name: Option<String>,
}
pub struct VaultDataToImport {
......@@ -390,6 +407,10 @@ pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliE
}
Subcommand::Derive {
address_or_vault_name,
derivation_path,
password,
no_password,
name,
} => {
let account_tree_node_to_derive =
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
println!("The linked <Base> account is {base_account}");
// Handle password from non-interactive mode or ask for it
let password = if no_password {
String::new()
} else if let Some(password) = password {
password
} else {
println!("Enter password to decrypt the <Base> account key");
let password = inputs::prompt_password()?;
inputs::prompt_password()?
};
let account_to_derive_secret_suri = vault_account::compute_suri_account_tree_node(
&account_tree_node_to_derive,
......@@ -429,7 +457,14 @@ pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliE
)?;
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 =
format!("{account_to_derive_secret_suri}{derivation_path}");
......@@ -451,6 +486,7 @@ pub async fn handle_command(data: Data, command: Subcommand) -> Result<(), GcliE
&derivation_address,
&derivation_path,
&account_to_derive.address.to_string(),
name,
)
.await?;
......@@ -742,7 +778,7 @@ pub async fn retrieve_account_tree_node<C>(
where
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?
} else if let Some(address) = &address_or_vault_name.address {
let base_account_tree_node =
......@@ -909,7 +945,8 @@ where
))? {
true => {
let name = if let Some(name) = name_opt {
Some(name)
validate_vault_name(&name)?;
trim_and_reduce_empty_as_none(name)
} else {
println!("(Optional) Enter a name for the vault entry (leave empty to remove the name)");
inputs::prompt_vault_name_and_check_availability(
......@@ -945,7 +982,8 @@ where
let encrypted_suri = compute_encrypted_suri(password, vault_data.secret_suri.clone())?;
let name = if let Some(name) = name_opt {
Some(name)
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?
......@@ -980,6 +1018,7 @@ pub async fn create_derivation_account<C>(
derivation_address: &String,
derivation_path: &String,
parent_address: &String,
name_opt: Option<String>,
) -> Result<vault_account::Model, GcliError>
where
C: ConnectionTrait,
......@@ -1044,14 +1083,17 @@ where
let result = inputs::select_action("Your choice?", vec!["1", "2"])?;
match result {
"2" => {
println!(
"(Optional) Enter a name for the vault entry (leave empty to remove the name)"
);
let name = inputs::prompt_vault_name_and_check_availability(
let name = if let Some(name) = name_opt {
validate_vault_name(&name)?;
trim_and_reduce_empty_as_none(name)
} else {
println!("(Optional) Enter a name for the vault entry (leave empty to remove the name)");
inputs::prompt_vault_name_and_check_availability(
db_tx,
existing_vault_account.name.as_ref(),
)
.await?;
.await?
};
// 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();
......@@ -1070,9 +1112,14 @@ where
return Err(GcliError::Input("derive canceled".into()));
}
}
} else {
let name = if let Some(name) = name_opt {
validate_vault_name(&name)?;
trim_and_reduce_empty_as_none(name)
} else {
println!("(Optional) Enter a name for the vault entry");
let name = inputs::prompt_vault_name_and_check_availability(db_tx, None).await?;
inputs::prompt_vault_name_and_check_availability(db_tx, None).await?
};
let derivation = vault_account::create_derivation_account(
db_tx,
......
......@@ -46,15 +46,16 @@ where
C: ConnectionTrait,
{
loop {
let mut text_inquire = inquire::Text::new("Name:").with_validator({
|input: &str| {
if input.contains('<') || input.contains('>') || input.contains('/') {
return Ok(Validation::Invalid(
"Name cannot contain characters '<', '>', '/'".into(),
));
let mut text_inquire = inquire::Text::new("Name:").with_validator(|input: &str| {
match validate_vault_name(input) {
Ok(_) => Ok(Validation::Valid),
Err(error) => {
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
.prompt()
.map_err(|e| GcliError::Input(e.to_string()))?;
let name = if name.trim().is_empty() {
None
} else {
Some(name.trim().to_string())
};
let name = trim_and_reduce_empty_as_none(name);
let available =
vault_account::check_name_available(db, initial_name, name.as_ref()).await?;
......@@ -86,16 +83,29 @@ 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
pub fn prompt_vault_derivation_path() -> Result<String, GcliError> {
inquire::Text::new("Derivation path:")
.with_validator(|input: &str| {
if !input.starts_with("/") {
Ok(Validation::Invalid(
"derivation path needs to start with one or more '/'".into(),
))
} else {
match vault::parse_prefix_and_derivation_path_from_suri(input.to_string()) {
.with_validator(
|input: &str| match validate_derivation_path(input.to_string()) {
Ok(_) => Ok(Validation::Valid),
Err(error) => {
if let GcliError::Input(message) = error {
......@@ -104,13 +114,25 @@ pub fn prompt_vault_derivation_path() -> Result<String, GcliError> {
Ok(Validation::Invalid("Unknown error".into()))
}
}
}
}
})
},
)
.prompt()
.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> {
inquire::Confirm::new(query.to_string().as_str())
.prompt()
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment