chore(DX): Improve the DX #27
15 changed files with 78 additions and 62 deletions
|
@ -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 {
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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};
|
||||||
|
|
||||||
|
|
|
@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue