Compare commits

..

No commits in common. "a6483cf333e6a5f3a0d48317b50c6304cfd956bb" and "e8ed50dd6a1f878843b5cecebded8f98f5617aa2" have entirely different histories.

6 changed files with 52 additions and 91 deletions

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,13 +32,31 @@ 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<()> {
self.vault_info.password = utils::user_password(self.password, "Vault password:")?; match 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,6 +41,7 @@ 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)]
@ -64,13 +65,26 @@ 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()),
utils::user_password(self.password, "New vault password:")? password.as_ref().or(vault.password.as_ref()),
.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

@ -17,10 +17,8 @@
use std::{fs, io::Error as IoError, io::ErrorKind as IoErrorKind, path::PathBuf}; use std::{fs, io::Error as IoError, io::ErrorKind as IoErrorKind, path::PathBuf};
use clap::Args; use clap::Args;
use sha2::Digest;
use crate::{ use crate::{
utils,
vault::{BitWardenPasswords, Format, Vaults}, vault::{BitWardenPasswords, Format, Vaults},
LprsCommand, LprsCommand,
LprsError, LprsError,
@ -32,17 +30,12 @@ use crate::{
/// Export command, used to export the vaults in `lprs` format or `BitWarden` /// Export command, used to export the vaults in `lprs` format or `BitWarden`
/// format. The exported file will be a json file. /// format. The exported file will be a json file.
pub struct Export { pub struct Export {
// TODO: `force` flag to write on existing file
/// The path to export to /// The path to export to
path: PathBuf, path: PathBuf,
/// Format to export vaults in /// Format to export vaults in
#[arg(short, long, value_name = "FORMAT", default_value_t= Format::Lprs)] #[arg(short, long, value_name = "FORMAT", default_value_t= Format::Lprs)]
format: Format, format: Format,
/// Encryption password of the exported vaults (in `lprs` format) // TODO: `force` flag to write on existing file
/// if there is not, will use the master password
#[arg(short = 'p', long)]
#[allow(clippy::option_option)]
encryption_password: Option<Option<String>>,
} }
impl LprsCommand for Export { impl LprsCommand for Export {
@ -53,19 +46,8 @@ impl LprsCommand for Export {
self.path.display(), self.path.display(),
self.format self.format
); );
let encryption_key: Option<[u8; 32]> =
utils::user_password(self.encryption_password, "Encryption Password:")?
.map(|p| sha2::Sha256::digest(p).into());
let exported_data = match self.format { let exported_data = match self.format {
Format::Lprs => { Format::Lprs => vault_manager.json_export()?,
vault_manager.json_export(
encryption_key
.as_ref()
.unwrap_or(&vault_manager.master_password),
)?
}
Format::BitWarden => serde_json::to_string(&BitWardenPasswords::from(vault_manager))?, Format::BitWarden => serde_json::to_string(&BitWardenPasswords::from(vault_manager))?,
}; };
@ -95,11 +77,6 @@ impl LprsCommand for Export {
format!("file `{}` is a directory", self.path.display()), format!("file `{}` is a directory", self.path.display()),
))); )));
} }
if self.encryption_password.is_some() && self.format != Format::Lprs {
return Err(LprsError::Other(
"You only can to use the encryption password with `lprs` format".to_owned(),
));
}
Ok(()) Ok(())
} }

View file

@ -22,10 +22,8 @@ use std::{
}; };
use clap::Args; use clap::Args;
use sha2::Digest;
use crate::{ use crate::{
utils,
vault::{BitWardenPasswords, Format, Vault, Vaults}, vault::{BitWardenPasswords, Format, Vault, Vaults},
LprsCommand, LprsCommand,
LprsError, LprsError,
@ -42,12 +40,7 @@ pub struct Import {
/// The format to import from /// The format to import from
#[arg(short, long, default_value_t = Format::Lprs)] #[arg(short, long, default_value_t = Format::Lprs)]
format: Format, format: Format,
/// Decryption password of the imported vaults (in `lprs` format)
/// if there is not, will use the master password
#[arg(short = 'p', long)]
#[allow(clippy::option_option)]
decryption_password: Option<Option<String>>,
} }
impl LprsCommand for Import { impl LprsCommand for Import {
@ -59,18 +52,10 @@ impl LprsCommand for Import {
vault_manager.vaults_file.display() vault_manager.vaults_file.display()
); );
let decryption_key: Option<[u8; 32]> =
utils::user_password(self.decryption_password, "Decryption password:")?
.map(|p| sha2::Sha256::digest(p).into());
let imported_passwords_len = match self.format { let imported_passwords_len = match self.format {
Format::Lprs => { Format::Lprs => {
let vaults = Vaults::json_reload( let vaults =
decryption_key Vaults::json_reload(&vault_manager.master_password, &fs::read(self.path)?)?;
.as_ref()
.unwrap_or(&vault_manager.master_password),
&fs::read(self.path)?,
)?;
let vaults_len = vaults.len(); let vaults_len = vaults.len();
vault_manager.vaults.extend(vaults); vault_manager.vaults.extend(vaults);
@ -96,7 +81,7 @@ impl LprsCommand for Import {
} }
fn validate_args(&self) -> LprsResult<()> { fn validate_args(&self) -> LprsResult<()> {
if !self if self
.path .path
.extension() .extension()
.is_some_and(|e| e.to_string_lossy().eq_ignore_ascii_case("json")) .is_some_and(|e| e.to_string_lossy().eq_ignore_ascii_case("json"))
@ -118,12 +103,6 @@ impl LprsCommand for Import {
format!("file `{}` is a directory", self.path.display()), format!("file `{}` is a directory", self.path.display()),
))); )));
} }
if self.decryption_password.is_some() && self.format != Format::Lprs {
return Err(LprsError::Other(
"You only can to use the decryption password with `lprs` format".to_owned(),
));
}
Ok(()) Ok(())
} }

View file

@ -16,7 +16,7 @@
use std::{fs, path::PathBuf}; use std::{fs, path::PathBuf};
use inquire::{validator::Validation, Password, PasswordDisplayMode}; use inquire::{validator::Validation, 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,35 +43,6 @@ 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>>,
prompt_message: &str,
) -> 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(prompt_message)
.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

View file

@ -29,7 +29,7 @@ mod bitwarden;
pub use bitwarden::*; pub use bitwarden::*;
#[derive(Clone, Debug, ValueEnum, Eq, PartialEq)] #[derive(Clone, Debug, ValueEnum)]
/// The vaults format /// The vaults format
pub enum Format { pub enum Format {
/// The lprs format, which is the default format /// The lprs format, which is the default format
@ -130,9 +130,11 @@ impl Vaults {
/// - if the encryption failed /// - if the encryption failed
/// ///
/// Note: The returned string is `Vec<Vault>` /// Note: The returned string is `Vec<Vault>`
pub fn json_export(&self, encryption_key: &[u8; 32]) -> LprsResult<String> { pub fn json_export(&self) -> LprsResult<String> {
let encrypt = |val: &str| { let encrypt = |val: &str| {
LprsResult::Ok(crate::BASE64.encode(cipher::encrypt(encryption_key, val.as_ref()))) LprsResult::Ok(
crate::BASE64.encode(cipher::encrypt(&self.master_password, val.as_ref())),
)
}; };
serde_json::to_string( serde_json::to_string(