chore(DX): Improve the DX #27

Merged
awiteb merged 9 commits from awiteb/improve-dx into master 2024-05-06 23:19:20 +02:00 AGit
15 changed files with 78 additions and 62 deletions
Showing only changes of commit 3436e616f8 - Show all commits

View file

@ -19,7 +19,9 @@ use inquire::{Password, PasswordDisplayMode};
use crate::{ use crate::{
vault::{Vault, Vaults}, vault::{Vault, Vaults},
LprsCommand, LprsError, LprsResult, LprsCommand,
LprsError,
LprsResult,
}; };
#[derive(Debug, Args)] #[derive(Debug, Args)]
@ -32,7 +34,7 @@ pub struct Add {
#[arg(short, long)] #[arg(short, long)]
// FIXME: I think replacing `Option<Option<String>>` with custom type will be better // 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 {

View file

@ -21,7 +21,9 @@ use inquire::{Password, PasswordDisplayMode};
use crate::{ use crate::{
vault::{Vault, Vaults}, vault::{Vault, Vaults},
LprsCommand, LprsError, LprsResult, LprsCommand,
LprsError,
LprsResult,
}; };
#[derive(Debug, Args)] #[derive(Debug, Args)]
@ -33,7 +35,7 @@ pub struct Edit {
#[arg(short, long)] #[arg(short, long)]
/// The new vault name /// The new vault name
name: Option<String>, name: Option<String>,
#[arg(short, long)] #[arg(short, long)]
/// The new vault username /// The new vault username
username: Option<String>, username: Option<String>,
@ -44,10 +46,10 @@ pub struct Edit {
password: Option<Option<String>>, password: Option<Option<String>>,
#[arg(short, long)] #[arg(short, long)]
/// The new vault service /// The new vault service
service: Option<String>, service: Option<String>,
#[arg(short = 'o', long)] #[arg(short = 'o', long)]
/// The new vault note /// The new vault note
note: Option<String>, note: Option<String>,
} }
impl LprsCommand for Edit { impl LprsCommand for Edit {
@ -66,13 +68,15 @@ impl LprsCommand for Edit {
// Get the password from stdin or from its value if provided // Get the password from stdin or from its value if provided
let password = match self.password { let password = match self.password {
Some(Some(password)) => Some(password), Some(Some(password)) => Some(password),
Some(None) => Some( Some(None) => {
Password::new("New vault password:") Some(
.without_confirmation() Password::new("New vault password:")
.with_formatter(&|p| "*".repeat(p.chars().count())) .without_confirmation()
.with_display_mode(PasswordDisplayMode::Masked) .with_formatter(&|p| "*".repeat(p.chars().count()))
.prompt()?, .with_display_mode(PasswordDisplayMode::Masked)
), .prompt()?,
)
}
None => None, None => None,
}; };

View file

@ -20,16 +20,18 @@ use clap::Args;
use crate::{ use crate::{
vault::{BitWardenPasswords, Format, Vaults}, vault::{BitWardenPasswords, Format, Vaults},
LprsCommand, LprsError, LprsResult, LprsCommand,
LprsError,
LprsResult,
}; };
#[derive(Debug, Args)] #[derive(Debug, Args)]
#[command(author, version, about, long_about = None)] #[command(author, version, about, long_about = None)]
/// Export command, used to export the vaults in `lprs` format or `BitWarden` format. /// Export command, used to export the vaults in `lprs` format or `BitWarden`
/// The exported file will be a json file. /// format. The exported file will be a json file.
pub struct Export { pub struct Export {
/// 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,

View file

@ -36,10 +36,10 @@ pub struct Gen {
lowercase: bool, lowercase: bool,
/// With numbers (0-9) /// With numbers (0-9)
#[arg(short, long)] #[arg(short, long)]
numbers: bool, numbers: bool,
/// With symbols (!,# ...) /// With symbols (!,# ...)
#[arg(short, long)] #[arg(short, long)]
symbols: bool, symbols: bool,
} }
impl LprsCommand for Gen { impl LprsCommand for Gen {

View file

@ -25,12 +25,15 @@ use clap::Args;
use crate::{ use crate::{
vault::{BitWardenPasswords, Format, Vault, Vaults}, vault::{BitWardenPasswords, Format, Vault, Vaults},
LprsCommand, LprsError, LprsResult, LprsCommand,
LprsError,
LprsResult,
}; };
#[derive(Debug, Args)] #[derive(Debug, Args)]
#[command(author, version, about, long_about = None)] #[command(author, version, about, long_about = None)]
/// Import command, used to import vaults from the exported files, `lprs` or `BitWarden` /// Import command, used to import vaults from the exported files, `lprs` or
/// `BitWarden`
pub struct Import { pub struct Import {
/// The file path to import from /// The file path to import from
path: PathBuf, path: PathBuf,

View file

@ -27,13 +27,13 @@ use crate::{vault::Vaults, LprsCommand, LprsError, LprsResult};
pub struct List { pub struct List {
/// Return the password with spesifc index /// Return the password with spesifc index
#[arg(short, long, value_name = "INDEX")] #[arg(short, long, value_name = "INDEX")]
get: Option<NonZeroU64>, get: Option<NonZeroU64>,
/// Filter the select list /// Filter the select list
#[arg(short, long, value_name = "TEXT")] #[arg(short, long, value_name = "TEXT")]
filter: Option<String>, filter: Option<String>,
/// Enable regex when use `--filter` option /// Enable regex when use `--filter` option
#[arg(short, long)] #[arg(short, long)]
regex: bool, regex: bool,
} }
impl LprsCommand for List { impl LprsCommand for List {

View file

@ -31,7 +31,8 @@ pub mod edit_command;
pub mod export_command; pub mod export_command;
/// Generate command, used to generate a password /// Generate command, used to generate a password
pub mod gen_command; pub mod gen_command;
/// Import command, used to import vaults from the exported files, `lprs` or `BitWarden` /// Import command, used to import vaults from the exported files, `lprs` or
/// `BitWarden`
pub mod import_command; pub mod import_command;
/// List command, used to list the vaults and search /// List command, used to list the vaults and search
pub mod list_command; pub mod list_command;
@ -72,7 +73,7 @@ pub struct Cli {
pub vaults_file: Option<PathBuf>, pub vaults_file: Option<PathBuf>,
/// Show the logs in the stdout /// Show the logs in the stdout
#[arg(short, long)] #[arg(short, long)]
pub verbose: bool, pub verbose: bool,
#[command(subcommand)] #[command(subcommand)]
/// The provided command to run /// The provided command to run

View file

@ -27,7 +27,8 @@ pub struct Remove {
/// The password index /// The password index
index: NonZeroU64, index: NonZeroU64,
/// Force remove, will not return error if there is no password with this index /// Force remove, will not return error if there is no password with this
/// index
#[arg(short, long)] #[arg(short, long)]
force: bool, force: bool,
} }

View file

@ -24,7 +24,10 @@ pub type Result<T> = result::Result<T, Error>;
pub enum Error { pub enum Error {
#[error("Encryption Error: {0}")] #[error("Encryption Error: {0}")]
Encryption(String), Encryption(String),
#[error("Decryption Error: The given key cannot decrypt the given data. Either the data has been tampered with or the key is incorrect.")] #[error(
"Decryption Error: The given key cannot decrypt the given data. Either the data has been \
tampered with or the key is incorrect."
)]
Decryption, Decryption,
#[error("Wrong Master Password Error: Wrong decryption password")] #[error("Wrong Master Password Error: Wrong decryption password")]
WrongMasterPassword, WrongMasterPassword,

View file

@ -29,10 +29,7 @@
/// #### Output /// #### Output
/// ```rust /// ```rust
/// impl crate::LprsCommand for TestCommands { /// impl crate::LprsCommand for TestCommands {
/// fn run( /// fn run(&self, vault_manager: crate::vault::Vaults) -> crate::LprsResult<()> {
/// &self,
/// vault_manager: crate::vault::Vaults,
/// ) -> crate::LprsResult<()> {
/// match self { /// match self {
/// Self::Test(command) => command.run(vault_manager), /// Self::Test(command) => command.run(vault_manager),
/// Self::Some(command) => command.run(vault_manager), /// Self::Some(command) => command.run(vault_manager),

View file

@ -15,11 +15,12 @@
// 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>.
#![doc = include_str!("../README.md")] #![doc = include_str!("../README.md")]
use clap::Parser;
use inquire::InquireError;
use std::env; use std::env;
use std::process::ExitCode; use std::process::ExitCode;
use clap::Parser;
use inquire::InquireError;
/// The main module of the lprs crate, contains the cli and the commands. /// The main module of the lprs crate, contains the cli and the commands.
pub mod cli; pub mod cli;
/// The errors module, contains the errors and the result type. /// The errors module, contains the errors and the result type.

View file

@ -18,10 +18,9 @@ use std::{fs, path::PathBuf};
use inquire::{validator::Validation, PasswordDisplayMode}; use inquire::{validator::Validation, PasswordDisplayMode};
use passwords::{analyzer, scorer}; use passwords::{analyzer, scorer};
use sha2::Digest;
#[cfg(feature = "update-notify")] #[cfg(feature = "update-notify")]
use reqwest::blocking::Client as BlockingClient; use reqwest::blocking::Client as BlockingClient;
use sha2::Digest;
use crate::{LprsError, LprsResult}; use crate::{LprsError, LprsResult};
@ -64,8 +63,8 @@ pub fn vaults_file() -> LprsResult<PathBuf> {
/// - Its score must be greater than 80.0 /// - Its score must be greater than 80.0
/// ///
/// ## Errors /// ## Errors
/// - There is no errors, just the return type of inquire validator /// - There is no errors, just the return type of inquire validator must be
/// must be Result<Validation, inquire::CustomUserError> /// Result<Validation, inquire::CustomUserError>
pub fn password_validator(password: &str) -> Result<Validation, inquire::CustomUserError> { pub fn password_validator(password: &str) -> Result<Validation, inquire::CustomUserError> {
let analyzed = analyzer::analyze(password); let analyzed = analyzer::analyze(password);
Ok(if analyzed.length() < 15 { Ok(if analyzed.length() < 15 {

View file

@ -14,8 +14,8 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// 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>.
// This file is not important, it is just a struct that is used to serialize and deserialize the vaults // This file is not important, it is just a struct that is used to serialize and
// from and to the BitWarden format. // deserialize the vaults from and to the BitWarden format.
#![allow(missing_docs)] #![allow(missing_docs)]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -26,27 +26,27 @@ use super::{Vault, Vaults};
pub struct BitWardenLoginData { pub struct BitWardenLoginData {
pub username: Option<String>, pub username: Option<String>,
pub password: Option<String>, pub password: Option<String>,
pub uris: Option<Vec<BitWardenUri>>, pub uris: Option<Vec<BitWardenUri>>,
} }
#[derive(Clone, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
pub struct BitWardenUri { pub struct BitWardenUri {
#[serde(rename = "match")] #[serde(rename = "match")]
pub mt: Option<i32>, pub mt: Option<i32>,
pub uri: String, pub uri: String,
} }
#[derive(Default, Deserialize, Serialize)] #[derive(Default, Deserialize, Serialize)]
pub struct BitWardenFolder { pub struct BitWardenFolder {
pub id: String, pub id: String,
pub name: String, pub name: String,
} }
#[derive(Clone, Debug, Deserialize, Serialize)] #[derive(Clone, Debug, Deserialize, Serialize)]
pub struct BitWardenPassword { pub struct BitWardenPassword {
#[serde(rename = "type")] #[serde(rename = "type")]
pub ty: i32, pub ty: i32,
pub name: String, pub name: String,
pub login: Option<BitWardenLoginData>, pub login: Option<BitWardenLoginData>,
pub notes: Option<String>, pub notes: Option<String>,
} }
@ -55,8 +55,8 @@ pub struct BitWardenPassword {
#[derive(Default, Deserialize, Serialize)] #[derive(Default, Deserialize, Serialize)]
pub struct BitWardenPasswords { pub struct BitWardenPasswords {
pub encrypted: bool, pub encrypted: bool,
pub folders: Vec<BitWardenFolder>, pub folders: Vec<BitWardenFolder>,
pub items: Vec<BitWardenPassword>, pub items: Vec<BitWardenPassword>,
} }
impl From<BitWardenPassword> for Vault { impl From<BitWardenPassword> for Vault {
@ -78,12 +78,12 @@ impl From<BitWardenPassword> for Vault {
impl From<Vault> for BitWardenPassword { impl From<Vault> for BitWardenPassword {
fn from(value: Vault) -> Self { fn from(value: Vault) -> Self {
Self { Self {
ty: 1, ty: 1,
name: value.name, name: value.name,
login: Some(BitWardenLoginData { login: Some(BitWardenLoginData {
username: value.username, username: value.username,
password: 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 }]),
}), }),
@ -96,8 +96,8 @@ impl From<Vaults> for BitWardenPasswords {
fn from(value: Vaults) -> Self { fn from(value: Vaults) -> Self {
Self { Self {
encrypted: false, encrypted: false,
folders: Vec::new(), folders: Vec::new(),
items: value items: value
.vaults .vaults
.into_iter() .into_iter()
.map(BitWardenPassword::from) .map(BitWardenPassword::from)

View file

@ -14,9 +14,10 @@
// You should have received a copy of the GNU General Public License // You should have received a copy of the GNU General Public License
// 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 std::time::{SystemTime, UNIX_EPOCH};
use aes::cipher::{block_padding::Pkcs7, BlockDecryptMut, BlockEncryptMut, KeyIvInit}; use aes::cipher::{block_padding::Pkcs7, BlockDecryptMut, BlockEncryptMut, KeyIvInit};
use rand::{rngs::StdRng, Rng, SeedableRng}; use rand::{rngs::StdRng, Rng, SeedableRng};
use std::time::{SystemTime, UNIX_EPOCH};
use crate::{LprsError, LprsResult}; use crate::{LprsError, LprsResult};

View file

@ -33,9 +33,11 @@ pub use bitwarden::*;
/// 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
/// and is is the result of the serialization/deserialization of the Vaults struct /// and is is the result of the serialization/deserialization of the Vaults
/// struct
Lprs, Lprs,
/// The BitWarden format, which is the result of the serialization/deserialization of the BitWardenPasswords struct /// The BitWarden format, which is the result of the
/// serialization/deserialization of the BitWardenPasswords struct
BitWarden, BitWarden,
} }
@ -44,7 +46,7 @@ pub enum Format {
pub struct Vault { pub struct Vault {
/// The name of the vault /// The name of the vault
#[arg(short, long)] #[arg(short, long)]
pub name: String, pub name: String,
/// The username /// The username
#[arg(short, long)] #[arg(short, long)]
pub username: Option<String>, pub username: Option<String>,
@ -53,10 +55,10 @@ pub struct Vault {
pub password: Option<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>,
/// Add a note to the vault /// Add a note to the vault
#[arg(short = 'o', long)] #[arg(short = 'o', long)]
pub note: Option<String>, pub note: Option<String>,
} }
/// The vaults manager /// The vaults manager
@ -65,9 +67,9 @@ pub struct Vaults {
/// Hash of the master password /// Hash of the master password
pub master_password: [u8; 32], pub master_password: [u8; 32],
/// The json vaults file /// The json vaults file
pub vaults_file: PathBuf, pub vaults_file: PathBuf,
/// The vaults /// The vaults
pub vaults: Vec<Vault>, pub vaults: Vec<Vault>,
} }
impl Vault { impl Vault {
@ -80,11 +82,11 @@ impl Vault {
note: Option<impl Into<String>>, note: Option<impl Into<String>>,
) -> Self { ) -> Self {
Self { Self {
name: name.into(), name: name.into(),
username: username.map(Into::into), username: username.map(Into::into),
password: password.map(Into::into), password: password.map(Into::into),
service: service.map(Into::into), service: service.map(Into::into),
note: note.map(Into::into), note: note.map(Into::into),
} }
} }