feat: Make the username & password optional in the vault (#12)
All checks were successful
Write changelog / write-changelog (push) Successful in 3s
Rust CI / Rust CI (push) Successful in 1m19s

Reviewed-on: #12
Co-authored-by: Awiteb <a@4rs.nl>
Co-committed-by: Awiteb <a@4rs.nl>
This commit is contained in:
Awiteb 2024-04-20 16:38:50 +02:00 committed by awiteb
parent fbf00ebacd
commit af6664da5c
6 changed files with 57 additions and 129 deletions

View file

@ -18,7 +18,7 @@ use clap::Args;
use crate::{ use crate::{
vault::{vault_state::*, Vault, Vaults}, vault::{vault_state::*, Vault, Vaults},
LprsResult, RunCommand, LprsError, LprsResult, RunCommand,
}; };
#[derive(Debug, Args)] #[derive(Debug, Args)]
@ -30,6 +30,13 @@ pub struct Add {
impl RunCommand for Add { impl RunCommand for Add {
fn run(&self, mut vault_manager: Vaults<Plain>) -> LprsResult<()> { fn run(&self, mut vault_manager: Vaults<Plain>) -> 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.add_vault(self.vault_info.clone());
vault_manager.try_export() vault_manager.try_export()
} }

View file

@ -63,8 +63,8 @@ impl RunCommand for Edit {
} else { } else {
*vault = Vault::<Plain>::new( *vault = Vault::<Plain>::new(
self.name.as_ref().unwrap_or(&vault.name), self.name.as_ref().unwrap_or(&vault.name),
self.username.as_ref().unwrap_or(&vault.username), self.username.as_ref().or(vault.username.as_ref()),
self.password.as_ref().unwrap_or(&vault.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

@ -17,8 +17,6 @@
use std::num::NonZeroU64; use std::num::NonZeroU64;
use clap::Args; use clap::Args;
use comfy_table::Table;
use regex::Regex;
use crate::{ use crate::{
vault::{vault_state::*, Vaults}, vault::{vault_state::*, Vaults},
@ -54,90 +52,11 @@ pub struct List {
impl RunCommand for List { impl RunCommand for List {
fn run(&self, vault_manager: Vaults<Plain>) -> LprsResult<()> { fn run(&self, vault_manager: Vaults<Plain>) -> LprsResult<()> {
if vault_manager.vaults.is_empty() { if vault_manager.vaults.is_empty() {
Err(LprsError::Other( return Err(LprsError::Other(
"Looks like there is no passwords to list".to_owned(), "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(); todo!("https://git.4rs.nl/awiteb/lprs/issues/8")
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(())
}
} }
} }

View file

@ -43,17 +43,13 @@ impl From<BitWardenPassword> for Vault<Plain> {
fn from(value: BitWardenPassword) -> Self { fn from(value: BitWardenPassword) -> Self {
Self::new( Self::new(
value.name, value.name,
value value.login.as_ref().and_then(|l| l.username.as_ref()),
.login value.login.as_ref().and_then(|l| l.password.as_ref()),
value.login.as_ref().and_then(|l| {
l.uris
.as_ref() .as_ref()
.map_or_else(String::new, |l| l.username.to_owned().unwrap_or_default()), .and_then(|p| p.first().map(|u| u.uri.clone()))
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.notes, value.notes,
) )
} }
@ -65,8 +61,8 @@ impl From<Vault<Plain>> for BitWardenPassword {
ty: 1, ty: 1,
name: value.name, name: value.name,
login: Some(BitWardenLoginData { login: Some(BitWardenLoginData {
username: Some(value.username), username: value.username,
password: Some(value.password), password: value.password,
uris: value uris: value
.service .service
.map(|s| vec![BitWardenUri { mt: None, uri: s }]), .map(|s| vec![BitWardenUri { mt: None, uri: s }]),

View file

@ -46,3 +46,21 @@ pub fn decrypt(master_password: &[u8], data: &str) -> LprsResult<String> {
}) })
.map(|d| String::from_utf8(d).map_err(LprsError::Utf8))? .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<impl AsRef<str>>,
) -> LprsResult<Option<String>> {
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<impl AsRef<str>>,
) -> LprsResult<Option<String>> {
data.map(|d| decrypt(master_password, d.as_ref()))
.transpose()
}

View file

@ -58,10 +58,10 @@ where
pub name: String, pub name: String,
/// The username /// The username
#[arg(short, long)] #[arg(short, long)]
pub username: String, pub username: Option<String>,
/// The password /// The password
#[arg(short, long)] #[arg(short, long)]
pub password: String, pub password: Option<String>,
/// The service name. e.g the website url /// The service name. e.g the website url
#[arg(short, long)] #[arg(short, long)]
pub service: Option<String>, pub service: Option<String>,
@ -96,17 +96,17 @@ where
/// Create new [`Vault`] instance /// Create new [`Vault`] instance
pub fn new( pub fn new(
name: impl Into<String>, name: impl Into<String>,
username: impl Into<String>, username: Option<impl Into<String>>,
password: impl Into<String>, password: Option<impl Into<String>>,
service: Option<impl Into<String>>, service: Option<impl Into<String>>,
note: Option<impl Into<String>>, note: Option<impl Into<String>>,
) -> Self { ) -> Self {
Self { Self {
name: name.into(), name: name.into(),
username: username.into(), username: username.map(Into::into),
password: password.into(), password: password.map(Into::into),
service: service.map(|s| s.into()), service: service.map(Into::into),
note: note.map(|s| s.into()), note: note.map(Into::into),
phantom: std::marker::PhantomData, phantom: std::marker::PhantomData,
} }
} }
@ -117,16 +117,10 @@ impl Vault<Encrypted> {
pub fn decrypt(&self, master_password: &[u8]) -> LprsResult<Vault<Plain>> { pub fn decrypt(&self, master_password: &[u8]) -> LprsResult<Vault<Plain>> {
Ok(Vault::<Plain>::new( Ok(Vault::<Plain>::new(
cipher::decrypt(master_password, &self.name)?, cipher::decrypt(master_password, &self.name)?,
cipher::decrypt(master_password, &self.username)?, cipher::decrypt_some(master_password, self.username.as_ref())?,
cipher::decrypt(master_password, &self.password)?, cipher::decrypt_some(master_password, self.password.as_ref())?,
self.service cipher::decrypt_some(master_password, self.service.as_ref())?,
.as_ref() cipher::decrypt_some(master_password, self.note.as_ref())?,
.map(|url| cipher::decrypt(master_password, url))
.transpose()?,
self.note
.as_ref()
.map(|note| cipher::decrypt(master_password, note))
.transpose()?,
)) ))
} }
} }
@ -136,16 +130,10 @@ impl Vault<Plain> {
pub fn encrypt(&self, master_password: &[u8]) -> LprsResult<Vault<Encrypted>> { pub fn encrypt(&self, master_password: &[u8]) -> LprsResult<Vault<Encrypted>> {
Ok(Vault::<Encrypted>::new( Ok(Vault::<Encrypted>::new(
cipher::encrypt(master_password, &self.name)?, cipher::encrypt(master_password, &self.name)?,
cipher::encrypt(master_password, &self.username)?, cipher::encrypt_some(master_password, self.username.as_ref())?,
cipher::encrypt(master_password, &self.password)?, cipher::encrypt_some(master_password, self.password.as_ref())?,
self.service cipher::encrypt_some(master_password, self.service.as_ref())?,
.as_ref() cipher::encrypt_some(master_password, self.note.as_ref())?,
.map(|url| cipher::encrypt(master_password, url))
.transpose()?,
self.note
.as_ref()
.map(|note| cipher::encrypt(master_password, note))
.transpose()?,
)) ))
} }
} }