From ba710886793989b17af15aa89d8231efcc36d661 Mon Sep 17 00:00:00 2001 From: TheAwiteb Date: Sun, 24 Dec 2023 20:22:28 +0300 Subject: [PATCH 1/5] Exit with 1 status code when there is no passwords to list --- src/cli/list_command.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/cli/list_command.rs b/src/cli/list_command.rs index 1035ac6..917770c 100644 --- a/src/cli/list_command.rs +++ b/src/cli/list_command.rs @@ -51,7 +51,9 @@ pub struct List { impl RunCommand for List { fn run(&self, password_manager: Passwords) -> PassrsResult<()> { if password_manager.passwords.is_empty() { - println!("Looks like there is no passwords to list") + Err(PassrsError::Other( + "Looks like there is no passwords to list".to_owned(), + )) } else { if self.get.is_some() && self.search.is_some() { return Err(PassrsError::ArgsConflict( @@ -132,7 +134,7 @@ impl RunCommand for List { table.add_row(row); } println!("{table}"); + Ok(()) } - Ok(()) } } From bdfb17d888b67dc7c238b6dd818e74170a0bb4ac Mon Sep 17 00:00:00 2001 From: TheAwiteb Date: Sun, 24 Dec 2023 20:25:47 +0300 Subject: [PATCH 2/5] Derive `Default` for `Passwords` --- src/password/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/password/mod.rs b/src/password/mod.rs index e400b0d..7cb3c4f 100644 --- a/src/password/mod.rs +++ b/src/password/mod.rs @@ -27,7 +27,7 @@ mod validator; pub use validator::*; /// The passwords manager -#[derive(Deserialize, Serialize)] +#[derive(Default, Deserialize, Serialize)] pub struct Passwords { /// Hash of the master password #[serde(skip)] From 4b15b9403abe4b6ffc820db60e0f43d8b88287bc Mon Sep 17 00:00:00 2001 From: TheAwiteb Date: Sun, 24 Dec 2023 20:26:51 +0300 Subject: [PATCH 3/5] Don't ask for master password in clean command --- src/cli/mod.rs | 43 +++++++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 292af26..8090fca 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -63,26 +63,33 @@ impl Cli { "Getting password file: {}", passwords_file.to_string_lossy() ); - let password = scanpw::scanpw!("Master Password: "); - - if password::is_new_password_file(&passwords_file)? { - let analyzed = passwords::analyzer::analyze(&password); - if analyzed.length() < 15 { - return Err(PassrsError::WeakPassword( - "The password length must be beggier then 15".to_owned(), - )); - } else if passwords::scorer::score(&analyzed) < 80.0 { - return Err(PassrsError::WeakPassword( - "Your password is not stronge enough".to_owned(), - )); + let password_manager = if matches!(self.command, Commands::Clean(..)) { + Passwords { + passwords_file, + ..Default::default() } - } + } else { + let password = scanpw::scanpw!("Master Password: "); - let master_password = sha256::digest(password); - let password_manager = Passwords::try_reload( - passwords_file, - master_password.into_bytes().into_iter().take(32).collect(), - )?; + if password::is_new_password_file(&passwords_file)? { + let analyzed = passwords::analyzer::analyze(&password); + if analyzed.length() < 15 { + return Err(PassrsError::WeakPassword( + "The password length must be beggier then 15".to_owned(), + )); + } else if passwords::scorer::score(&analyzed) < 80.0 { + return Err(PassrsError::WeakPassword( + "Your password is not stronge enough".to_owned(), + )); + } + } + + let master_password = sha256::digest(password); + Passwords::try_reload( + passwords_file, + master_password.into_bytes().into_iter().take(32).collect(), + )? + }; self.command.run(password_manager)?; Ok(()) From 4e333d966507b57d84afdd3c121b8246142b0604 Mon Sep 17 00:00:00 2001 From: TheAwiteb Date: Sun, 24 Dec 2023 20:27:50 +0300 Subject: [PATCH 4/5] Add generate command --- src/cli/gen_command.rs | 66 ++++++++++++++++++++++++++++++++++++++++++ src/cli/mod.rs | 4 ++- 2 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 src/cli/gen_command.rs diff --git a/src/cli/gen_command.rs b/src/cli/gen_command.rs new file mode 100644 index 0000000..5517aae --- /dev/null +++ b/src/cli/gen_command.rs @@ -0,0 +1,66 @@ +// 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::num::NonZeroU64; + +use clap::Args; + +use crate::{password::Passwords, PassrsError, PassrsResult, RunCommand}; + +#[derive(Debug, Args)] +#[command(author, version, about, long_about = None)] +pub struct Gen { + /// The password length + #[arg(default_value_t = NonZeroU64::new(18).unwrap())] + length: NonZeroU64, + + /// With uppercase letters (A-Z) + #[arg(short, long)] + uppercase: bool, + /// With lowercase letters (a-z) + #[arg(short, long)] + lowercase: bool, + /// With numbers (0-9) + #[arg(short, long)] + numbers: bool, + /// With symbols (!,# ...) + #[arg(short, long)] + symbols: bool, +} + +impl RunCommand for Gen { + fn run(&self, _password_manager: Passwords) -> PassrsResult<()> { + if self.uppercase || self.lowercase || self.numbers || self.symbols { + println!( + "{}", + passwords::PasswordGenerator::new() + .length(self.length.get() as usize) + .uppercase_letters(self.uppercase) + .lowercase_letters(self.lowercase) + .numbers(self.numbers) + .symbols(self.symbols) + .strict(true) + .generate_one() + .expect("The length cannot be zero") + ); + Ok(()) + } else { + Err(PassrsError::Other( + "You need to enable at least one kind of characters".to_owned(), + )) + } + } +} diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 8090fca..8fe71f2 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -26,6 +26,7 @@ use crate::{ pub mod add_command; pub mod clean_command; pub mod edit_command; +pub mod gen_command; pub mod list_command; crate::create_commands!( @@ -34,6 +35,7 @@ crate::create_commands!( "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 // TODO: Remove command // TODO: Export command // TODO: Import command @@ -63,7 +65,7 @@ impl Cli { "Getting password file: {}", passwords_file.to_string_lossy() ); - let password_manager = if matches!(self.command, Commands::Clean(..)) { + let password_manager = if matches!(self.command, Commands::Clean(..) | Commands::Gen(..)) { Passwords { passwords_file, ..Default::default() From 75f7e740961d0fa9fb22ae6996df8121c3e94337 Mon Sep 17 00:00:00 2001 From: TheAwiteb Date: Sun, 24 Dec 2023 20:28:03 +0300 Subject: [PATCH 5/5] Update README.md --- README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/README.md b/README.md index 59ca08a..7422f61 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ Commands: list List your password and search clean Clean the password file edit Edit the password content + gen Generate password help Print this message or the help of the given subcommand(s) Options: @@ -46,6 +47,24 @@ Options: Print version ``` +### Example +```bash +passrs add -n "Gmail" -u "some@gmail.com" -p $(passrs gen 19 -u -l -s) -s "https://mail.google.com" +``` + +#### Result +This is the result when search for it +``` +$ passrs list -e "mail" -p -s +Master Password: *************** ++-------+-------+----------------+---------------------+-------------------------+ +| Index | Name | Username | Password | Service | ++================================================================================+ +| 31 | Gmail | some@gmail.com | >NC`q$%+Nno