diff --git a/Cargo.lock b/Cargo.lock index 52116a7..4612f0e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1666,7 +1666,7 @@ checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.3.3", + "regex-automata 0.3.4", "regex-syntax 0.7.4", ] @@ -1681,9 +1681,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310" +checksum = "b7b6d6190b7594385f61bd3911cd1be99dfddcfc365a4160cc2ab5bff4aed294" dependencies = [ "aho-corasick", "memchr", @@ -2118,18 +2118,18 @@ checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918" [[package]] name = "serde" -version = "1.0.177" +version = "1.0.178" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63ba2516aa6bf82e0b19ca8b50019d52df58455d3cf9bdaf6315225fdd0c560a" +checksum = "60363bdd39a7be0266a520dab25fdc9241d2f987b08a01e01f0ec6d06a981348" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.177" +version = "1.0.178" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "401797fe7833d72109fedec6bfcbe67c0eed9b99772f26eb8afd261f0abc6fd3" +checksum = "f28482318d6641454cb273da158647922d1be6b5a2fcc6165cd89ebdd7ed576b" dependencies = [ "proc-macro2", "quote", diff --git a/src/callbacks/decrypt.rs b/src/callbacks/decrypt.rs new file mode 100644 index 0000000..7d60f64 --- /dev/null +++ b/src/callbacks/decrypt.rs @@ -0,0 +1,68 @@ +use crate::{change_state, prelude::*}; +use teloxide::types::ParseMode; +use tokio::task::spawn_blocking; + +async fn get_master_pass( + bot: Throttle, + msg: Message, + db: DatabaseConnection, + dialogue: MainDialogue, + mut ids: MessageIds, + name: String, + master_pass: String, +) -> crate::Result<()> { + dialogue.exit().await?; + + let user_id = msg.from().ok_or(NoUserInfo)?.id.0; + + let account = match Account::get(user_id, &name, &db).await? { + Some(account) => account, + None => { + bot.send_message(msg.chat.id, "Account not found") + .reply_markup(deletion_markup()) + .await?; + return Ok(()); + } + }; + + let (login, password) = spawn_blocking(move || account.decrypt(&master_pass)).await??; + let text = format!("Name:\n`{name}`\nLogin:\n`{login}`\nPassword:\n`{password}`"); + + ids.alter_message( + &bot, + text, + account_markup(&name, false), + ParseMode::MarkdownV2, + ) + .await?; + Ok(()) +} + +pub async fn decrypt( + bot: Throttle, + q: CallbackQuery, + db: DatabaseConnection, + dialogue: MainDialogue, + hash: super::NameHash, +) -> crate::Result<()> { + let mut msg: MessageIds = q.message.as_ref().unwrap().into(); + let user_id = q.from.id.0; + let name = match name_from_hash(&db, user_id, &hash).await? { + Some(name) => name, + None => { + msg.alter_message( + &bot, + "Account wan't found. Select another one", + menu_markup("decrypt", user_id, &db).await?, + None, + ) + .await?; + return Ok(()); + } + }; + + msg.alter_message(&bot, "Send master password", None, None) + .await?; + + change_state!(dialogue, msg, (name), State::GetMasterPass, get_master_pass) +} diff --git a/src/callbacks/delete.rs b/src/callbacks/delete.rs new file mode 100644 index 0000000..d9c2094 --- /dev/null +++ b/src/callbacks/delete.rs @@ -0,0 +1,63 @@ +use crate::{change_state, prelude::*}; + +async fn get_master_pass( + bot: Throttle, + msg: Message, + db: DatabaseConnection, + dialogue: MainDialogue, + mut ids: MessageIds, + name: String, + _: String, +) -> crate::Result<()> { + dialogue.exit().await?; + + let user_id = msg.from().ok_or(NoUserInfo)?.id.0; + Account::delete_by_id((user_id, name)).exec(&db).await?; + + ids.alter_message( + &bot, + "The account is successfully deleted", + deletion_markup(), + None, + ) + .await?; + + Ok(()) +} + +pub async fn delete( + bot: Throttle, + q: CallbackQuery, + db: DatabaseConnection, + dialogue: MainDialogue, + hash: super::NameHash, +) -> crate::Result<()> { + let mut msg: MessageIds = q.message.as_ref().unwrap().into(); + let user_id = q.from.id.0; + let name = match name_from_hash(&db, user_id, &hash).await? { + Some(name) => name, + None => { + msg.alter_message( + &bot, + "Account wan't found. Select another one", + menu_markup("delete", user_id, &db).await?, + None, + ) + .await?; + return Ok(()); + } + }; + + let previous = bot.send_message( + msg.0, + "Send master password. Once you send correct master password the account is unrecoverable" + ).await?; + + change_state!( + dialogue, + &previous, + (name), + State::GetMasterPass, + get_master_pass + ) +} diff --git a/src/callbacks/get.rs b/src/callbacks/get.rs index 8c6948a..296c2cc 100644 --- a/src/callbacks/get.rs +++ b/src/callbacks/get.rs @@ -10,8 +10,7 @@ pub async fn get( let user_id = q.from.id.0; let msg = q.message.as_ref().unwrap(); - let hash = hex::encode(hash); - let name = match Account::get_name_by_hash(user_id, hash, &db).await? { + let name = match name_from_hash(&db, user_id, &hash).await? { Some(name) => name, None => { bot.edit_message_text( @@ -19,13 +18,13 @@ pub async fn get( msg.id, "Account wan't found. Select another one", ) - .reply_markup(menu_markup(user_id, &db).await?) + .reply_markup(menu_markup("get", user_id, &db).await?) .await?; return Ok(()); } }; - let text = format!("Name:\n`{name}`\nLogin:\n***\nPassword:\n***"); + let text = format!("Name:\n`{name}`\nLogin:\n\\*\\*\\*\nPassword:\n\\*\\*\\*"); bot.send_message(msg.chat.id, text) .reply_markup(account_markup(&name, true)) .parse_mode(ParseMode::MarkdownV2) diff --git a/src/callbacks/get_menu.rs b/src/callbacks/get_menu.rs index ea0eadd..77d2ac1 100644 --- a/src/callbacks/get_menu.rs +++ b/src/callbacks/get_menu.rs @@ -18,9 +18,10 @@ pub async fn get_menu( bot.edit_message_text(msg.chat.id, msg.id, "You don't have any accounts") .reply_markup(deletion_markup()) .await?; + return Ok(()); } - let markup = spawn_blocking(|| menu_markup_sync(names)).await?; + let markup = spawn_blocking(|| menu_markup_sync("get", names)).await?; bot.edit_message_text(msg.chat.id, msg.id, "Choose your account") .reply_markup(markup) .await?; diff --git a/src/callbacks/mod.rs b/src/callbacks/mod.rs index 8e00e18..65b711c 100644 --- a/src/callbacks/mod.rs +++ b/src/callbacks/mod.rs @@ -1,9 +1,13 @@ //! This module consists of endpoints to handle callbacks +mod decrypt; +mod delete; mod delete_message; mod get; mod get_menu; +pub use decrypt::decrypt; +pub use delete::delete; pub use delete_message::delete_message; pub use get::get; pub use get_menu::get_menu; @@ -36,7 +40,7 @@ pub enum CallbackCommand { impl CallbackCommand { pub fn from_query(q: CallbackQuery) -> Option { q.message.as_ref()?; - q.data.and_then(|text| dbg!(text.parse()).ok()) + q.data.and_then(|data| data.parse().ok()) } } @@ -47,8 +51,6 @@ impl FromStr for CallbackCommand { use AlterableField::*; use CallbackCommand::*; - println!("{s}"); - match s { "delete_message" => return Ok(DeleteMessage), "get_menu" => return Ok(GetMenu), diff --git a/src/commands/delete.rs b/src/commands/delete.rs index 39715da..2c80c36 100644 --- a/src/commands/delete.rs +++ b/src/commands/delete.rs @@ -1,40 +1,11 @@ use crate::prelude::*; -/// Gets the master password and deletes the account. -/// Although it doesn't use the master password, we get it to be sure that it's the user who used that command -async fn get_master_pass( - bot: Throttle, - msg: Message, - db: DatabaseConnection, - dialogue: MainDialogue, - mut ids: MessageIds, - name: String, - _: String, -) -> crate::Result<()> { - dialogue.exit().await?; - +pub async fn delete(bot: Throttle, msg: Message, db: DatabaseConnection) -> crate::Result<()> { let user_id = msg.from().ok_or(NoUserInfo)?.id.0; - Account::delete_by_id((user_id, name)).exec(&db).await?; - ids.alter_message( - &bot, - "The account is successfully deleted", - deletion_markup(), - None, - ) - .await?; + bot.send_message(msg.chat.id, "Choose the account to delete") + .reply_markup(menu_markup("delete", user_id, &db).await?) + .await?; Ok(()) } - -handler!( - get_account_name(name: String), - "Send master password. Once you send correct master password the account is unrecoverable", - State::GetMasterPass, - get_master_pass -); -ask_name_handler!( - delete, - "Send the name of the account to delete", - get_account_name -); diff --git a/src/commands/get_account.rs b/src/commands/get_account.rs index 84e7f54..aef0be9 100644 --- a/src/commands/get_account.rs +++ b/src/commands/get_account.rs @@ -1,42 +1,15 @@ use crate::prelude::*; -use teloxide::types::ParseMode; -use tokio::task::spawn_blocking; -/// Gets the master password, decryptes the account and sends it to the user with copyable fields -async fn get_master_pass( +pub async fn get_account( bot: Throttle, msg: Message, db: DatabaseConnection, - dialogue: MainDialogue, - mut ids: MessageIds, - name: String, - master_pass: String, ) -> crate::Result<()> { - dialogue.exit().await?; - let user_id = msg.from().ok_or(NoUserInfo)?.id.0; - let account = match Account::get(user_id, &name, &db).await? { - Some(account) => account, - None => { - bot.send_message(msg.chat.id, "Account not found") - .reply_markup(deletion_markup()) - .await?; - return Ok(()); - } - }; - - let (login, password) = spawn_blocking(move || account.decrypt(&master_pass)).await??; - let text = format!("Name:\n`{name}`\nLogin:\n`{login}`\nPassword:\n`{password}`"); - - ids.alter_message(&bot, text, deletion_markup(), ParseMode::MarkdownV2) + bot.send_message(msg.chat.id, "Choose the account to get") + .reply_markup(menu_markup("decrypt", user_id, &db).await?) .await?; + Ok(()) } - -handler!(get_account_name(name:String), "Send master password", State::GetMasterPass, get_master_pass); -ask_name_handler!( - get_account, - "Send the name of the account to get", - get_account_name -); diff --git a/src/commands/menu.rs b/src/commands/menu.rs index 3082653..f9e885d 100644 --- a/src/commands/menu.rs +++ b/src/commands/menu.rs @@ -15,7 +15,7 @@ pub async fn menu(bot: Throttle, msg: Message, db: DatabaseConnection) -> c .await?; } - let markup = spawn_blocking(|| menu_markup_sync(names)).await?; + let markup = spawn_blocking(|| menu_markup_sync("get", names)).await?; bot.send_message(msg.chat.id, "Choose your account") .reply_markup(markup) .await?; diff --git a/src/macros.rs b/src/macros.rs index 05e55bb..0c1ff78 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -34,7 +34,7 @@ macro_rules! handler { #[inline] async fn $function_name( bot: Throttle, - msg: Message, + _: Message, _: DatabaseConnection, dialogue: MainDialogue, mut ids: MessageIds, @@ -47,34 +47,6 @@ macro_rules! handler { }; } -#[macro_export] -macro_rules! ask_name_handler { - ($function_name: ident, $message: literal, $next_func: ident) => { - #[inline] - pub async fn $function_name( - bot: Throttle, - msg: Message, - dialogue: MainDialogue, - db: DatabaseConnection, - ) -> $crate::Result<()> { - let user_id = msg.from().ok_or(NoUserInfo)?.id.0; - let markup = account_list_markup(user_id, &db).await?; - if markup.keyboard.is_empty() { - bot.send_message(msg.chat.id, "No accounts found") - .reply_markup(deletion_markup()) - .await?; - return Ok(()); - } - let previous = bot - .send_message(msg.chat.id, $message) - .reply_markup(markup) - .await?; - - $crate::change_state!(dialogue, &previous, (), State::GetExistingName, $next_func) - } - }; -} - #[macro_export] macro_rules! simple_state_handler { ($function_name: ident, $check: ident, $no_text_message: literal) => { diff --git a/src/main.rs b/src/main.rs index 42bc2e3..4e31dbe 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,3 @@ -#![allow(unused)] - mod callbacks; mod commands; mod default; @@ -49,8 +47,6 @@ fn get_dispatcher( let message_handler = Update::filter_message() .map_async(utils::delete_message) - .enter_dialogue::, State>() - .branch(case![State::GetExistingName(next)].endpoint(state::get_existing_name)) .branch(case![State::GetNewName(next)].endpoint(state::get_new_name)) .branch(case![State::GetMasterPass(next)].endpoint(state::get_master_pass)) .branch(case![State::GetNewMasterPass(next)].endpoint(state::get_new_master_pass)) @@ -64,9 +60,12 @@ fn get_dispatcher( .filter_map(CallbackCommand::from_query) .branch(case![CallbackCommand::GetMenu].endpoint(callbacks::get_menu)) .branch(case![CallbackCommand::DeleteMessage].endpoint(callbacks::delete_message)) - .branch(case![CallbackCommand::Get(hash)].endpoint(callbacks::get)); + .branch(case![CallbackCommand::Get(hash)].endpoint(callbacks::get)) + .branch(case![CallbackCommand::Decrypt(hash)].endpoint(callbacks::decrypt)) + .branch(case![CallbackCommand::DeleteAccount(hash)].endpoint(callbacks::delete)); let handler = dptree::entry() + .enter_dialogue::, State>() .branch(message_handler) .branch(callback_handler); diff --git a/src/markups.rs b/src/markups.rs index 6f5c730..106260e 100644 --- a/src/markups.rs +++ b/src/markups.rs @@ -2,36 +2,21 @@ use crate::prelude::*; use base64::{engine::general_purpose::STANDARD_NO_PAD as B64_ENGINE, Engine as _}; use itertools::Itertools; use sha2::{Digest, Sha256}; -use teloxide::types::{InlineKeyboardButton, InlineKeyboardMarkup, KeyboardButton, KeyboardMarkup}; +use teloxide::types::{InlineKeyboardButton, InlineKeyboardMarkup}; use tokio::task::spawn_blocking; -/// Creates a markup of all user's account names #[inline] -pub async fn account_list_markup( - user_id: u64, - db: &DatabaseConnection, -) -> crate::Result { - let account_names: Vec> = Account::get_names(user_id, db) - .await? - .map_ok(KeyboardButton::new) - .try_chunks(3) - .map_err(|err| err.1) - .try_collect() - .await?; - let markup = KeyboardMarkup::new(account_names) - .resize_keyboard(true) - .one_time_keyboard(true); - Ok(markup) -} - -#[inline] -pub fn menu_markup_sync(names: impl IntoIterator) -> InlineKeyboardMarkup { +pub fn menu_markup_sync( + command: &str, + names: impl IntoIterator, +) -> InlineKeyboardMarkup { let names = names .into_iter() .map(|name| { let hash = ::digest(name.as_bytes()); - let mut data = "get ".to_owned(); - data.reserve(43); + let mut data = command.to_owned(); + data.reserve(44); + data.push(' '); B64_ENGINE.encode_string(hash, &mut data); InlineKeyboardButton::callback(name, data) }) @@ -42,12 +27,14 @@ pub fn menu_markup_sync(names: impl IntoIterator) -> InlineKeyboa #[inline] pub async fn menu_markup( + command: impl Into, user_id: u64, db: &DatabaseConnection, ) -> crate::Result { + let command: String = command.into(); let names: Vec = Account::get_names(user_id, db).await?.try_collect().await?; - spawn_blocking(|| menu_markup_sync(names)) + spawn_blocking(move || menu_markup_sync(&command, names)) .await .map_err(Into::into) } @@ -69,26 +56,26 @@ pub fn account_markup(name: &str, is_encrypted: bool) -> InlineKeyboardMarkup { .unwrap(); let hash = std::str::from_utf8(&hash).unwrap(); - let alter_buttons = [ + let encryption_button = if is_encrypted { + ("Decrypt", "decrypt") + } else { + ("Hide", "get") + }; + + let main_buttons = [ ("Alter name", "an"), ("Alter login", "al"), ("Alter password", "ap"), + encryption_button, + ("Delete account", "delete"), ] - .map(|(text, command)| make_button(text, command, hash)); - - let mut second_raw = Vec::new(); - if is_encrypted { - second_raw.push(make_button("Decrypt", "decrypt", hash)) - } else { - second_raw.push(make_button("Hide", "hide", hash)); - } - second_raw.push(make_button("Delete account", "delete", hash)); + .into_iter() + .map(|(text, command)| make_button(text, command, hash)) + .chunks(3); let menu_button = InlineKeyboardButton::callback("Back to the menu", "get_menu"); - InlineKeyboardMarkup::new([alter_buttons]) - .append_row(second_raw) - .append_row([menu_button]) + InlineKeyboardMarkup::new(&main_buttons).append_row([menu_button]) } /// Creates a markup with a "Delete message" button. diff --git a/src/prelude.rs b/src/prelude.rs index 626f825..8329a98 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -1,5 +1,4 @@ pub(crate) use crate::{ - ask_name_handler, commands::Command, errors::*, first_handler, handler, diff --git a/src/state/get_existing_name.rs b/src/state/get_existing_name.rs deleted file mode 100644 index ed4d88f..0000000 --- a/src/state/get_existing_name.rs +++ /dev/null @@ -1,60 +0,0 @@ -use crate::prelude::*; - -/// Function to handle GetExistingName state -pub async fn get_existing_name( - bot: Throttle, - msg: Message, - db: DatabaseConnection, - dialogue: MainDialogue, - next: PackagedHandler, -) -> crate::Result<()> { - let user_id = msg.from().ok_or(NoUserInfo)?.id.0; - - let mut handler = next.lock().await; - if handler.func.is_none() { - let _ = dialogue.exit().await; - return Err(HandlerUsed.into()); - } - - let text = match msg.text() { - Some(text) => text.trim(), - None => { - let msg = bot - .send_message( - msg.chat.id, - "Couldn't get the text of the message. Send the name again", - ) - .reply_markup(account_list_markup(user_id, &db).await?) - .await?; - handler.previous = MessageIds::from(&msg); - return Ok(()); - } - }; - - if text == "/cancel" { - dialogue.exit().await?; - handler - .previous - .alter_message(&bot, "Successfully cancelled", deletion_markup(), None) - .await?; - return Ok(()); - } - - if !Account::exists(user_id, text, &db).await? { - let msg = bot - .send_message(msg.chat.id, "Account doesn't exists. Try again") - .reply_markup(account_list_markup(user_id, &db).await?) - .await?; - handler.previous = MessageIds::from(&msg); - return Ok(()); - } - - let func = handler.func.take().unwrap(); - let text = text.to_owned(); - - if let Err(err) = func(bot, msg, db, dialogue.clone(), handler.previous, text).await { - let _ = dialogue.exit().await; - return Err(err); - } - Ok(()) -} diff --git a/src/state/handler.rs b/src/state/handler.rs index c1ee2b7..9fcfe1a 100644 --- a/src/state/handler.rs +++ b/src/state/handler.rs @@ -39,7 +39,7 @@ impl MessageIds { } match edit.send_ref().await { - Ok(msg) => return Ok(()), + Ok(_) => return Ok(()), Err(RequestError::Api(_)) => (), Err(err) => return Err(err.into()), }; diff --git a/src/state/mod.rs b/src/state/mod.rs index eaf432e..02d71bd 100644 --- a/src/state/mod.rs +++ b/src/state/mod.rs @@ -1,7 +1,6 @@ //! This module consists of endpoints to handle the state mod generic; -mod get_existing_name; mod get_login; mod get_master_pass; mod get_new_master_pass; @@ -10,7 +9,6 @@ mod get_password; mod get_user; mod handler; -pub use get_existing_name::get_existing_name; pub use get_login::get_login; pub use get_master_pass::get_master_pass; pub use get_new_master_pass::get_new_master_pass; @@ -26,7 +24,6 @@ use teloxide::dispatching::dialogue::InMemStorage; pub enum State { #[default] Start, - GetExistingName(PackagedHandler), GetNewName(PackagedHandler), GetMasterPass(PackagedHandler), GetNewMasterPass(PackagedHandler), diff --git a/src/utils.rs b/src/utils.rs index de8e29c..34658d5 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -16,3 +16,14 @@ pub fn validate_field(field: &str) -> bool { .chars() .all(|char| !['`', '\\', '\n', '\t'].contains(&char)) } + +pub async fn name_from_hash( + db: &DatabaseConnection, + user_id: u64, + hash: &[u8], +) -> crate::Result> { + let hash = hex::encode(hash); + Account::get_name_by_hash(user_id, hash, db) + .await + .map_err(Into::into) +}