From e8fc43f9adbfa214393bd33fcfa8894f6925def7 Mon Sep 17 00:00:00 2001 From: StNicolay Date: Thu, 27 Apr 2023 16:25:23 +0300 Subject: [PATCH] Extended functionality --- Cargo.lock | 244 ++++++----------------------------- Cargo.toml | 8 +- src/entity/account.rs | 85 +++++++++++- src/entity/master_pass.rs | 24 +++- src/handlers/add_account.rs | 18 +++ src/handlers/default.rs | 6 + src/handlers/get_account.rs | 26 ++++ src/handlers/get_accounts.rs | 34 +++++ src/handlers/help.rs | 7 + src/handlers/mod.rs | 56 ++++++++ src/main.rs | 124 ++---------------- 11 files changed, 305 insertions(+), 327 deletions(-) create mode 100644 src/handlers/add_account.rs create mode 100644 src/handlers/default.rs create mode 100644 src/handlers/get_account.rs create mode 100644 src/handlers/get_accounts.rs create mode 100644 src/handlers/help.rs create mode 100644 src/handlers/mod.rs diff --git a/Cargo.lock b/Cargo.lock index a025b4a..67dac05 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -480,9 +480,9 @@ dependencies = [ [[package]] name = "clap" -version = "3.2.23" +version = "3.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5" +checksum = "eef2b3ded6a26dfaec672a742c93c8cf6b689220324da509ec5caa20de55dc83" dependencies = [ "bitflags", "clap_derive", @@ -494,9 +494,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "3.2.18" +version = "3.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65" +checksum = "d756c5824fc5c0c1ee8e36000f576968dbcb2081def956c83fad6f40acd46f96" dependencies = [ "heck 0.4.1", "proc-macro-error", @@ -545,16 +545,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" -[[package]] -name = "core-foundation" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "core-foundation-sys" version = "0.8.4" @@ -835,21 +825,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "form_urlencoded" version = "1.1.0" @@ -1178,16 +1153,16 @@ dependencies = [ ] [[package]] -name = "hyper-tls" -version = "0.5.0" +name = "hyper-rustls" +version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c" dependencies = [ - "bytes", + "http", "hyper", - "native-tls", + "rustls", "tokio", - "tokio-native-tls", + "tokio-rustls", ] [[package]] @@ -1430,24 +1405,6 @@ dependencies = [ "windows-sys 0.45.0", ] -[[package]] -name = "native-tls" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" -dependencies = [ - "lazy_static", - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - [[package]] name = "never" version = "0.1.0" @@ -1545,50 +1502,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" -[[package]] -name = "openssl" -version = "0.10.51" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97ea2d98598bf9ada7ea6ee8a30fb74f9156b63bbe495d64ec2b87c269d2dda3" -dependencies = [ - "bitflags", - "cfg-if", - "foreign-types", - "libc", - "once_cell", - "openssl-macros", - "openssl-sys", -] - -[[package]] -name = "openssl-macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.15", -] - -[[package]] -name = "openssl-probe" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - -[[package]] -name = "openssl-sys" -version = "0.9.86" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "992bac49bdbab4423199c654a5515bd2a6c6a23bf03f2dd3bdb7e5ae6259bc69" -dependencies = [ - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "os_str_bytes" version = "6.5.0" @@ -1644,7 +1557,7 @@ dependencies = [ "cfg-if", "instant", "libc", - "redox_syscall 0.2.16", + "redox_syscall", "smallvec", "winapi", ] @@ -1660,11 +1573,11 @@ dependencies = [ "migration", "pbkdf2", "pretty_env_logger", + "rand", "scrypt", "sea-orm", "sha2", "teloxide", - "thiserror", "tokio", ] @@ -1764,12 +1677,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "pkg-config" -version = "0.3.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" - [[package]] name = "polling" version = "2.8.0" @@ -1938,15 +1845,6 @@ dependencies = [ "bitflags", ] -[[package]] -name = "redox_syscall" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" -dependencies = [ - "bitflags", -] - [[package]] name = "regex" version = "1.8.1" @@ -2003,21 +1901,22 @@ dependencies = [ "http", "http-body", "hyper", - "hyper-tls", + "hyper-rustls", "ipnet", "js-sys", "log", "mime", "mime_guess", - "native-tls", "once_cell", "percent-encoding", "pin-project-lite", + "rustls", + "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", "tokio", - "tokio-native-tls", + "tokio-rustls", "tokio-util", "tower-service", "url", @@ -2025,6 +1924,7 @@ dependencies = [ "wasm-bindgen-futures", "wasm-streams", "web-sys", + "webpki-roots", "winreg", ] @@ -2117,9 +2017,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.37.14" +version = "0.37.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b864d3c18a5785a05953adeed93e2dca37ed30f18e69bba9f30079d51f363f" +checksum = "a0661814f891c57c930a610266415528da53c4933e6dea5fb350cbfe048a9ece" dependencies = [ "bitflags", "errno", @@ -2171,15 +2071,6 @@ dependencies = [ "cipher", ] -[[package]] -name = "schannel" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" -dependencies = [ - "windows-sys 0.42.0", -] - [[package]] name = "scopeguard" version = "1.1.0" @@ -2383,29 +2274,6 @@ version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" -[[package]] -name = "security-framework" -version = "2.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" -dependencies = [ - "bitflags", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "semver" version = "1.0.17" @@ -2771,6 +2639,7 @@ dependencies = [ "tokio-util", "url", "uuid", + "vecrem", ] [[package]] @@ -2785,19 +2654,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "tempfile" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" -dependencies = [ - "cfg-if", - "fastrand", - "redox_syscall 0.3.5", - "rustix", - "windows-sys 0.45.0", -] - [[package]] name = "termcolor" version = "1.2.0" @@ -2887,9 +2743,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.27.0" +version = "1.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0de47a4eecbe11f498978a9b29d792f0d2692d1dd003650c24c76510e3bc001" +checksum = "c3c786bf8134e5a3a166db9b29ab8f48134739014a3eca7bc6bfa95d673b136f" dependencies = [ "autocfg", "bytes", @@ -2900,30 +2756,20 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] name = "tokio-macros" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", "syn 2.0.15", ] -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - [[package]] name = "tokio-rustls" version = "0.23.4" @@ -2937,9 +2783,9 @@ dependencies = [ [[package]] name = "tokio-stream" -version = "0.1.12" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fb52b74f05dbf495a8fba459fdc331812b96aa086d9eb78101fa0d4569c3313" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" dependencies = [ "futures-core", "pin-project-lite", @@ -2948,9 +2794,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.7" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2" +checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" dependencies = [ "bytes", "futures-core", @@ -2977,11 +2823,10 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.37" +version = "0.1.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +checksum = "cf9cf6a813d3f40c88b0b6b6f29a5c95c6cdbf97c1f9cc53fb820200f5ad814d" dependencies = [ - "cfg-if", "log", "pin-project-lite", "tracing-attributes", @@ -2990,13 +2835,13 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.15", ] [[package]] @@ -3132,10 +2977,10 @@ dependencies = [ ] [[package]] -name = "vcpkg" -version = "0.2.15" +name = "vecrem" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +checksum = "c4808a28789238714a29163e4cb8031f0f050dd670f7a0cc74b6d80f3ce343fa" [[package]] name = "version_check" @@ -3313,21 +3158,6 @@ dependencies = [ "windows-targets 0.48.0", ] -[[package]] -name = "windows-sys" -version = "0.42.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - [[package]] name = "windows-sys" version = "0.45.0" diff --git a/Cargo.toml b/Cargo.toml index c61ba0e..cd06d38 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,9 +19,9 @@ log = "0.4.17" migration = { version = "0.1.0", path = "migration" } pbkdf2 = "0.12.1" pretty_env_logger = "0.4.0" -scrypt = "0.11.0" +rand = { version = "0.8.5", default-features = false, features = ["std_rng"] } +scrypt = { version = "0.11.0", default-features = false, features = ["std"] } sea-orm = { version = "0.11.2", features = ["sqlx-mysql", "runtime-tokio-rustls"] } sha2 = "0.10.6" -teloxide = { version = "0.12.2", features = ["macros"] } -thiserror = "1.0.40" -tokio = { version = "1.27.0", features = ["macros"] } +teloxide = { version = "0.12.2", features = ["macros", "ctrlc_handler", "rustls", "throttle"], default-features = false } +tokio = { version = "1.27.0", features = ["macros", "rt-multi-thread"] } diff --git a/src/entity/account.rs b/src/entity/account.rs index d06212e..382bb4f 100644 --- a/src/entity/account.rs +++ b/src/entity/account.rs @@ -1,6 +1,10 @@ //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 -use sea_orm::entity::prelude::*; +use chacha20poly1305::{aead::Aead, AeadCore, ChaCha20Poly1305, KeyInit}; +use pbkdf2::pbkdf2_hmac_array; +use rand::{rngs::OsRng, RngCore}; +use sea_orm::{prelude::*, ActiveValue::Set, QuerySelect}; +use sha2::Sha256; #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] #[sea_orm(table_name = "account")] @@ -21,3 +25,82 @@ pub struct Model { pub enum Relation {} impl ActiveModelBehavior for ActiveModel {} + +struct Cipher { + chacha: ChaCha20Poly1305, +} + +impl Cipher { + fn new(password: &[u8], salt: &[u8]) -> Self { + let key = pbkdf2_hmac_array::(password, salt, 480000); + + Self { + chacha: ChaCha20Poly1305::new(&key.into()), + } + } + + pub fn encrypt(&self, value: &[u8]) -> crate::Result> { + let nonce = ChaCha20Poly1305::generate_nonce(&mut OsRng); + let mut result = self.chacha.encrypt(&nonce, value).unwrap(); + result.extend(nonce); + Ok(result) + } + + fn decrypt(&self, value: &[u8]) -> crate::Result> { + let (data, nonce) = value.split_at(value.len() - 12); + self.chacha + .decrypt(nonce.into(), data) + .map_err(|err| err.into()) + } +} + +impl ActiveModel { + pub fn from_unencrypted( + user_id: u64, + name: String, + login: &str, + password: &str, + master_pass: &str, + ) -> crate::Result { + let mut salt = vec![0; 64]; + OsRng.fill_bytes(&mut salt); + let cipher = Cipher::new(master_pass.as_ref(), &salt); + let enc_login = Set(cipher.encrypt(login.as_ref())?); + let enc_password = Set(cipher.encrypt(password.as_ref())?); + Ok(Self { + name: Set(name), + user_id: Set(user_id), + salt: Set(salt), + enc_login, + enc_password, + }) + } +} + +impl Model { + pub fn decrypt(&self, master_pass: &str) -> crate::Result<(String, String)> { + let cipher = Cipher::new(master_pass.as_ref(), self.salt.as_ref()); + let login = String::from_utf8(cipher.decrypt(self.enc_login.as_ref())?)?; + let password = String::from_utf8(cipher.decrypt(self.enc_password.as_ref())?)?; + Ok((login, password)) + } +} + +#[derive(Copy, Clone, EnumIter, DeriveColumn, Debug)] +enum GetNamesQuery { + AccountName, +} + +impl Entity { + /// Gets a list of account names of a user + pub async fn get_names(user_id: u64, db: &DatabaseConnection) -> crate::Result> { + Self::find() + .select_only() + .column_as(Column::Name, GetNamesQuery::AccountName) + .filter(Column::UserId.eq(user_id)) + .into_values::<_, GetNamesQuery>() + .all(db) + .await + .map_err(|err| err.into()) + } +} diff --git a/src/entity/master_pass.rs b/src/entity/master_pass.rs index 87928ce..23f0154 100644 --- a/src/entity/master_pass.rs +++ b/src/entity/master_pass.rs @@ -1,6 +1,8 @@ //! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.2 -use sea_orm::entity::prelude::*; +use rand::{rngs::OsRng, RngCore}; +use scrypt::{scrypt, Params}; +use sea_orm::{entity::prelude::*, ActiveValue::Set}; #[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] #[sea_orm(table_name = "master_pass")] @@ -17,3 +19,23 @@ pub struct Model { pub enum Relation {} impl ActiveModelBehavior for ActiveModel {} + +impl ActiveModel { + pub fn from_unencrypted(user_id: u64, password: &str) -> crate::Result { + let mut salt = vec![0; 64]; + OsRng.fill_bytes(&mut salt); + let params = Params::new( + Params::RECOMMENDED_LOG_N, + Params::RECOMMENDED_R, + Params::RECOMMENDED_P, + 128, + )?; + let mut password_hash = vec![0; 128]; + scrypt(password.as_ref(), &salt, ¶ms, &mut password_hash)?; + Ok(Self { + user_id: Set(user_id), + salt: Set(salt), + password_hash: Set(password_hash), + }) + } +} diff --git a/src/handlers/add_account.rs b/src/handlers/add_account.rs new file mode 100644 index 0000000..a028d22 --- /dev/null +++ b/src/handlers/add_account.rs @@ -0,0 +1,18 @@ +use sea_orm::prelude::*; +use teloxide::{adaptors::Throttle, prelude::*}; + +use crate::entity::account; + +pub async fn add_account( + bot: Throttle, + msg: Message, + db: DatabaseConnection, + (name, login, password, master_pass): (String, String, String, String), +) -> crate::Result<()> { + let user_id = msg.from().unwrap().id.0; + let account = + account::ActiveModel::from_unencrypted(user_id, name, &login, &password, &master_pass)?; + account.insert(&db).await?; + bot.send_message(msg.chat.id, "Success").await?; + Ok(()) +} diff --git a/src/handlers/default.rs b/src/handlers/default.rs new file mode 100644 index 0000000..47721e0 --- /dev/null +++ b/src/handlers/default.rs @@ -0,0 +1,6 @@ +use teloxide::{adaptors::Throttle, prelude::*}; + +pub async fn default(bot: Throttle, msg: Message) -> crate::Result<()> { + bot.send_message(msg.chat.id, "Unknown command").await?; + Ok(()) +} diff --git a/src/handlers/get_account.rs b/src/handlers/get_account.rs new file mode 100644 index 0000000..00aba84 --- /dev/null +++ b/src/handlers/get_account.rs @@ -0,0 +1,26 @@ +use crate::entity::{account, prelude::Account}; +use sea_orm::prelude::*; +use teloxide::{adaptors::Throttle, prelude::*, types::ParseMode}; + +pub async fn get_account( + bot: Throttle, + msg: Message, + db: DatabaseConnection, + (name, master_pass): (String, String), +) -> crate::Result<()> { + let account = Account::find() + .filter( + account::Column::UserId + .eq(msg.from().unwrap().id.0) + .add(account::Column::Name.eq(&name)), + ) + .one(&db) + .await? + .unwrap(); + let (login, password) = account.decrypt(&master_pass)?; + let message = format!("Name:\n`{name}`\nLogin:\n`{login}`\nPassword:\n`{password}`"); + bot.send_message(msg.chat.id, message) + .parse_mode(ParseMode::MarkdownV2) + .await?; + Ok(()) +} diff --git a/src/handlers/get_accounts.rs b/src/handlers/get_accounts.rs new file mode 100644 index 0000000..b79ab54 --- /dev/null +++ b/src/handlers/get_accounts.rs @@ -0,0 +1,34 @@ +use crate::entity::prelude::Account; +use sea_orm::prelude::*; +use teloxide::{adaptors::Throttle, prelude::*, types::ParseMode}; + +#[derive(Clone, Copy, EnumIter, DeriveColumn, Debug)] +enum Query { + AccountName, +} + +pub async fn get_accounts( + bot: Throttle, + msg: Message, + db: DatabaseConnection, +) -> crate::Result<()> { + let user_id = msg.from().unwrap().id.0; + let mut account_names = Account::get_names(user_id, &db).await?.into_iter(); + let mut result = match account_names.next() { + Some(name) => format!("Accounts:\n`{name}`"), + None => { + bot.send_message(msg.chat.id, "No accounts found").await?; + return Ok(()); + } + }; + for name in account_names { + result.reserve(name.len() + 3); + result.push_str("\n`"); + result.push_str(&name); + result.push('\'') + } + bot.send_message(msg.chat.id, result) + .parse_mode(ParseMode::MarkdownV2) + .await?; + Ok(()) +} diff --git a/src/handlers/help.rs b/src/handlers/help.rs new file mode 100644 index 0000000..1c765a0 --- /dev/null +++ b/src/handlers/help.rs @@ -0,0 +1,7 @@ +use teloxide::{adaptors::Throttle, prelude::*, utils::command::BotCommands}; + +pub async fn help(bot: Throttle, msg: Message) -> crate::Result<()> { + bot.send_message(msg.chat.id, super::Command::descriptions().to_string()) + .await?; + Ok(()) +} diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs new file mode 100644 index 0000000..8eb4eff --- /dev/null +++ b/src/handlers/mod.rs @@ -0,0 +1,56 @@ +use sea_orm::prelude::*; +use teloxide::{ + adaptors::{throttle::Limits, Throttle}, + filter_command, + prelude::*, + utils::command::BotCommands, +}; + +mod add_account; +mod default; +mod get_account; +mod get_accounts; +mod help; + +#[derive(BotCommands, Clone)] +#[command(rename_rule = "snake_case")] +enum Command { + #[command()] + Help, + #[command(parse_with = "split")] + AddAccount(String, String, String, String), + #[command(parse_with = "split")] + GetAccount(String, String), + #[command()] + GetAccounts, +} + +pub fn get_dispatcher( + token: String, + db: DatabaseConnection, +) -> Dispatcher, crate::Error, teloxide::dispatching::DefaultKey> { + let bot = Bot::new(token).throttle(Limits::default()); + Dispatcher::builder( + bot, + Update::filter_message() + .branch( + filter_command::() + .branch(dptree::case![Command::Help].endpoint(help::help)) + .branch( + dptree::case![Command::AddAccount(name, login, password, master_pass)] + .endpoint(add_account::add_account), + ) + .branch( + dptree::case![Command::GetAccount(name, master_pass)] + .endpoint(get_account::get_account), + ) + .branch( + dptree::case![Command::GetAccounts].endpoint(get_accounts::get_accounts), + ), + ) + .branch(dptree::endpoint(default::default)), + ) + .dependencies(dptree::deps![db]) + .enable_ctrlc_handler() + .build() +} diff --git a/src/main.rs b/src/main.rs index 543ecc3..24de3db 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,126 +1,22 @@ mod entity; +mod handlers; -use entity::{account, prelude::Account}; - -use anyhow::Result; -use chacha20poly1305::{ - aead::{rand_core::RngCore, Aead, AeadCore, OsRng}, - ChaCha20Poly1305, KeyInit, -}; +use anyhow::{Error, Result}; use dotenv::dotenv; -use pbkdf2::pbkdf2_hmac_array; -use sea_orm::{ - ActiveModelTrait, ActiveValue::Set, ColumnTrait, ConnectOptions, Database, EntityTrait, - QueryFilter, -}; -use sha2::Sha256; +use handlers::get_dispatcher; +use migration::{Migrator, MigratorTrait}; +use sea_orm::Database; use std::env; -use teloxide::{prelude::*, utils::command::BotCommands}; - -#[derive(BotCommands, Clone)] -#[command(rename_rule = "lowercase")] -enum Command { - #[command()] - Help, - #[command(parse_with = "split")] - AddAccount(String, String, String, String), - #[command(parse_with = "split")] - GetAccount(String, String), -} - -use Command::*; - -struct Cipher { - chacha: ChaCha20Poly1305, -} - -impl Cipher { - fn new(password: &[u8], salt: &[u8]) -> Self { - let key = pbkdf2_hmac_array::(password, salt, 480000); - - Self { - chacha: ChaCha20Poly1305::new(&key.into()), - } - } - - fn encrypt(&self, value: &[u8]) -> Result> { - let nonce = ChaCha20Poly1305::generate_nonce(&mut OsRng); - let mut result = self.chacha.encrypt(&nonce, value).unwrap(); - result.extend(nonce); - Ok(result) - } - - fn decrypt(&self, value: &[u8]) -> Result> { - let (data, nonce) = value.split_at(value.len() - 12); - assert!(nonce.len() == 12); - self.chacha - .decrypt(nonce.into(), data) - .map_err(|err| err.into()) - } -} - -async fn answer_command(db: ConnectOptions, bot: Bot, msg: Message, cmd: Command) -> Result<()> { - let user_id = msg.from().unwrap().id.0; - let db = Database::connect(db).await?; - match cmd { - Help => { - bot.send_message(msg.chat.id, Command::descriptions().to_string()) - .await?; - } - AddAccount(name, login, password, master_pass) => { - let mut salt = vec![0; 64]; - OsRng.fill_bytes(&mut salt); - let cipher = Cipher::new(master_pass.as_ref(), &salt); - let enc_login = Set(cipher.encrypt(login.as_ref())?); - let enc_password = Set(cipher.encrypt(password.as_ref())?); - let account = account::ActiveModel { - name: Set(name), - user_id: Set(user_id), - salt: Set(salt), - enc_login, - enc_password, - }; - account.insert(&db).await?; - bot.send_message(msg.chat.id, "Success").await?; - } - GetAccount(name, master_pass) => { - let account = Account::find() - .filter( - account::Column::UserId - .eq(user_id) - .add(account::Column::Name.eq(&name)), - ) - .one(&db) - .await? - .unwrap(); - let cipher = Cipher::new(master_pass.as_ref(), &account.salt); - let login = String::from_utf8(cipher.decrypt(&account.enc_login)?)?; - let password = String::from_utf8(cipher.decrypt(&account.enc_password)?)?; - let message = format!("Account `{name}`\nLogin: `{login}`\nPassword: `{password}`"); - bot.send_message(msg.chat.id, message).await?; - } - } - Ok(()) -} #[tokio::main] -async fn main() { +async fn main() -> Result<()> { let _ = dotenv(); pretty_env_logger::init(); let token = env::var("TOKEN").unwrap(); let database_url = env::var("DATABASE_URL").unwrap(); - let bot = Bot::new(token); - let db = ConnectOptions::new(database_url); - Dispatcher::builder( - bot, - Update::filter_message() - .filter_command::() - .endpoint(answer_command), - ) - .dependencies(dptree::deps![db]) - .enable_ctrlc_handler() - .build() - .dispatch() - .await; + let db = Database::connect(database_url).await?; + Migrator::up(&db, None).await?; + get_dispatcher(token, db).dispatch().await; + Ok(()) }