From 4def4aadb20cc367d57466dc5e88c3043e468d20 Mon Sep 17 00:00:00 2001 From: Awiteb Date: Mon, 18 Mar 2024 23:32:48 +0300 Subject: [PATCH] refactor: Add a ecryption state to the vault Also - Rename `password`somthing to `vault`somthing - Rename the `password` mod to `vault` BREAKING-CHANGE: Moving from password to vault ISSUE: https://git.4rs.nl/awiteb/lprs/issues/4 --- src/cli/add_command.rs | 2 +- src/cli/clean_command.rs | 9 +- src/cli/edit_command.rs | 6 +- src/cli/export_command.rs | 8 +- src/cli/gen_command.rs | 7 +- src/cli/import_command.rs | 4 +- src/cli/list_command.rs | 9 +- src/cli/mod.rs | 28 ++--- src/cli/remove_command.rs | 11 +- src/errors.rs | 5 +- src/macros.rs | 2 +- src/main.rs | 4 +- src/password/mod.rs | 157 --------------------------- src/traits.rs | 7 +- src/utils.rs | 12 +- src/{password => vault}/bitwarden.rs | 28 ++--- src/{password => vault}/cipher.rs | 0 src/{password => vault}/validator.rs | 8 +- 18 files changed, 84 insertions(+), 223 deletions(-) delete mode 100644 src/password/mod.rs rename src/{password => vault}/bitwarden.rs (82%) rename src/{password => vault}/cipher.rs (100%) rename src/{password => vault}/validator.rs (81%) diff --git a/src/cli/add_command.rs b/src/cli/add_command.rs index 46b836b..8bd052f 100644 --- a/src/cli/add_command.rs +++ b/src/cli/add_command.rs @@ -17,7 +17,7 @@ use clap::Args; use crate::{ - password::{Vault, Vaults}, + vault::{vault_state::*, Vault, Vaults}, LprsResult, RunCommand, }; diff --git a/src/cli/clean_command.rs b/src/cli/clean_command.rs index ca49ee7..683fa9d 100644 --- a/src/cli/clean_command.rs +++ b/src/cli/clean_command.rs @@ -18,14 +18,17 @@ use std::fs; use clap::Args; -use crate::{password::Vaults, LprsError, LprsResult, RunCommand}; +use crate::{ + vault::{vault_state::*, Vaults}, + LprsError, LprsResult, RunCommand, +}; #[derive(Debug, Args)] #[command(author, version, about, long_about = None)] pub struct Clean {} impl RunCommand for Clean { - fn run(&self, password_manager: Vaults) -> LprsResult<()> { - fs::write(password_manager.passwords_file, "[]").map_err(LprsError::Io) + fn run(&self, password_manager: Vaults) -> LprsResult<()> { + fs::write(password_manager.vaults_file, "[]").map_err(LprsError::Io) } } diff --git a/src/cli/edit_command.rs b/src/cli/edit_command.rs index 4336a10..df99815 100644 --- a/src/cli/edit_command.rs +++ b/src/cli/edit_command.rs @@ -19,7 +19,7 @@ use std::num::NonZeroU64; use clap::Args; use crate::{ - password::{Vault, Vaults}, + vault::{vault_state::*, Vault, Vaults}, LprsError, LprsResult, RunCommand, }; @@ -47,7 +47,7 @@ pub struct Edit { } impl RunCommand for Edit { - fn run(&self, mut password_manager: Vaults) -> LprsResult<()> { + fn run(&self, mut password_manager: Vaults) -> LprsResult<()> { let index = self.index.get() as usize; if let Some(vault) = vault_manager.vaults.get_mut(index - 1) { @@ -74,7 +74,7 @@ impl RunCommand for Edit { Err(LprsError::InvalidVaultIndex(format!( "The index `{}` is greater than the vaults count {}", self.index, - password_manager.passwords.len() + password_manager.vaults.len() ))) } } diff --git a/src/cli/export_command.rs b/src/cli/export_command.rs index 8c5fb31..555b214 100644 --- a/src/cli/export_command.rs +++ b/src/cli/export_command.rs @@ -19,7 +19,7 @@ use std::{fs, io::Error as IoError, io::ErrorKind as IoErrorKind, path::PathBuf} use clap::Args; use crate::{ - password::{BitWardenPasswords, Format, Vaults}, + vault::{vault_state::*, BitWardenPasswords, Format, Vault, Vaults}, LprsError, LprsResult, RunCommand, }; @@ -34,7 +34,7 @@ pub struct Export { } impl RunCommand for Export { - fn run(&self, password_manager: Vaults) -> LprsResult<()> { + fn run(&self, password_manager: Vaults) -> LprsResult<()> { if self .path .extension() @@ -42,7 +42,9 @@ impl RunCommand for Export { { if !self.path.exists() { let exported_data = match self.format { - Format::Lprs => serde_json::to_string(&password_manager.encrypt()?.passwords), + Format::Lprs => serde_json::to_string::>>( + &password_manager.encrypt_vaults()?, + ), Format::BitWarden => { serde_json::to_string(&BitWardenPasswords::from(password_manager)) } diff --git a/src/cli/gen_command.rs b/src/cli/gen_command.rs index 7dd7c96..41b5c54 100644 --- a/src/cli/gen_command.rs +++ b/src/cli/gen_command.rs @@ -18,7 +18,10 @@ use std::num::NonZeroU64; use clap::Args; -use crate::{password::Vaults, LprsError, LprsResult, RunCommand}; +use crate::{ + vault::{vault_state::*, Vaults}, + LprsError, LprsResult, RunCommand, +}; #[derive(Debug, Args)] #[command(author, version, about, long_about = None)] @@ -42,7 +45,7 @@ pub struct Gen { } impl RunCommand for Gen { - fn run(&self, _password_manager: Vaults) -> LprsResult<()> { + fn run(&self, _password_manager: Vaults) -> LprsResult<()> { if self.uppercase || self.lowercase || self.numbers || self.symbols { println!( "{}", diff --git a/src/cli/import_command.rs b/src/cli/import_command.rs index 04a54d0..4e559dd 100644 --- a/src/cli/import_command.rs +++ b/src/cli/import_command.rs @@ -19,7 +19,7 @@ use std::{fs::File, io::Error as IoError, io::ErrorKind as IoErrorKind, path::Pa use clap::Args; use crate::{ - password::{BitWardenPasswords, Format, Vault, Vaults}, + vault::{vault_state::*, BitWardenPasswords, Format, Vault, Vaults}, LprsError, LprsResult, RunCommand, }; @@ -35,7 +35,7 @@ pub struct Import { } impl RunCommand for Import { - fn run(&self, mut password_manager: Vaults) -> LprsResult<()> { + fn run(&self, mut password_manager: Vaults) -> LprsResult<()> { if self.path.exists() { if self .path diff --git a/src/cli/list_command.rs b/src/cli/list_command.rs index 3015a04..e36609f 100644 --- a/src/cli/list_command.rs +++ b/src/cli/list_command.rs @@ -20,7 +20,10 @@ use clap::Args; use comfy_table::Table; use regex::Regex; -use crate::{password::Vaults, LprsError, LprsResult, RunCommand}; +use crate::{ + vault::{vault_state::*, Vaults}, + LprsError, LprsResult, RunCommand, +}; #[derive(Debug, Args)] #[command(author, version, about, long_about = None)] @@ -49,8 +52,8 @@ pub struct List { } impl RunCommand for List { - fn run(&self, password_manager: Vaults) -> LprsResult<()> { - if password_manager.passwords.is_empty() { + fn run(&self, password_manager: Vaults) -> LprsResult<()> { + if password_manager.vaults.is_empty() { Err(LprsError::Other( "Looks like there is no passwords to list".to_owned(), )) diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 29b1bd4..1221ebb 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -19,7 +19,7 @@ use std::path::PathBuf; use clap::Parser; use crate::{ - password::{self, Vaults}, + vault::{self, Vaults}, LprsError, LprsResult, RunCommand, }; @@ -34,14 +34,14 @@ pub mod remove_command; crate::create_commands!( enum Commands - "Add new password", Add => add_command::Add - "Remove password", Remove => remove_command::Remove - "List your password and search", List => list_command::List - "Clean the password file", Clean => clean_command::Clean - "Edit the password content", Edit => edit_command::Edit - "Generate password", Gen => gen_command::Gen - "Export the passwords", Export => export_command::Export - "Import passwords", Import => import_command::Import + "Add new vault", Add => add_command::Add + "Remove vault", Remove => remove_command::Remove + "List your vaults and search", List => list_command::List + "Clean the vaults file", Clean => clean_command::Clean + "Edit the vault content", Edit => edit_command::Edit + "Generate a password", Gen => gen_command::Gen + "Export the vaults", Export => export_command::Export + "Import vaults", Import => import_command::Import ); #[derive(Parser, Debug)] @@ -49,7 +49,7 @@ crate::create_commands!( pub struct Cli { /// The vaults json file #[arg(short, long)] - passwords_file: Option, + vaults_file: Option, // TODO: verbose flag #[command(subcommand)] @@ -59,15 +59,15 @@ pub struct Cli { impl Cli { /// Run the cli pub fn run(self) -> LprsResult<()> { - let passwords_file = if let Some(ref path) = self.passwords_file { + let vaults_file = if let Some(ref path) = self.vaults_file { path.clone() } else { - crate::utils::passwords_file()? + crate::utils::vaults_file()? }; log::debug!("Getting the vaults file: {}", vaults_file.to_string_lossy()); let vault_manager = if matches!(self.command, Commands::Clean(..) | Commands::Gen(..)) { Vaults { - passwords_file, + vaults_file, ..Default::default() } } else { @@ -88,7 +88,7 @@ impl Cli { let master_password = sha256::digest(master_password); Vaults::try_reload( - passwords_file, + vaults_file, master_password.into_bytes().into_iter().take(32).collect(), )? }; diff --git a/src/cli/remove_command.rs b/src/cli/remove_command.rs index 6c9151b..331cd21 100644 --- a/src/cli/remove_command.rs +++ b/src/cli/remove_command.rs @@ -18,7 +18,10 @@ use std::num::NonZeroU64; use clap::Args; -use crate::{password::Vaults, LprsError, LprsResult, RunCommand}; +use crate::{ + vault::{vault_state::*, Vaults}, + LprsError, LprsResult, RunCommand, +}; #[derive(Debug, Args)] #[command(author, version, about, long_about = None)] @@ -32,16 +35,16 @@ pub struct Remove { } impl RunCommand for Remove { - fn run(&self, mut password_manager: Vaults) -> LprsResult<()> { + fn run(&self, mut password_manager: Vaults) -> LprsResult<()> { let index = (self.index.get() - 1) as usize; - if index > password_manager.passwords.len() { + if index > password_manager.vaults.len() { if !self.force { return Err(LprsError::Other( "The index is greater than the passwords counts".to_owned(), )); } } else { - password_manager.passwords.remove(index); + password_manager.vaults.remove(index); password_manager.try_export()?; } Ok(()) diff --git a/src/errors.rs b/src/errors.rs index 6be1c8f..50d44e6 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -34,8 +34,8 @@ pub enum Error { WeakPassword(String), #[error("Args Conflict Error: {0}")] ArgsConflict(String), - #[error("Invalid Password Index Error: {0}")] - InvalidPasswordIndex(String), + #[error("Invalid Vault Index Error: {0}")] + InvalidVaultIndex(String), #[error("{0}")] Other(String), @@ -56,6 +56,7 @@ pub enum Error { impl Error { /// Return the exit code of the error pub fn exit_code(&self) -> ExitCode { + // TODO: Exit with more specific exit code ExitCode::FAILURE } } diff --git a/src/macros.rs b/src/macros.rs index 1819671..f31d85a 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -63,7 +63,7 @@ macro_rules! create_commands { #[automatically_derived] impl $crate::RunCommand for $enum_name{ - fn run(&self, password_manager: $crate::password::Vaults) -> $crate::LprsResult<()> { + fn run(&self, password_manager: $crate::vault::Vaults<$crate::vault::vault_state::Plain>) -> $crate::LprsResult<()> { match self { $( Self::$varint(command) => command.run(password_manager), diff --git a/src/main.rs b/src/main.rs index 04ba220..c3186cf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -24,8 +24,8 @@ use clap::Parser; pub mod cli; pub mod errors; -pub mod password; pub mod utils; +pub mod vault; mod macros; mod traits; @@ -34,7 +34,7 @@ pub use errors::{Error as LprsError, Result as LprsResult}; pub use traits::*; pub const STANDARDBASE: GeneralPurpose = GeneralPurpose::new(&alphabet::STANDARD, PAD); -pub const DEFAULT_PASSWORD_FILE: &str = "passwords.json"; +pub const DEFAULT_VAULTS_FILE: &str = "vaults.json"; #[cfg(feature = "update-notify")] pub const VERSION: &str = env!("CARGO_PKG_VERSION"); diff --git a/src/password/mod.rs b/src/password/mod.rs deleted file mode 100644 index 86ad388..0000000 --- a/src/password/mod.rs +++ /dev/null @@ -1,157 +0,0 @@ -// Lprs - A local CLI password manager -// Copyright (C) 2024 Awiteb -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -use std::{fs, path::PathBuf}; - -use clap::{Parser, ValueEnum}; -use serde::{Deserialize, Serialize}; - -use crate::{LprsError, LprsResult}; - -pub mod cipher; - -mod bitwarden; -mod validator; - -pub use bitwarden::*; -pub use validator::*; - -#[derive(Clone, Debug, ValueEnum)] -pub enum Format { - Lprs, - BitWarden, -} - -/// The password struct -#[serde_with_macros::skip_serializing_none] -#[derive(Clone, Debug, Deserialize, Serialize, Parser)] -pub struct Vault { - /// The name of the password - #[arg(short, long)] - pub name: String, - /// The username - #[arg(short, long)] - pub username: String, - /// The password - #[arg(short, long)] - pub password: String, - /// The service name. e.g the website url - #[arg(short, long)] - pub service: Option, - /// The note of the password - #[arg(short = 'o', long)] - pub note: Option, -} - -/// The passwords manager -#[derive(Default)] -pub struct Vaults { - /// Hash of the master password - pub master_password: Vec, - /// The json passwords file - pub passwords_file: PathBuf, - /// The passwords - pub passwords: Vec, -} - -impl Vault { - /// Encrypt the password data - pub fn encrypt(self, master_password: &[u8]) -> LprsResult { - Ok(Self { - name: cipher::encrypt(master_password, &self.name)?, - username: cipher::encrypt(master_password, &self.username)?, - password: cipher::encrypt(master_password, &self.password)?, - service: self - .service - .map(|url| cipher::encrypt(master_password, &url)) - .transpose()?, - note: self - .note - .map(|note| cipher::encrypt(master_password, ¬e)) - .transpose()?, - }) - } - - /// Decrypt the password data - pub fn decrypt(self, master_password: &[u8]) -> LprsResult { - Ok(Self { - name: cipher::decrypt(master_password, &self.name)?, - username: cipher::decrypt(master_password, &self.username)?, - password: cipher::decrypt(master_password, &self.password)?, - service: self - .service - .map(|url| cipher::decrypt(master_password, &url)) - .transpose()?, - note: self - .note - .map(|note| cipher::decrypt(master_password, ¬e)) - .transpose()?, - }) - } -} - -impl Vaults { - /// Create new Passwords instnce - pub fn new(master_password: Vec, passwords_file: PathBuf, passwords: Vec) -> Self { - Self { - master_password, - passwords_file, - passwords, - } - } - - /// Encrypt the passwords - pub fn encrypt(self) -> LprsResult { - Ok(Self { - passwords: self - .passwords - .into_iter() - .map(|p| p.encrypt(&self.master_password)) - .collect::>>()?, - ..self - }) - } - - /// Reload the passwords from the file - pub fn try_reload(passwords_file: PathBuf, master_password: Vec) -> LprsResult { - let passwords = serde_json::from_str::>(&fs::read_to_string(&passwords_file)?)? - .into_iter() - .map(|p| p.decrypt(master_password.as_slice())) - .collect::>>()?; - - Ok(Self::new(master_password, passwords_file, passwords)) - } - - /// Export the passwords to the file - pub fn try_export(self) -> LprsResult<()> { - let path = self.passwords_file.to_path_buf(); - fs::write(path, serde_json::to_string(&self.encrypt()?.passwords)?).map_err(LprsError::Io) - } - - /// Add new password - pub fn add_password(&mut self, password: Vault) { - self.passwords.push(password) - } -} - -impl ToString for Format { - fn to_string(&self) -> String { - self.to_possible_value() - .expect("There is no skiped values") - .get_name() - .to_owned() - } -} diff --git a/src/traits.rs b/src/traits.rs index e12a415..5be273b 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -14,9 +14,12 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::{password::Vaults, LprsResult}; +use crate::{ + vault::{vault_state::*, Vaults}, + LprsResult, +}; /// Trait to run the command pub trait RunCommand { - fn run(&self, password_manager: Vaults) -> LprsResult<()>; + fn run(&self, password_manager: Vaults) -> LprsResult<()>; } diff --git a/src/utils.rs b/src/utils.rs index 37adda3..ef1dd7d 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -31,13 +31,13 @@ pub fn local_project_file(filename: &str) -> LprsResult { Ok(local_dir.join(filename)) } -/// Returns the default passwords json file -pub fn passwords_file() -> LprsResult { - let password_file = local_project_file(crate::DEFAULT_PASSWORD_FILE)?; - if !password_file.exists() { - fs::write(&password_file, "[]")?; +/// Returns the default vaults json file +pub fn vaults_file() -> LprsResult { + let vaults_file = local_project_file(crate::DEFAULT_VAULTS_FILE)?; + if !vaults_file.exists() { + fs::write(&vaults_file, "[]")?; } - Ok(password_file) + Ok(vaults_file) } /// Retuns the current lprs version from `crates.io` diff --git a/src/password/bitwarden.rs b/src/vault/bitwarden.rs similarity index 82% rename from src/password/bitwarden.rs rename to src/vault/bitwarden.rs index c31fd8f..7c4670c 100644 --- a/src/password/bitwarden.rs +++ b/src/vault/bitwarden.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -use super::{Vault, Vaults}; +use super::{vault_state::*, Vault, Vaults}; #[derive(Clone, Debug, Deserialize, Serialize)] pub struct BitWardenLoginData { @@ -39,28 +39,28 @@ pub struct BitWardenPasswords { pub items: Vec, } -impl From for Vault { +impl From for Vault { fn from(value: BitWardenPassword) -> Self { - Self { - name: value.name, - username: value + Self::new( + value.name, + value .login .as_ref() .map_or_else(String::new, |l| l.username.to_owned().unwrap_or_default()), - password: value + value .login .as_ref() .map_or_else(String::new, |l| l.password.to_owned().unwrap_or_default()), - service: value + value .login .and_then(|l| l.uris.and_then(|p| p.first().map(|u| u.uri.clone()))), - note: value.notes, - } + value.notes, + ) } } -impl From for BitWardenPassword { - fn from(value: Vault) -> Self { +impl From> for BitWardenPassword { + fn from(value: Vault) -> Self { Self { ty: 1, name: value.name, @@ -76,13 +76,13 @@ impl From for BitWardenPassword { } } -impl From for BitWardenPasswords { - fn from(value: Vaults) -> Self { +impl From> for BitWardenPasswords { + fn from(value: Vaults) -> Self { Self { encrypted: false, folders: Vec::new(), items: value - .passwords + .vaults .into_iter() .map(BitWardenPassword::from) .collect(), diff --git a/src/password/cipher.rs b/src/vault/cipher.rs similarity index 100% rename from src/password/cipher.rs rename to src/vault/cipher.rs diff --git a/src/password/validator.rs b/src/vault/validator.rs similarity index 81% rename from src/password/validator.rs rename to src/vault/validator.rs index 1fc015c..f27e37c 100644 --- a/src/password/validator.rs +++ b/src/vault/validator.rs @@ -18,15 +18,15 @@ use std::{fs, path::Path}; use crate::LprsResult; -use super::Vault; +use super::{vault_state::*, Vault}; -/// Return if the password file new file or not -pub fn is_new_password_file(path: &Path) -> LprsResult { +/// Return if the vaults file new file or not +pub fn is_new_vaults_file(path: &Path) -> LprsResult { if path.exists() { let file_content = fs::read_to_string(path)?; if !file_content.is_empty() && file_content.trim() != "[]" - && serde_json::from_str::>(&file_content).is_ok() + && serde_json::from_str::>>(&file_content).is_ok() { return Ok(false); }