From af6664da5c08cc39cf732d64ba74de1731095723 Mon Sep 17 00:00:00 2001 From: Awiteb Date: Sat, 20 Apr 2024 16:38:50 +0200 Subject: [PATCH] feat: Make the username & password optional in the vault (#12) Reviewed-on: https://git.4rs.nl/awiteb/lprs/pulls/12 Co-authored-by: Awiteb Co-committed-by: Awiteb --- src/cli/add_command.rs | 9 ++++- src/cli/edit_command.rs | 4 +- src/cli/list_command.rs | 89 ++--------------------------------------- src/vault/bitwarden.rs | 22 +++++----- src/vault/cipher.rs | 18 +++++++++ src/vault/mod.rs | 44 ++++++++------------ 6 files changed, 57 insertions(+), 129 deletions(-) diff --git a/src/cli/add_command.rs b/src/cli/add_command.rs index 7b5a5c9..25009c6 100644 --- a/src/cli/add_command.rs +++ b/src/cli/add_command.rs @@ -18,7 +18,7 @@ use clap::Args; use crate::{ vault::{vault_state::*, Vault, Vaults}, - LprsResult, RunCommand, + LprsError, LprsResult, RunCommand, }; #[derive(Debug, Args)] @@ -30,6 +30,13 @@ pub struct Add { impl RunCommand for Add { fn run(&self, mut vault_manager: Vaults) -> LprsResult<()> { + if self.vault_info.username.is_none() + && self.vault_info.password.is_none() + && self.vault_info.service.is_none() + && self.vault_info.note.is_none() + { + return Err(LprsError::Other("You can't add empty vault".to_owned())); + } vault_manager.add_vault(self.vault_info.clone()); vault_manager.try_export() } diff --git a/src/cli/edit_command.rs b/src/cli/edit_command.rs index d8cdd88..a7d393d 100644 --- a/src/cli/edit_command.rs +++ b/src/cli/edit_command.rs @@ -63,8 +63,8 @@ impl RunCommand for Edit { } else { *vault = Vault::::new( self.name.as_ref().unwrap_or(&vault.name), - self.username.as_ref().unwrap_or(&vault.username), - self.password.as_ref().unwrap_or(&vault.password), + self.username.as_ref().or(vault.username.as_ref()), + self.password.as_ref().or(vault.password.as_ref()), self.service.as_ref().or(vault.service.as_ref()), self.note.as_ref().or(vault.note.as_ref()), ); diff --git a/src/cli/list_command.rs b/src/cli/list_command.rs index a7550c9..c26483c 100644 --- a/src/cli/list_command.rs +++ b/src/cli/list_command.rs @@ -17,8 +17,6 @@ use std::num::NonZeroU64; use clap::Args; -use comfy_table::Table; -use regex::Regex; use crate::{ vault::{vault_state::*, Vaults}, @@ -54,90 +52,11 @@ pub struct List { impl RunCommand for List { fn run(&self, vault_manager: Vaults) -> LprsResult<()> { if vault_manager.vaults.is_empty() { - Err(LprsError::Other( + return Err(LprsError::Other( "Looks like there is no passwords to list".to_owned(), - )) - } else { - if self.get.is_some() && self.search.is_some() { - return Err(LprsError::ArgsConflict( - "You cannot use `--get` arg with `--search` arg".to_owned(), - )); - } - if self.regex && self.search.is_none() { - return Err(LprsError::ArgsConflict( - "You cannot use `--regex` without `--search` arg".to_owned(), - )); - } - - let mut table = Table::new(); - let mut header = vec!["Index", "Name", "Username", "Password"]; - if self.with_service { - header.push("Service"); - } - if self.with_note { - header.push("Note"); - } - let re = Regex::new(self.search.as_deref().unwrap_or("."))?; - - table.set_header(header); - let vaults = vault_manager - .vaults - .iter() - .enumerate() - .filter(|(idx, pass)| { - if let Some(index) = self.get { - return (idx + 1) == index.get() as usize; - } - if let Some(ref pattern) = self.search { - if self.regex { - return re.is_match(&pass.name) - || re.is_match(&pass.username) - || (self.with_service - && pass.service.as_ref().is_some_and(|s| re.is_match(s))) - || (self.with_note - && pass.note.as_ref().is_some_and(|n| re.is_match(n))); - } else { - let pattern = pattern.to_lowercase(); - return pass.name.to_lowercase().contains(&pattern) - || pass.username.to_lowercase().contains(&pattern) - || (self.with_service - && pass - .service - .as_ref() - .is_some_and(|s| s.to_lowercase().contains(&pattern))) - || (self.with_note - && pass - .note - .as_ref() - .is_some_and(|n| n.to_lowercase().contains(&pattern))); - } - } - - true - }); - for (idx, vault) in vaults { - let hide_password = "*".repeat(vault.password.chars().count()); - let idx = (idx + 1).to_string(); - let mut row = vec![ - idx.as_str(), - vault.name.as_str(), - vault.username.as_str(), - if self.unhide_password { - vault.password.as_str() - } else { - hide_password.as_str() - }, - ]; - if self.with_service { - row.push(vault.service.as_deref().unwrap_or("Not Set")) - } - if self.with_note { - row.push(vault.note.as_deref().unwrap_or("Not Set")) - } - table.add_row(row); - } - println!("{table}"); - Ok(()) + )); } + + todo!("https://git.4rs.nl/awiteb/lprs/issues/8") } } diff --git a/src/vault/bitwarden.rs b/src/vault/bitwarden.rs index 7c4670c..f287464 100644 --- a/src/vault/bitwarden.rs +++ b/src/vault/bitwarden.rs @@ -43,17 +43,13 @@ impl From for Vault { fn from(value: BitWardenPassword) -> Self { Self::new( value.name, - value - .login - .as_ref() - .map_or_else(String::new, |l| l.username.to_owned().unwrap_or_default()), - value - .login - .as_ref() - .map_or_else(String::new, |l| l.password.to_owned().unwrap_or_default()), - value - .login - .and_then(|l| l.uris.and_then(|p| p.first().map(|u| u.uri.clone()))), + value.login.as_ref().and_then(|l| l.username.as_ref()), + value.login.as_ref().and_then(|l| l.password.as_ref()), + value.login.as_ref().and_then(|l| { + l.uris + .as_ref() + .and_then(|p| p.first().map(|u| u.uri.clone())) + }), value.notes, ) } @@ -65,8 +61,8 @@ impl From> for BitWardenPassword { ty: 1, name: value.name, login: Some(BitWardenLoginData { - username: Some(value.username), - password: Some(value.password), + username: value.username, + password: value.password, uris: value .service .map(|s| vec![BitWardenUri { mt: None, uri: s }]), diff --git a/src/vault/cipher.rs b/src/vault/cipher.rs index 8e66c71..39a5c21 100644 --- a/src/vault/cipher.rs +++ b/src/vault/cipher.rs @@ -46,3 +46,21 @@ pub fn decrypt(master_password: &[u8], data: &str) -> LprsResult { }) .map(|d| String::from_utf8(d).map_err(LprsError::Utf8))? } + +/// Encrypt if the `Option` are `Some` +pub fn encrypt_some( + master_password: &[u8], + data: Option>, +) -> LprsResult> { + data.map(|d| encrypt(master_password, d.as_ref())) + .transpose() +} + +/// Decrypt if the `Option` are `Some` +pub fn decrypt_some( + master_password: &[u8], + data: Option>, +) -> LprsResult> { + data.map(|d| decrypt(master_password, d.as_ref())) + .transpose() +} diff --git a/src/vault/mod.rs b/src/vault/mod.rs index 2e4bacd..5f47c53 100644 --- a/src/vault/mod.rs +++ b/src/vault/mod.rs @@ -58,10 +58,10 @@ where pub name: String, /// The username #[arg(short, long)] - pub username: String, + pub username: Option, /// The password #[arg(short, long)] - pub password: String, + pub password: Option, /// The service name. e.g the website url #[arg(short, long)] pub service: Option, @@ -96,17 +96,17 @@ where /// Create new [`Vault`] instance pub fn new( name: impl Into, - username: impl Into, - password: impl Into, + username: Option>, + password: Option>, service: Option>, note: Option>, ) -> Self { Self { name: name.into(), - username: username.into(), - password: password.into(), - service: service.map(|s| s.into()), - note: note.map(|s| s.into()), + username: username.map(Into::into), + password: password.map(Into::into), + service: service.map(Into::into), + note: note.map(Into::into), phantom: std::marker::PhantomData, } } @@ -117,16 +117,10 @@ impl Vault { pub fn decrypt(&self, master_password: &[u8]) -> LprsResult> { Ok(Vault::::new( cipher::decrypt(master_password, &self.name)?, - cipher::decrypt(master_password, &self.username)?, - cipher::decrypt(master_password, &self.password)?, - self.service - .as_ref() - .map(|url| cipher::decrypt(master_password, url)) - .transpose()?, - self.note - .as_ref() - .map(|note| cipher::decrypt(master_password, note)) - .transpose()?, + cipher::decrypt_some(master_password, self.username.as_ref())?, + cipher::decrypt_some(master_password, self.password.as_ref())?, + cipher::decrypt_some(master_password, self.service.as_ref())?, + cipher::decrypt_some(master_password, self.note.as_ref())?, )) } } @@ -136,16 +130,10 @@ impl Vault { pub fn encrypt(&self, master_password: &[u8]) -> LprsResult> { Ok(Vault::::new( cipher::encrypt(master_password, &self.name)?, - cipher::encrypt(master_password, &self.username)?, - cipher::encrypt(master_password, &self.password)?, - self.service - .as_ref() - .map(|url| cipher::encrypt(master_password, url)) - .transpose()?, - self.note - .as_ref() - .map(|note| cipher::encrypt(master_password, note)) - .transpose()?, + cipher::encrypt_some(master_password, self.username.as_ref())?, + cipher::encrypt_some(master_password, self.password.as_ref())?, + cipher::encrypt_some(master_password, self.service.as_ref())?, + cipher::encrypt_some(master_password, self.note.as_ref())?, )) } }