feat: Support export and import with different password #30

Merged
awiteb merged 5 commits from awiteb/fix-25 into master 2024-05-08 05:06:28 +02:00 AGit
3 changed files with 33 additions and 39 deletions
Showing only changes of commit 621ec6a07a - Show all commits

View file

@ -15,9 +15,9 @@
// along with this program. If not, see <https://www.gnu.org/licenses/gpl-3.0.html>. // along with this program. If not, see <https://www.gnu.org/licenses/gpl-3.0.html>.
use clap::Args; use clap::Args;
use inquire::{Password, PasswordDisplayMode};
use crate::{ use crate::{
utils,
vault::{Vault, Vaults}, vault::{Vault, Vaults},
LprsCommand, LprsCommand,
LprsError, LprsError,
@ -32,31 +32,13 @@ pub struct Add {
vault_info: Vault, vault_info: Vault,
/// The password, if there is no value for it you will prompt it /// The password, if there is no value for it you will prompt it
#[arg(short, long)] #[arg(short, long)]
// FIXME: I think replacing `Option<Option<String>>` with custom type will be better
#[allow(clippy::option_option)] #[allow(clippy::option_option)]
password: Option<Option<String>>, password: Option<Option<String>>,
} }
impl LprsCommand for Add { impl LprsCommand for Add {
fn run(mut self, mut vault_manager: Vaults) -> LprsResult<()> { fn run(mut self, mut vault_manager: Vaults) -> LprsResult<()> {
match self.password { self.vault_info.password = utils::user_password(self.password)?;
Some(Some(password)) => {
log::debug!("User provided a password");
self.vault_info.password = Some(password);
}
Some(None) => {
log::debug!("User didn't provide a password, prompting it");
self.vault_info.password = Some(
Password::new("Vault password:")
.without_confirmation()
.with_formatter(&|p| "*".repeat(p.chars().count()))
.with_display_mode(PasswordDisplayMode::Masked)
.prompt()?,
);
}
None => {}
};
vault_manager.add_vault(self.vault_info); vault_manager.add_vault(self.vault_info);
vault_manager.try_export() vault_manager.try_export()
} }

View file

@ -17,9 +17,9 @@
use std::num::NonZeroU64; use std::num::NonZeroU64;
use clap::Args; use clap::Args;
use inquire::{Password, PasswordDisplayMode};
use crate::{ use crate::{
utils,
vault::{Vault, Vaults}, vault::{Vault, Vaults},
LprsCommand, LprsCommand,
LprsError, LprsError,
@ -41,7 +41,6 @@ pub struct Edit {
username: Option<String>, username: Option<String>,
#[arg(short, long)] #[arg(short, long)]
/// The new password, if there is no value for it you will prompt it /// The new password, if there is no value for it you will prompt it
// FIXME: I think replacing `Option<Option<String>>` with custom type will be better
#[allow(clippy::option_option)] #[allow(clippy::option_option)]
password: Option<Option<String>>, password: Option<Option<String>>,
#[arg(short, long)] #[arg(short, long)]
@ -65,26 +64,13 @@ impl LprsCommand for Edit {
))); )));
}; };
// Get the password from stdin or from its value if provided
let password = match self.password {
Some(Some(password)) => Some(password),
Some(None) => {
Some(
Password::new("New vault password:")
.without_confirmation()
.with_formatter(&|p| "*".repeat(p.chars().count()))
.with_display_mode(PasswordDisplayMode::Masked)
.prompt()?,
)
}
None => None,
};
log::info!("Applying the new values to the vault"); log::info!("Applying the new values to the vault");
*vault = Vault::new( *vault = Vault::new(
self.name.as_ref().unwrap_or(&vault.name), self.name.as_ref().unwrap_or(&vault.name),
self.username.as_ref().or(vault.username.as_ref()), self.username.as_ref().or(vault.username.as_ref()),
password.as_ref().or(vault.password.as_ref()), utils::user_password(self.password)?
.as_ref()
.or(vault.password.as_ref()),
self.service.as_ref().or(vault.service.as_ref()), self.service.as_ref().or(vault.service.as_ref()),
self.note.as_ref().or(vault.note.as_ref()), self.note.as_ref().or(vault.note.as_ref()),
); );

View file

@ -16,7 +16,7 @@
use std::{fs, path::PathBuf}; use std::{fs, path::PathBuf};
use inquire::{validator::Validation, PasswordDisplayMode}; use inquire::{validator::Validation, Password, PasswordDisplayMode};
use passwords::{analyzer, scorer}; use passwords::{analyzer, scorer};
#[cfg(feature = "update-notify")] #[cfg(feature = "update-notify")]
use reqwest::blocking::Client as BlockingClient; use reqwest::blocking::Client as BlockingClient;
@ -43,6 +43,32 @@ pub fn local_project_file(filename: &str) -> LprsResult<PathBuf> {
Ok(local_dir.join(filename)) Ok(local_dir.join(filename))
} }
/// Returns the user password if any
///
/// - If the `password` is `None` will return `None`
/// - If the `password` is `Some(None)` will ask the user for a password in the
/// stdin and return it
/// - If the `password` is `Some(Some(password))` will return `Some(password)`
///
/// ## Errors
/// - When failed to get the password from stdin
pub fn user_password(password: Option<Option<String>>) -> LprsResult<Option<String>> {
Ok(match password {
None => None,
Some(Some(p)) => Some(p),
Some(None) => {
log::debug!("User didn't provide a password, prompting it");
Some(
Password::new("Vault password:")
.without_confirmation()
.with_formatter(&|p| "*".repeat(p.chars().count()))
.with_display_mode(PasswordDisplayMode::Masked)
.prompt()?,
)
}
})
}
/// Returns the default vaults json file /// Returns the default vaults json file
/// ///
/// ## Errors /// ## Errors