diff --git a/README.md b/README.md index d12b666..b3c331f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Lprs -Lprs is a local password manager designed to securely store and manage your passwords. +Lprs is a local vault manager designed to securely store and manage your vaults. ### MSRV The Minimum Supported Rust Version (MSRV) is `1.70.0`. @@ -31,7 +31,7 @@ cargo uninstall lprs ## Usage -Lprs provides a command-line interface for managing your passwords. The following commands are available: +Lprs provides a command-line interface for managing your vaults. The following commands are available: ``` A local CLI password manager @@ -39,23 +39,20 @@ A local CLI password manager Usage: lprs [OPTIONS] Commands: - add Add new password - remove Remove password - list List your password and search - clean Clean the password file - edit Edit the password content - gen Generate password - export Export the passwords - import Import passwords + add Add new vault + remove Remove vault + list List your vaults and search + clean Clean the vaults file + edit Edit the vault content + gen Generate a password + export Export the vaults + import Import vaults help Print this message or the help of the given subcommand(s) Options: - -p, --passwords-file - The passwords json file, default: $HOME/.local/share/lprs/passwords.json - -h, --help - Print help - -V, --version - Print version + -v, --vaults-file The vaults json file + -h, --help Print help + -V, --version Print version ``` ### Example @@ -78,7 +75,7 @@ Master Password: *************** ### Backup -It is important to regularly backup your passwords to prevent data loss. Lprs does not provide an automatic backup feature. To backup your passwords, you can use the `export` command provided by Lprs. This command allows you to export your encrypted passwords to a json file, which you can then manually backup to a secure location. +It is important to regularly backup your vaults to prevent data loss. Lprs does not provide an automatic backup feature. To backup your vaults, you can use the `export` command provided by Lprs. This command allows you to export your encrypted vaults to a json file, which you can then manually backup to a secure location. #### Formats The format of the exported file can be specified using the `--format` option. The following formats are supported: @@ -104,4 +101,4 @@ Lprs is licensed under the GPL-3.0 License. This means that you are free to use, --- -[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/awiteb) \ No newline at end of file +[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/awiteb) diff --git a/src/cli/add_command.rs b/src/cli/add_command.rs index cdc2557..7b5a5c9 100644 --- a/src/cli/add_command.rs +++ b/src/cli/add_command.rs @@ -1,4 +1,4 @@ -// Lprs - A local CLI password manager +// Lprs - A local CLI vault manager // Copyright (C) 2024 Awiteb // // This program is free software: you can redistribute it and/or modify @@ -17,7 +17,7 @@ use clap::Args; use crate::{ - password::{Password, Passwords}, + vault::{vault_state::*, Vault, Vaults}, LprsResult, RunCommand, }; @@ -25,12 +25,12 @@ use crate::{ #[command(author, version, about, long_about = None)] pub struct Add { #[command(flatten)] - password_info: Password, + vault_info: Vault, } impl RunCommand for Add { - fn run(&self, mut password_manager: Passwords) -> LprsResult<()> { - password_manager.add_password(self.password_info.clone()); - password_manager.try_export() + fn run(&self, mut vault_manager: Vaults) -> LprsResult<()> { + vault_manager.add_vault(self.vault_info.clone()); + vault_manager.try_export() } } diff --git a/src/cli/clean_command.rs b/src/cli/clean_command.rs index 8c91892..2efeed3 100644 --- a/src/cli/clean_command.rs +++ b/src/cli/clean_command.rs @@ -1,4 +1,4 @@ -// Lprs - A local CLI password manager +// Lprs - A local CLI vault manager // Copyright (C) 2024 Awiteb // // This program is free software: you can redistribute it and/or modify @@ -18,14 +18,17 @@ use std::fs; use clap::Args; -use crate::{password::Passwords, 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: Passwords) -> LprsResult<()> { - fs::write(password_manager.passwords_file, "[]").map_err(LprsError::Io) + fn run(&self, vault_manager: Vaults) -> LprsResult<()> { + fs::write(vault_manager.vaults_file, "[]").map_err(LprsError::Io) } } diff --git a/src/cli/edit_command.rs b/src/cli/edit_command.rs index 6b2885e..d8cdd88 100644 --- a/src/cli/edit_command.rs +++ b/src/cli/edit_command.rs @@ -1,4 +1,4 @@ -// Lprs - A local CLI password manager +// Lprs - A local CLI vault manager // Copyright (C) 2024 Awiteb // // This program is free software: you can redistribute it and/or modify @@ -19,7 +19,7 @@ use std::num::NonZeroU64; use clap::Args; use crate::{ - password::{Password, Passwords}, + vault::{vault_state::*, Vault, Vaults}, LprsError, LprsResult, RunCommand, }; @@ -30,27 +30,27 @@ pub struct Edit { index: NonZeroU64, #[arg(short, long)] - /// The new password name + /// The new vault name name: Option, #[arg(short, long)] - /// The new password username + /// The new vault username username: Option, #[arg(short, long)] /// The new password password: Option, #[arg(short, long)] - /// The new password service + /// The new vault service service: Option, #[arg(short = 'o', long)] - /// The new password note + /// The new vault note note: Option, } impl RunCommand for Edit { - fn run(&self, mut password_manager: Passwords) -> LprsResult<()> { + fn run(&self, mut vault_manager: Vaults) -> LprsResult<()> { let index = self.index.get() as usize; - if let Some(password) = password_manager.passwords.get_mut(index - 1) { + if let Some(vault) = vault_manager.vaults.get_mut(index - 1) { if self.name.is_none() && self.username.is_none() && self.password.is_none() @@ -61,28 +61,20 @@ impl RunCommand for Edit { "You must edit one option at least".to_owned(), )) } else { - *password = Password { - name: self.name.as_ref().unwrap_or(&password.name).to_string(), - username: self - .username - .as_ref() - .unwrap_or(&password.username) - .to_string(), - password: self - .password - .as_ref() - .unwrap_or(&password.password) - .to_string(), - service: self.service.as_ref().or(password.service.as_ref()).cloned(), - note: self.note.as_ref().or(password.note.as_ref()).cloned(), - }; - password_manager.try_export() + *vault = Vault::::new( + self.name.as_ref().unwrap_or(&vault.name), + self.username.as_ref().unwrap_or(&vault.username), + self.password.as_ref().unwrap_or(&vault.password), + self.service.as_ref().or(vault.service.as_ref()), + self.note.as_ref().or(vault.note.as_ref()), + ); + vault_manager.try_export() } } else { - Err(LprsError::InvalidPasswordIndex(format!( - "The index `{}` is greater than the passwords count {}", + Err(LprsError::InvalidVaultIndex(format!( + "The index `{}` is greater than the vaults count {}", self.index, - password_manager.passwords.len() + vault_manager.vaults.len() ))) } } diff --git a/src/cli/export_command.rs b/src/cli/export_command.rs index 475b8b5..1375ebb 100644 --- a/src/cli/export_command.rs +++ b/src/cli/export_command.rs @@ -1,4 +1,4 @@ -// Lprs - A local CLI password manager +// Lprs - A local CLI vault manager // Copyright (C) 2024 Awiteb // // This program is free software: you can redistribute it and/or modify @@ -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, Passwords}, + vault::{vault_state::*, BitWardenPasswords, Format, Vault, Vaults}, LprsError, LprsResult, RunCommand, }; @@ -28,13 +28,13 @@ use crate::{ pub struct Export { /// The path to export to path: PathBuf, - /// Format to export passwords in + /// Format to export vaults in #[arg(short, long, value_name = "FORMAT", default_value_t= Format::Lprs)] format: Format, } impl RunCommand for Export { - fn run(&self, password_manager: Passwords) -> LprsResult<()> { + fn run(&self, vault_manager: Vaults) -> LprsResult<()> { if self .path .extension() @@ -42,9 +42,11 @@ 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::>>( + &vault_manager.encrypt_vaults()?, + ), Format::BitWarden => { - serde_json::to_string(&BitWardenPasswords::from(password_manager)) + serde_json::to_string(&BitWardenPasswords::from(vault_manager)) } }?; diff --git a/src/cli/gen_command.rs b/src/cli/gen_command.rs index afb31d5..d5dacac 100644 --- a/src/cli/gen_command.rs +++ b/src/cli/gen_command.rs @@ -1,4 +1,4 @@ -// Lprs - A local CLI password manager +// Lprs - A local CLI vault manager // Copyright (C) 2024 Awiteb // // This program is free software: you can redistribute it and/or modify @@ -18,7 +18,10 @@ use std::num::NonZeroU64; use clap::Args; -use crate::{password::Passwords, 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: Passwords) -> LprsResult<()> { + fn run(&self, _vault_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 aa02bc3..cbc080a 100644 --- a/src/cli/import_command.rs +++ b/src/cli/import_command.rs @@ -1,4 +1,4 @@ -// Lprs - A local CLI password manager +// Lprs - A local CLI vault manager // Copyright (C) 2024 Awiteb // // This program is free software: you can redistribute it and/or modify @@ -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, Password, Passwords}, + 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: Passwords) -> LprsResult<()> { + fn run(&self, mut vault_manager: Vaults) -> LprsResult<()> { if self.path.exists() { if self .path @@ -44,30 +44,30 @@ impl RunCommand for Import { { let imported_passwords_len = match self.format { Format::Lprs => { - let passwords = Passwords::try_reload( + let vaults = Vaults::try_reload( self.path.to_path_buf(), - password_manager.master_password.to_vec(), + vault_manager.master_password.to_vec(), )?; - let passwords_len = passwords.passwords.len(); + let vaults_len = vaults.vaults.len(); - password_manager.passwords.extend(passwords.passwords); - password_manager.try_export()?; - passwords_len + vault_manager.vaults.extend(vaults.vaults); + vault_manager.try_export()?; + vaults_len } Format::BitWarden => { - let passwords: BitWardenPasswords = + let vaults: BitWardenPasswords = serde_json::from_reader(File::open(&self.path)?)?; - let passwords_len = passwords.items.len(); + let vaults_len = vaults.items.len(); - password_manager - .passwords - .extend(passwords.items.into_iter().map(Password::from)); - password_manager.try_export()?; - passwords_len + vault_manager + .vaults + .extend(vaults.items.into_iter().map(Vault::from)); + vault_manager.try_export()?; + vaults_len } }; println!( - "{imported_passwords_len} password{s} were imported successfully", + "{imported_passwords_len} vault{s} were imported successfully", s = if imported_passwords_len >= 2 { "s" } else { "" } ); diff --git a/src/cli/list_command.rs b/src/cli/list_command.rs index 32b47e9..a7550c9 100644 --- a/src/cli/list_command.rs +++ b/src/cli/list_command.rs @@ -1,4 +1,4 @@ -// Lprs - A local CLI password manager +// Lprs - A local CLI vault manager // Copyright (C) 2024 Awiteb // // This program is free software: you can redistribute it and/or modify @@ -20,7 +20,10 @@ use clap::Args; use comfy_table::Table; use regex::Regex; -use crate::{password::Passwords, 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: Passwords) -> LprsResult<()> { - if password_manager.passwords.is_empty() { + fn run(&self, vault_manager: Vaults) -> LprsResult<()> { + if vault_manager.vaults.is_empty() { Err(LprsError::Other( "Looks like there is no passwords to list".to_owned(), )) @@ -77,8 +80,8 @@ impl RunCommand for List { let re = Regex::new(self.search.as_deref().unwrap_or("."))?; table.set_header(header); - let passwords = password_manager - .passwords + let vaults = vault_manager + .vaults .iter() .enumerate() .filter(|(idx, pass)| { @@ -112,24 +115,24 @@ impl RunCommand for List { true }); - for (idx, password) in passwords { - let hide_password = "*".repeat(password.password.chars().count()); + 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(), - password.name.as_str(), - password.username.as_str(), + vault.name.as_str(), + vault.username.as_str(), if self.unhide_password { - password.password.as_str() + vault.password.as_str() } else { hide_password.as_str() }, ]; if self.with_service { - row.push(password.service.as_deref().unwrap_or("Not Set")) + row.push(vault.service.as_deref().unwrap_or("Not Set")) } if self.with_note { - row.push(password.note.as_deref().unwrap_or("Not Set")) + row.push(vault.note.as_deref().unwrap_or("Not Set")) } table.add_row(row); } diff --git a/src/cli/mod.rs b/src/cli/mod.rs index f4459e0..d76a89d 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -1,4 +1,4 @@ -// Lprs - A local CLI password manager +// Lprs - A local CLI vault manager // Copyright (C) 2024 Awiteb // // This program is free software: you can redistribute it and/or modify @@ -19,7 +19,7 @@ use std::path::PathBuf; use clap::Parser; use crate::{ - password::{self, Passwords}, + vault::{self, Vaults}, LprsError, LprsResult, RunCommand, }; @@ -34,22 +34,22 @@ 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)] #[command(author, version, about, long_about = None)] pub struct Cli { - /// The passwords json file, default: $HOME/.local/share/lprs/passwords.json + /// The vaults json file #[arg(short, long)] - passwords_file: Option, + vaults_file: Option, // TODO: verbose flag #[command(subcommand)] @@ -59,43 +59,40 @@ 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 password file: {}", - passwords_file.to_string_lossy() - ); - let password_manager = if matches!(self.command, Commands::Clean(..) | Commands::Gen(..)) { - Passwords { - passwords_file, + log::debug!("Getting the vaults file: {}", vaults_file.to_string_lossy()); + let vault_manager = if matches!(self.command, Commands::Clean(..) | Commands::Gen(..)) { + Vaults { + vaults_file, ..Default::default() } } else { - let password = scanpw::scanpw!("Master Password: "); + let master_password = scanpw::scanpw!("Master Password: "); - if password::is_new_password_file(&passwords_file)? { - let analyzed = passwords::analyzer::analyze(&password); + if vault::is_new_vaults_file(&vaults_file)? { + let analyzed = passwords::analyzer::analyze(&master_password); if analyzed.length() < 15 { return Err(LprsError::WeakPassword( - "The password length must be beggier then 15".to_owned(), + "The master password length must be beggier then 15".to_owned(), )); } else if passwords::scorer::score(&analyzed) < 80.0 { return Err(LprsError::WeakPassword( - "Your password is not stronge enough".to_owned(), + "Your master password is not stronge enough".to_owned(), )); } } - let master_password = sha256::digest(password); - Passwords::try_reload( - passwords_file, + let master_password = sha256::digest(master_password); + Vaults::try_reload( + vaults_file, master_password.into_bytes().into_iter().take(32).collect(), )? }; - self.command.run(password_manager)?; + self.command.run(vault_manager)?; Ok(()) } diff --git a/src/cli/remove_command.rs b/src/cli/remove_command.rs index ac12597..29ac6b6 100644 --- a/src/cli/remove_command.rs +++ b/src/cli/remove_command.rs @@ -1,4 +1,4 @@ -// Lprs - A local CLI password manager +// Lprs - A local CLI vault manager // Copyright (C) 2024 Awiteb // // This program is free software: you can redistribute it and/or modify @@ -18,7 +18,10 @@ use std::num::NonZeroU64; use clap::Args; -use crate::{password::Passwords, LprsError, LprsResult, RunCommand}; +use crate::{ + vault::{vault_state::*, Vaults}, + LprsError, LprsResult, RunCommand, +}; #[derive(Debug, Args)] #[command(author, version, about, long_about = None)] @@ -32,17 +35,17 @@ pub struct Remove { } impl RunCommand for Remove { - fn run(&self, mut password_manager: Passwords) -> LprsResult<()> { + fn run(&self, mut vault_manager: Vaults) -> LprsResult<()> { let index = (self.index.get() - 1) as usize; - if index > password_manager.passwords.len() { + if index > vault_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.try_export()?; + vault_manager.vaults.remove(index); + vault_manager.try_export()?; } Ok(()) } diff --git a/src/errors.rs b/src/errors.rs index 6be1c8f..52ac749 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -1,4 +1,4 @@ -// Lprs - A local CLI password manager +// Lprs - A local CLI vault manager // Copyright (C) 2024 Awiteb // // This program is free software: you can redistribute it and/or modify @@ -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 80ba849..454b8ed 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -1,4 +1,4 @@ -// Lprs - A local CLI password manager +// Lprs - A local CLI vault manager // Copyright (C) 2024 Awiteb // // This program is free software: you can redistribute it and/or modify @@ -40,11 +40,11 @@ /// impl crate::RunCommand for TestCommands { /// fn run( /// &self, -/// password_manager: crate::password::Passwords, +/// vault_manager: crate::vault::Vaults, /// ) -> crate::LprsResult<()> { /// match self { -/// Self::Test(command) => command.run(password_manager), -/// Self::Some(command) => command.run(password_manager), +/// Self::Test(command) => command.run(vault_manager), +/// Self::Some(command) => command.run(vault_manager), /// } /// } /// } @@ -63,10 +63,10 @@ macro_rules! create_commands { #[automatically_derived] impl $crate::RunCommand for $enum_name{ - fn run(&self, password_manager: $crate::password::Passwords) -> $crate::LprsResult<()> { + fn run(&self, vault_manager: $crate::vault::Vaults<$crate::vault::vault_state::Plain>) -> $crate::LprsResult<()> { match self { $( - Self::$varint(command) => command.run(password_manager), + Self::$varint(command) => command.run(vault_manager), )+ } } diff --git a/src/main.rs b/src/main.rs index 04ba220..bfe7443 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,4 @@ -// Lprs - A local CLI password manager +// Lprs - A local CLI vault manager // Copyright (C) 2024 Awiteb // // This program is free software: you can redistribute it and/or modify @@ -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 a13fbe7..0000000 --- a/src/password/mod.rs +++ /dev/null @@ -1,162 +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 Password { - /// 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 Passwords { - /// Hash of the master password - pub master_password: Vec, - /// The json passwords file - pub passwords_file: PathBuf, - /// The passwords - pub passwords: Vec, -} - -impl Password { - /// 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 Passwords { - /// 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: Password) { - 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 05da877..dd8e9c4 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -1,4 +1,4 @@ -// Lprs - A local CLI password manager +// Lprs - A local CLI vault manager // Copyright (C) 2024 Awiteb // // This program is free software: you can redistribute it and/or modify @@ -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::Passwords, LprsResult}; +use crate::{ + vault::{vault_state::*, Vaults}, + LprsResult, +}; /// Trait to run the command pub trait RunCommand { - fn run(&self, password_manager: Passwords) -> LprsResult<()>; + fn run(&self, vault_manager: Vaults) -> LprsResult<()>; } diff --git a/src/utils.rs b/src/utils.rs index 37adda3..4e21c24 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,4 +1,4 @@ -// Lprs - A local CLI password manager +// Lprs - A local CLI vault manager // Copyright (C) 2024 Awiteb // // This program is free software: you can redistribute it and/or modify @@ -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 81% rename from src/password/bitwarden.rs rename to src/vault/bitwarden.rs index b1ab0a9..7c4670c 100644 --- a/src/password/bitwarden.rs +++ b/src/vault/bitwarden.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -use super::{Password, Passwords}; +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 Password { +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: Password) -> 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: Passwords) -> 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 97% rename from src/password/cipher.rs rename to src/vault/cipher.rs index 0b34b0b..8e66c71 100644 --- a/src/password/cipher.rs +++ b/src/vault/cipher.rs @@ -1,4 +1,4 @@ -// Lprs - A local CLI password manager +// Lprs - A local CLI vault manager // Copyright (C) 2024 Awiteb // // This program is free software: you can redistribute it and/or modify diff --git a/src/vault/mod.rs b/src/vault/mod.rs new file mode 100644 index 0000000..2e4bacd --- /dev/null +++ b/src/vault/mod.rs @@ -0,0 +1,209 @@ +// Lprs - A local CLI vault 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, marker::PhantomData, path::PathBuf}; + +use clap::{Parser, ValueEnum}; +use serde::{Deserialize, Serialize}; + +use crate::{LprsError, LprsResult}; +use vault_state::*; + +pub mod cipher; + +mod bitwarden; +mod validator; + +pub use bitwarden::*; +pub use validator::*; + +#[derive(Clone, Debug, ValueEnum)] +pub enum Format { + Lprs, + BitWarden, +} + +/// The states of the vaults +pub mod vault_state { + /// Means the vault is encrypted + #[derive(Clone, Debug, Default)] + pub struct Encrypted; + /// Means the vault is not encrypted + #[derive(Clone, Debug, Default)] + pub struct Plain; +} + +/// The vault struct +#[serde_with_macros::skip_serializing_none] +#[derive(Clone, Debug, Deserialize, Serialize, Parser)] +pub struct Vault +where + T: std::fmt::Debug + Clone, +{ + /// The name of the vault + #[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, + /// Add a note to the vault + #[arg(short = 'o', long)] + pub note: Option, + + /// State phantom + #[serde(skip)] + #[arg(skip)] + phantom: PhantomData, +} + +/// The vaults manager +#[derive(Default)] +pub struct Vaults +where + T: std::fmt::Debug + Clone, +{ + /// Hash of the master password + pub master_password: Vec, + /// The json vaults file + pub vaults_file: PathBuf, + /// The vaults + pub vaults: Vec>, +} + +impl Vault +where + T: std::fmt::Debug + Clone, +{ + /// Create new [`Vault`] instance + pub fn new( + name: impl Into, + username: impl Into, + password: impl Into, + service: Option>, + note: Option>, + ) -> Self { + Self { + name: name.into(), + username: username.into(), + password: password.into(), + service: service.map(|s| s.into()), + note: note.map(|s| s.into()), + phantom: std::marker::PhantomData, + } + } +} + +impl Vault { + /// Decrypt the vault + pub fn decrypt(&self, master_password: &[u8]) -> LprsResult> { + Ok(Vault::::new( + cipher::decrypt(master_password, &self.name)?, + cipher::decrypt(master_password, &self.username)?, + cipher::decrypt(master_password, &self.password)?, + self.service + .as_ref() + .map(|url| cipher::decrypt(master_password, url)) + .transpose()?, + self.note + .as_ref() + .map(|note| cipher::decrypt(master_password, note)) + .transpose()?, + )) + } +} + +impl Vault { + /// Encrypt the vault + pub fn encrypt(&self, master_password: &[u8]) -> LprsResult> { + Ok(Vault::::new( + cipher::encrypt(master_password, &self.name)?, + cipher::encrypt(master_password, &self.username)?, + cipher::encrypt(master_password, &self.password)?, + self.service + .as_ref() + .map(|url| cipher::encrypt(master_password, url)) + .transpose()?, + self.note + .as_ref() + .map(|note| cipher::encrypt(master_password, note)) + .transpose()?, + )) + } +} + +impl Vaults +where + T: std::fmt::Debug + Clone, +{ + /// Create new [`Vaults`] instnce + pub fn new(master_password: Vec, vaults_file: PathBuf, vaults: Vec>) -> Self { + Self { + master_password, + vaults_file, + vaults, + } + } +} + +impl Vaults { + /// Encrypt the vaults + pub fn encrypt_vaults(&self) -> LprsResult>> { + self.vaults + .iter() + .map(|p| p.encrypt(&self.master_password)) + .collect() + } + + /// Reload the vaults from the file then decrypt it + pub fn try_reload(vaults_file: PathBuf, master_password: Vec) -> LprsResult { + let vaults = + serde_json::from_str::>>(&fs::read_to_string(&vaults_file)?)? + .into_iter() + .map(|p| p.decrypt(master_password.as_slice())) + .collect::>>>()?; + + Ok(Self::new(master_password, vaults_file, vaults)) + } + + /// Encrypt the vaults then export it to the file + pub fn try_export(self) -> LprsResult<()> { + fs::write( + &self.vaults_file, + serde_json::to_string(&self.encrypt_vaults()?)?, + ) + .map_err(LprsError::Io) + } + + /// Add new vault + pub fn add_vault(&mut self, vault: Vault) { + self.vaults.push(vault) + } +} + +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/password/validator.rs b/src/vault/validator.rs similarity index 78% rename from src/password/validator.rs rename to src/vault/validator.rs index fb9bbf6..784a84b 100644 --- a/src/password/validator.rs +++ b/src/vault/validator.rs @@ -1,4 +1,4 @@ -// Lprs - A local CLI password manager +// Lprs - A local CLI vault manager // Copyright (C) 2024 Awiteb // // This program is free software: you can redistribute it and/or modify @@ -18,15 +18,15 @@ use std::{fs, path::Path}; use crate::LprsResult; -use super::Password; +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); }