Sepparated the code out into 3 more library crates: cryptography, entity and pass_manager
This commit is contained in:
62
src/state/generic.rs
Normal file
62
src/state/generic.rs
Normal file
@ -0,0 +1,62 @@
|
||||
use crate::{errors::HandlerUsed, markups::deletion_markup, utils::delete_optional, PinnedFuture};
|
||||
use sea_orm::prelude::*;
|
||||
use teloxide::{adaptors::Throttle, prelude::*};
|
||||
|
||||
/// A generic state handler. It checks for "/cancel" messages and runs the provided validation function
|
||||
#[inline]
|
||||
pub async fn generic<F>(
|
||||
bot: Throttle<Bot>,
|
||||
msg: Message,
|
||||
db: DatabaseConnection,
|
||||
dialogue: crate::MainDialogue,
|
||||
check: F,
|
||||
no_text_message: impl Into<String>,
|
||||
next: super::PackagedHandler<String>,
|
||||
) -> crate::Result<()>
|
||||
where
|
||||
for<'a> F: FnOnce(
|
||||
&'a Throttle<Bot>,
|
||||
&'a Message,
|
||||
&'a DatabaseConnection,
|
||||
&'a str,
|
||||
) -> PinnedFuture<'a, crate::Result<Option<Message>>>,
|
||||
{
|
||||
let mut handler = next.lock().await;
|
||||
delete_optional(&bot, handler.previous.as_ref()).await;
|
||||
|
||||
let text = match msg.text() {
|
||||
Some(text) => text.trim_end(),
|
||||
None => {
|
||||
bot.send_message(msg.chat.id, no_text_message).await?;
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
if text == "/cancel" {
|
||||
dialogue.exit().await?;
|
||||
bot.send_message(msg.chat.id, "Successfully cancelled")
|
||||
.reply_markup(deletion_markup())
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if handler.func.is_none() {
|
||||
let _ = dialogue.exit().await;
|
||||
return Err(HandlerUsed.into());
|
||||
}
|
||||
|
||||
if let Some(failure_message) = check(&bot, &msg, &db, text).await? {
|
||||
handler.previous = Some(failure_message);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let func = handler.func.take().unwrap();
|
||||
drop(handler);
|
||||
let text = text.to_owned();
|
||||
|
||||
if let Err(err) = func(bot, msg, db, dialogue.clone(), text).await {
|
||||
let _ = dialogue.exit().await;
|
||||
return Err(err);
|
||||
}
|
||||
Ok(())
|
||||
}
|
110
src/state/get_account_name.rs
Normal file
110
src/state/get_account_name.rs
Normal file
@ -0,0 +1,110 @@
|
||||
use crate::{
|
||||
errors::{HandlerUsed, NoUserInfo},
|
||||
markups::{account_markup, deletion_markup},
|
||||
utils::{delete_optional, validate_field},
|
||||
MainDialogue,
|
||||
};
|
||||
use entity::prelude::*;
|
||||
use sea_orm::DatabaseConnection;
|
||||
use teloxide::{adaptors::Throttle, prelude::*};
|
||||
|
||||
/// Specifies the kind of checks to be run during the account name validation
|
||||
#[derive(Clone, Copy)]
|
||||
pub enum NameCheckKind {
|
||||
NewAccountName,
|
||||
MustExist,
|
||||
}
|
||||
|
||||
/// Validates the account name
|
||||
#[inline]
|
||||
async fn check_name(
|
||||
bot: &Throttle<Bot>,
|
||||
msg: &Message,
|
||||
db: &DatabaseConnection,
|
||||
name: &str,
|
||||
check_kind: NameCheckKind,
|
||||
user_id: u64,
|
||||
) -> crate::Result<Option<Message>> {
|
||||
match check_kind {
|
||||
NameCheckKind::MustExist => {
|
||||
if !Account::exists(user_id, name, db).await? {
|
||||
let msg = bot
|
||||
.send_message(msg.chat.id, "Account doesn't exists. Try again")
|
||||
.reply_markup(account_markup(user_id, db).await?)
|
||||
.await?;
|
||||
return Ok(Some(msg));
|
||||
}
|
||||
}
|
||||
NameCheckKind::NewAccountName => {
|
||||
if Account::exists(user_id, name, db).await? {
|
||||
let msg = bot
|
||||
.send_message(msg.chat.id, "Account already exists")
|
||||
.await?;
|
||||
return Ok(Some(msg));
|
||||
}
|
||||
if !validate_field(name) {
|
||||
let msg = bot
|
||||
.send_message(msg.chat.id, "Invalid account name. Try again")
|
||||
.await?;
|
||||
return Ok(Some(msg));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Function to handle GetAccountName state
|
||||
pub async fn get_account_name(
|
||||
bot: Throttle<Bot>,
|
||||
msg: Message,
|
||||
db: DatabaseConnection,
|
||||
dialogue: MainDialogue,
|
||||
(next, check_kind): (super::PackagedHandler<String>, NameCheckKind),
|
||||
) -> crate::Result<()> {
|
||||
let user_id = msg.from().ok_or(NoUserInfo)?.id.0;
|
||||
let mut handler = next.lock().await;
|
||||
delete_optional(&bot, handler.previous.as_ref()).await;
|
||||
|
||||
let text = match msg.text() {
|
||||
Some(text) => text.trim_end(),
|
||||
None => {
|
||||
let mut send = bot.send_message(
|
||||
msg.chat.id,
|
||||
"Couldn't get the text of the message. Send the name again",
|
||||
);
|
||||
if let NameCheckKind::MustExist = check_kind {
|
||||
send = send.reply_markup(account_markup(user_id, &db).await?)
|
||||
}
|
||||
send.await?;
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
if text == "/cancel" {
|
||||
dialogue.exit().await?;
|
||||
bot.send_message(msg.chat.id, "Successfully cancelled")
|
||||
.reply_markup(deletion_markup())
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if handler.func.is_none() {
|
||||
let _ = dialogue.exit().await;
|
||||
return Err(HandlerUsed.into());
|
||||
}
|
||||
|
||||
if let Some(failure_message) = check_name(&bot, &msg, &db, text, check_kind, user_id).await? {
|
||||
handler.previous = Some(failure_message);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let func = handler.func.take().unwrap();
|
||||
drop(handler);
|
||||
let text = text.to_owned();
|
||||
|
||||
if let Err(err) = func(bot, msg, db, dialogue.clone(), text).await {
|
||||
let _ = dialogue.exit().await;
|
||||
return Err(err);
|
||||
}
|
||||
Ok(())
|
||||
}
|
34
src/state/get_login.rs
Normal file
34
src/state/get_login.rs
Normal file
@ -0,0 +1,34 @@
|
||||
use crate::{utils::validate_field, MainDialogue};
|
||||
use sea_orm::prelude::*;
|
||||
use teloxide::{adaptors::Throttle, prelude::*};
|
||||
|
||||
/// Function to handle GetLogin state
|
||||
pub async fn get_login(
|
||||
bot: Throttle<Bot>,
|
||||
msg: Message,
|
||||
db: DatabaseConnection,
|
||||
dialogue: MainDialogue,
|
||||
next: super::PackagedHandler<String>,
|
||||
) -> crate::Result<()> {
|
||||
super::generic::generic(
|
||||
bot,
|
||||
msg,
|
||||
db,
|
||||
dialogue,
|
||||
|bot, msg, _, login| {
|
||||
Box::pin(async move {
|
||||
let is_valid = validate_field(login);
|
||||
if !is_valid {
|
||||
let msg = bot
|
||||
.send_message(msg.chat.id, "Invalid login. Try again")
|
||||
.await?;
|
||||
return Ok(Some(msg));
|
||||
}
|
||||
Ok(None)
|
||||
})
|
||||
},
|
||||
"Couldn't get the text of the message. Send the login again",
|
||||
next,
|
||||
)
|
||||
.await
|
||||
}
|
63
src/state/get_master_pass.rs
Normal file
63
src/state/get_master_pass.rs
Normal file
@ -0,0 +1,63 @@
|
||||
use crate::{errors::NoUserInfo, MainDialogue};
|
||||
use cryptography::prelude::*;
|
||||
use entity::prelude::*;
|
||||
use log::error;
|
||||
use sea_orm::DatabaseConnection;
|
||||
use teloxide::{adaptors::Throttle, prelude::*};
|
||||
use tokio::task::spawn_blocking;
|
||||
|
||||
/// Returns true if the provided master password is valid
|
||||
#[inline]
|
||||
async fn check_master_pass(
|
||||
bot: &Throttle<Bot>,
|
||||
msg: &Message,
|
||||
db: &DatabaseConnection,
|
||||
master_pass: &str,
|
||||
) -> crate::Result<Option<Message>> {
|
||||
let user_id = msg.from().ok_or(NoUserInfo)?.id.0;
|
||||
let model = MasterPass::get(user_id, db).await?;
|
||||
|
||||
let is_valid = match model {
|
||||
Some(model) => {
|
||||
let master_pass = master_pass.to_owned();
|
||||
spawn_blocking(move || model.verify(&master_pass)).await?
|
||||
}
|
||||
None => {
|
||||
error!("User was put into the GetMasterPass state with no master password set");
|
||||
let msg = bot
|
||||
.send_message(
|
||||
msg.chat.id,
|
||||
"No master password set. Use /cancel and set it by using /set_master_pass",
|
||||
)
|
||||
.await?;
|
||||
return Ok(Some(msg));
|
||||
}
|
||||
};
|
||||
|
||||
if !is_valid {
|
||||
let msg = bot
|
||||
.send_message(msg.chat.id, "Wrong master password. Try again")
|
||||
.await?;
|
||||
return Ok(Some(msg));
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub async fn get_master_pass(
|
||||
bot: Throttle<Bot>,
|
||||
msg: Message,
|
||||
db: DatabaseConnection,
|
||||
dialogue: MainDialogue,
|
||||
next: super::PackagedHandler<String>,
|
||||
) -> crate::Result<()> {
|
||||
super::generic::generic(
|
||||
bot,
|
||||
msg,
|
||||
db,
|
||||
dialogue,
|
||||
|bot, msg, db, text| Box::pin(check_master_pass(bot, msg, db, text)),
|
||||
"Couldn't get the text of the message. Send the master password again",
|
||||
next,
|
||||
)
|
||||
.await
|
||||
}
|
35
src/state/get_password.rs
Normal file
35
src/state/get_password.rs
Normal file
@ -0,0 +1,35 @@
|
||||
use sea_orm::prelude::*;
|
||||
use teloxide::{adaptors::Throttle, prelude::*};
|
||||
|
||||
use crate::{utils::validate_field, MainDialogue};
|
||||
|
||||
/// Function to handle GetPassword state
|
||||
pub async fn get_password(
|
||||
bot: Throttle<Bot>,
|
||||
msg: Message,
|
||||
db: DatabaseConnection,
|
||||
dialogue: MainDialogue,
|
||||
next: super::PackagedHandler<String>,
|
||||
) -> crate::Result<()> {
|
||||
super::generic::generic(
|
||||
bot,
|
||||
msg,
|
||||
db,
|
||||
dialogue,
|
||||
|bot, msg, _, password| {
|
||||
Box::pin(async move {
|
||||
let is_valid = validate_field(password);
|
||||
if !is_valid {
|
||||
let msg = bot
|
||||
.send_message(msg.chat.id, "Invalid password. Try again")
|
||||
.await?;
|
||||
return Ok(Some(msg));
|
||||
}
|
||||
Ok(None)
|
||||
})
|
||||
},
|
||||
"Couldn't get the text of the message. Send the master password again",
|
||||
next,
|
||||
)
|
||||
.await
|
||||
}
|
87
src/state/get_user.rs
Normal file
87
src/state/get_user.rs
Normal file
@ -0,0 +1,87 @@
|
||||
use crate::{
|
||||
errors::HandlerUsed, markups::deletion_markup, models::User, utils::delete_optional,
|
||||
MainDialogue,
|
||||
};
|
||||
use futures::TryStreamExt;
|
||||
use sea_orm::prelude::*;
|
||||
use teloxide::{adaptors::Throttle, net::Download, prelude::*};
|
||||
use tokio::task::spawn_blocking;
|
||||
|
||||
/// Function to handle GetUser state. It doesn't actually validate anything
|
||||
pub async fn get_user(
|
||||
bot: Throttle<Bot>,
|
||||
msg: Message,
|
||||
db: DatabaseConnection,
|
||||
dialogue: MainDialogue,
|
||||
next: super::PackagedHandler<User>,
|
||||
) -> crate::Result<()> {
|
||||
let mut handler = next.lock().await;
|
||||
delete_optional(&bot, handler.previous.as_ref()).await;
|
||||
|
||||
if let Some("/cancel") = msg.text().map(str::trim_end) {
|
||||
dialogue.exit().await?;
|
||||
bot.send_message(msg.chat.id, "Successfully cancelled")
|
||||
.reply_markup(deletion_markup())
|
||||
.await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if handler.func.is_none() {
|
||||
let _ = dialogue.exit().await;
|
||||
return Err(HandlerUsed.into());
|
||||
}
|
||||
|
||||
let document = match msg.document() {
|
||||
Some(document) => document,
|
||||
None => {
|
||||
let msg = bot
|
||||
.send_message(msg.chat.id, "You didn't send a file. Try again")
|
||||
.await?;
|
||||
handler.previous = Some(msg);
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
match document.file_name.as_deref() {
|
||||
Some(name) if name.trim_end().ends_with(".json") => (),
|
||||
_ => {
|
||||
let msg = bot
|
||||
.send_message(
|
||||
msg.chat.id,
|
||||
"Invalid file name. You need to send a json file. Try again",
|
||||
)
|
||||
.await?;
|
||||
handler.previous = Some(msg);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
let file = bot.get_file(&document.file.id).await?;
|
||||
let mut data = Vec::with_capacity(document.file.size as usize);
|
||||
bot.download_file_stream(&file.path)
|
||||
.try_for_each(|bytes| {
|
||||
data.extend(bytes);
|
||||
async { Ok(()) }
|
||||
})
|
||||
.await?;
|
||||
|
||||
let user: User = match spawn_blocking(move || serde_json::from_slice(&data)).await? {
|
||||
Ok(user) => user,
|
||||
Err(_) => {
|
||||
let msg = bot
|
||||
.send_message(msg.chat.id, "Error parsing the json file. Try again")
|
||||
.await?;
|
||||
handler.previous = Some(msg);
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
let func = handler.func.take().unwrap();
|
||||
drop(handler);
|
||||
|
||||
if let Err(err) = func(bot, msg, db, dialogue.clone(), user).await {
|
||||
let _ = dialogue.exit().await;
|
||||
return Err(err);
|
||||
}
|
||||
Ok(())
|
||||
}
|
46
src/state/handler.rs
Normal file
46
src/state/handler.rs
Normal file
@ -0,0 +1,46 @@
|
||||
use crate::MainDialogue;
|
||||
use sea_orm::prelude::*;
|
||||
use std::{future::Future, sync::Arc};
|
||||
use teloxide::{adaptors::Throttle, prelude::*};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
type DynHanlder<T> = Box<
|
||||
dyn FnOnce(
|
||||
Throttle<Bot>,
|
||||
Message,
|
||||
DatabaseConnection,
|
||||
MainDialogue,
|
||||
T,
|
||||
) -> crate::PinnedFuture<'static, crate::Result<()>>
|
||||
+ Send
|
||||
+ Sync,
|
||||
>;
|
||||
|
||||
pub struct Handler<T> {
|
||||
pub func: Option<DynHanlder<T>>,
|
||||
|
||||
pub previous: Option<Message>,
|
||||
}
|
||||
|
||||
pub type PackagedHandler<T> = Arc<Mutex<Handler<T>>>;
|
||||
|
||||
impl<T> Handler<T> {
|
||||
/// Convinience method to convert a simple async function and a previous message into PackagedHandler
|
||||
#[inline]
|
||||
pub fn new<H, F>(f: H, previous: impl Into<Option<Message>>) -> PackagedHandler<T>
|
||||
where
|
||||
H: FnOnce(Throttle<Bot>, Message, DatabaseConnection, MainDialogue, T) -> F
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static,
|
||||
F: Future<Output = crate::Result<()>> + Send + 'static,
|
||||
{
|
||||
let handler = Self {
|
||||
func: Some(Box::new(move |bot, msg, db, dialogue, val| {
|
||||
Box::pin(f(bot, msg, db, dialogue, val))
|
||||
})),
|
||||
previous: previous.into(),
|
||||
};
|
||||
Arc::new(Mutex::new(handler))
|
||||
}
|
||||
}
|
32
src/state/mod.rs
Normal file
32
src/state/mod.rs
Normal file
@ -0,0 +1,32 @@
|
||||
//! This module consists of endpoints to handle the state
|
||||
|
||||
mod generic;
|
||||
mod get_account_name;
|
||||
mod get_login;
|
||||
mod get_master_pass;
|
||||
mod get_password;
|
||||
mod get_user;
|
||||
mod handler;
|
||||
|
||||
pub use get_account_name::{get_account_name, NameCheckKind};
|
||||
pub use get_login::get_login;
|
||||
pub use get_master_pass::get_master_pass;
|
||||
pub use get_password::get_password;
|
||||
pub use get_user::get_user;
|
||||
pub use handler::{Handler, PackagedHandler};
|
||||
|
||||
use crate::models::User;
|
||||
use teloxide::{dispatching::dialogue::InMemStorage, prelude::*};
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub enum State {
|
||||
#[default]
|
||||
Start,
|
||||
GetAccountName(PackagedHandler<String>, NameCheckKind),
|
||||
GetMasterPass(PackagedHandler<String>),
|
||||
GetLogin(PackagedHandler<String>),
|
||||
GetPassword(PackagedHandler<String>),
|
||||
GetUser(PackagedHandler<User>),
|
||||
}
|
||||
|
||||
pub type MainDialogue = Dialogue<State, InMemStorage<State>>;
|
Reference in New Issue
Block a user