diff --git a/Cargo.lock b/Cargo.lock index 3fcac16..903e321 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1318,6 +1318,7 @@ name = "pass_manager" version = "0.1.0" dependencies = [ "anyhow", + "arrayvec", "chacha20poly1305", "dotenv", "futures", diff --git a/Cargo.toml b/Cargo.toml index d61bfa4..4edc751 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ members = [".", "migration"] [dependencies] anyhow = "1.0.70" +arrayvec = "0.7.2" chacha20poly1305 = { version = "0.10.1", features = ["std"] } dotenv = "0.15.0" futures = "0.3.28" diff --git a/src/handlers/commands/gen_password.rs b/src/handlers/commands/gen_password.rs new file mode 100644 index 0000000..2f5012e --- /dev/null +++ b/src/handlers/commands/gen_password.rs @@ -0,0 +1,60 @@ +use arrayvec::{ArrayString, ArrayVec}; +use rand::{rngs::OsRng, seq::SliceRandom}; +use std::{iter, str::from_utf8_unchecked}; +use teloxide::{adaptors::Throttle, prelude::*, types::ParseMode}; +use tokio::task::JoinSet; + +use crate::handlers::markups::deletion_markup; + +const CHARS: &'static [u8] = br##"!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_abcdefghijklmnopqrstuvwxyz{|}~"##; + +#[inline] +fn check_generated_password(password: &[u8]) -> bool { + let mut flags: u8 = 0; + for &byte in password { + match byte { + b'a'..=b'z' => flags |= 0b1, + b'A'..=b'Z' => flags |= 0b10, + b'0'..=b'9' => flags |= 0b100, + b'!'..=b'/' | b':'..=b'@' | b'['..=b'`' | b'{'..=b'~' => flags |= 0b1000, + _ => (), + } + if flags == 0b1111 { + return true; + } + } + false +} + +fn generete_password() -> ArrayString<34> { + loop { + let password: ArrayVec = iter::repeat_with(|| *CHARS.choose(&mut OsRng).unwrap()) + .take(32) + .collect(); + if check_generated_password(&password) { + let mut string = ArrayString::<34>::new_const(); + string.push('`'); + unsafe { string.push_str(from_utf8_unchecked(&password)) }; + string.push('`'); + return string; + } + } +} + +pub async fn gen_password(bot: Throttle, msg: Message) -> crate::Result<()> { + let mut message = ArrayString::<{ 11 + 35 * 10 }>::new(); + message.push_str("Passwords:\n"); + let mut join_set = JoinSet::new(); + for _ in 0..10 { + join_set.spawn_blocking(|| generete_password()); + } + while let Some(password) = join_set.join_next().await { + message.push_str(password?.as_str()); + message.push('\n') + } + bot.send_message(msg.chat.id, message.as_str()) + .parse_mode(ParseMode::MarkdownV2) + .reply_markup(deletion_markup()) + .await?; + Ok(()) +} diff --git a/src/handlers/commands/mod.rs b/src/handlers/commands/mod.rs index 90ee1c8..2f513b2 100644 --- a/src/handlers/commands/mod.rs +++ b/src/handlers/commands/mod.rs @@ -3,6 +3,7 @@ mod default; mod delete; mod delete_all; mod export; +mod gen_password; mod get_account; mod get_accounts; mod help; @@ -14,6 +15,7 @@ pub use default::default; pub use delete::delete; pub use delete_all::delete_all; pub use export::export; +pub use gen_password::gen_password; pub use get_account::get_account; pub use get_accounts::get_accounts; pub use help::help; diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs index 3ba0390..95765f7 100644 --- a/src/handlers/mod.rs +++ b/src/handlers/mod.rs @@ -61,6 +61,8 @@ enum Command { Export, #[command(description = "loads the accounts from a json file")] Import, + #[command(description = "generates 10 secure passwords")] + GenPassword, } #[derive(Default, Clone)] @@ -85,6 +87,7 @@ pub fn get_dispatcher( let command_handler = filter_command::() .branch(case![Command::Help].endpoint(commands::help)) .branch(case![Command::SetMasterPass].endpoint(commands::set_master_pass)) + .branch(case![Command::GenPassword].endpoint(commands::gen_password)) .branch(master_password_check::get_handler()) .branch(case![Command::AddAccount].endpoint(commands::add_account)) .branch(case![Command::GetAccount].endpoint(commands::get_account))