feat: Make the username & password optional in the vault (#12)
Reviewed-on: #12 Co-authored-by: Awiteb <a@4rs.nl> Co-committed-by: Awiteb <a@4rs.nl>
This commit is contained in:
parent
fbf00ebacd
commit
af6664da5c
6 changed files with 57 additions and 129 deletions
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()),
|
||||||
);
|
);
|
||||||
|
|
|
@ -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(())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 }]),
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
||||||
|
|
|
@ -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()?,
|
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue