Switched from sea-orm to sqlx
This commit is contained in:
parent
e4e33f52b1
commit
9f967e82d5
12
.sqlx/query-06b0592a51c20f754e0c4d9ac2e6c266b47fb9ca86ff37657fe17538b3fb21c6.json
generated
Normal file
12
.sqlx/query-06b0592a51c20f754e0c4d9ac2e6c266b47fb9ca86ff37657fe17538b3fb21c6.json
generated
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"db_name": "MySQL",
|
||||||
|
"query": "INSERT INTO account VALUES (?, ?, ?, ?, ?)",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 5
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "06b0592a51c20f754e0c4d9ac2e6c266b47fb9ca86ff37657fe17538b3fb21c6"
|
||||||
|
}
|
12
.sqlx/query-1fc824ca1ca447a990c3e68ee4f2a15b8e5bc260641057d914bb7ff871b51aa3.json
generated
Normal file
12
.sqlx/query-1fc824ca1ca447a990c3e68ee4f2a15b8e5bc260641057d914bb7ff871b51aa3.json
generated
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"db_name": "MySQL",
|
||||||
|
"query": "INSERT INTO master_pass VALUES (?, ?, ?)",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 3
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "1fc824ca1ca447a990c3e68ee4f2a15b8e5bc260641057d914bb7ff871b51aa3"
|
||||||
|
}
|
12
.sqlx/query-20f824c521c2261a9e6c85a8972d83ccdffc2fa110ad152356d6392eb7d84326.json
generated
Normal file
12
.sqlx/query-20f824c521c2261a9e6c85a8972d83ccdffc2fa110ad152356d6392eb7d84326.json
generated
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"db_name": "MySQL",
|
||||||
|
"query": "UPDATE account SET enc_password = ? WHERE user_id = ? AND name = ?",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 3
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "20f824c521c2261a9e6c85a8972d83ccdffc2fa110ad152356d6392eb7d84326"
|
||||||
|
}
|
12
.sqlx/query-5d0690b7fff7d8b3d8d76631360a2b749d401d5722c1a08186da379cdea35cb9.json
generated
Normal file
12
.sqlx/query-5d0690b7fff7d8b3d8d76631360a2b749d401d5722c1a08186da379cdea35cb9.json
generated
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"db_name": "MySQL",
|
||||||
|
"query": "UPDATE account SET enc_login = ? WHERE user_id = ? AND name = ?",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 3
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "5d0690b7fff7d8b3d8d76631360a2b749d401d5722c1a08186da379cdea35cb9"
|
||||||
|
}
|
12
.sqlx/query-65cf7d5bfa7f322b3294755fcf3678807d31104ef8431f1842cfa05494af1bfd.json
generated
Normal file
12
.sqlx/query-65cf7d5bfa7f322b3294755fcf3678807d31104ef8431f1842cfa05494af1bfd.json
generated
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"db_name": "MySQL",
|
||||||
|
"query": "DELETE FROM account WHERE user_id = ?",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 1
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "65cf7d5bfa7f322b3294755fcf3678807d31104ef8431f1842cfa05494af1bfd"
|
||||||
|
}
|
12
.sqlx/query-d24ba49dfa1ba9dc0c3534fe543c8feb3557f8e2bfee2fac0dc37ede2562acfd.json
generated
Normal file
12
.sqlx/query-d24ba49dfa1ba9dc0c3534fe543c8feb3557f8e2bfee2fac0dc37ede2562acfd.json
generated
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"db_name": "MySQL",
|
||||||
|
"query": "DELETE FROM account WHERE user_id = ? AND name = ?",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 2
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "d24ba49dfa1ba9dc0c3534fe543c8feb3557f8e2bfee2fac0dc37ede2562acfd"
|
||||||
|
}
|
12
.sqlx/query-d2b33865cf969bb28341212d7dcac28a2f5832b90cfd7ed6a061e4fe203205a7.json
generated
Normal file
12
.sqlx/query-d2b33865cf969bb28341212d7dcac28a2f5832b90cfd7ed6a061e4fe203205a7.json
generated
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"db_name": "MySQL",
|
||||||
|
"query": "DELETE FROM master_pass WHERE user_id = ?",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 1
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "d2b33865cf969bb28341212d7dcac28a2f5832b90cfd7ed6a061e4fe203205a7"
|
||||||
|
}
|
12
.sqlx/query-f9f6cbb6958f2d35d4ba57e9db9b9d7fc9d67c8bbf9d60fd7e55bebd7188cd84.json
generated
Normal file
12
.sqlx/query-f9f6cbb6958f2d35d4ba57e9db9b9d7fc9d67c8bbf9d60fd7e55bebd7188cd84.json
generated
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"db_name": "MySQL",
|
||||||
|
"query": "UPDATE account SET name = ? WHERE user_id = ? AND name = ?",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 3
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "f9f6cbb6958f2d35d4ba57e9db9b9d7fc9d67c8bbf9d60fd7e55bebd7188cd84"
|
||||||
|
}
|
837
Cargo.lock
generated
837
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -9,7 +9,7 @@ edition = "2021"
|
|||||||
strip = true
|
strip = true
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [".", "migration", "entity", "cryptography"]
|
members = [".", "entity", "cryptography"]
|
||||||
|
|
||||||
[workspace.lints.clippy]
|
[workspace.lints.clippy]
|
||||||
pedantic = "warn"
|
pedantic = "warn"
|
||||||
@ -31,16 +31,12 @@ futures = "0.3"
|
|||||||
hex = "0.4"
|
hex = "0.4"
|
||||||
itertools = "0.12"
|
itertools = "0.12"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
migration = { version = "0.2", path = "migration" }
|
|
||||||
parking_lot = "0.12"
|
parking_lot = "0.12"
|
||||||
pretty_env_logger = "0.5"
|
pretty_env_logger = "0.5"
|
||||||
sea-orm = { version = "0.12", features = [
|
|
||||||
"sqlx-mysql",
|
|
||||||
"runtime-tokio-rustls",
|
|
||||||
] }
|
|
||||||
serde = "1"
|
serde = "1"
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
sha2 = "0.10"
|
sha2 = "0.10"
|
||||||
|
sqlx = { version = "0.7", features = ["mysql", "runtime-tokio-rustls", "macros", "migrate"], default-features = false }
|
||||||
teloxide = { version = "0.12", features = [
|
teloxide = { version = "0.12", features = [
|
||||||
"macros",
|
"macros",
|
||||||
"ctrlc_handler",
|
"ctrlc_handler",
|
||||||
|
5
build.rs
Normal file
5
build.rs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
// generated by `sqlx migrate build-script`
|
||||||
|
fn main() {
|
||||||
|
// trigger recompilation when a new migration is added
|
||||||
|
println!("cargo:rerun-if-changed=migrations");
|
||||||
|
}
|
@ -19,7 +19,6 @@ rand = { version = "0.8", default-features = false, features = [
|
|||||||
"std_rng",
|
"std_rng",
|
||||||
"std",
|
"std",
|
||||||
] }
|
] }
|
||||||
sea-orm = "0.12"
|
|
||||||
bitflags = "2"
|
bitflags = "2"
|
||||||
arrayvec = "0.7"
|
arrayvec = "0.7"
|
||||||
subtle = "2"
|
subtle = "2"
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
use chacha20poly1305::{AeadCore, AeadInPlace, ChaCha20Poly1305, KeyInit};
|
use chacha20poly1305::{AeadCore, AeadInPlace, ChaCha20Poly1305, KeyInit};
|
||||||
use entity::account::{self, ActiveModel};
|
use entity::account::Account;
|
||||||
use pbkdf2::pbkdf2_hmac_array;
|
use pbkdf2::pbkdf2_hmac_array;
|
||||||
use rand::{rngs::OsRng, RngCore};
|
use rand::{rngs::OsRng, RngCore};
|
||||||
use sea_orm::ActiveValue::Set;
|
|
||||||
use sha2::Sha256;
|
use sha2::Sha256;
|
||||||
|
|
||||||
pub struct Cipher {
|
pub struct Cipher {
|
||||||
@ -62,7 +61,7 @@ impl Decrypted {
|
|||||||
///
|
///
|
||||||
/// Returns an error if the tag doesn't match the ciphertext or if the decrypted data isn't valid UTF-8
|
/// Returns an error if the tag doesn't match the ciphertext or if the decrypted data isn't valid UTF-8
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn from_account(mut account: account::Model, master_pass: &str) -> crate::Result<Self> {
|
pub fn from_account(mut account: Account, master_pass: &str) -> crate::Result<Self> {
|
||||||
let cipher = Cipher::new(master_pass.as_bytes(), &account.salt);
|
let cipher = Cipher::new(master_pass.as_bytes(), &account.salt);
|
||||||
cipher.decrypt(&mut account.enc_login)?;
|
cipher.decrypt(&mut account.enc_login)?;
|
||||||
cipher.decrypt(&mut account.enc_password)?;
|
cipher.decrypt(&mut account.enc_password)?;
|
||||||
@ -77,22 +76,22 @@ impl Decrypted {
|
|||||||
/// Constructs `ActiveModel` with eath field Set by encrypting `self`
|
/// Constructs `ActiveModel` with eath field Set by encrypting `self`
|
||||||
#[inline]
|
#[inline]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn into_account(self, user_id: u64, master_pass: &str) -> account::ActiveModel {
|
pub fn into_account(self, user_id: u64, master_pass: &str) -> Account {
|
||||||
let mut login = self.login.into_bytes();
|
let mut enc_login = self.login.into_bytes();
|
||||||
let mut password = self.password.into_bytes();
|
let mut enc_password = self.password.into_bytes();
|
||||||
let mut salt = vec![0; 64];
|
let mut salt = vec![0; 64];
|
||||||
OsRng.fill_bytes(&mut salt);
|
OsRng.fill_bytes(&mut salt);
|
||||||
|
|
||||||
let cipher = Cipher::new(master_pass.as_bytes(), &salt);
|
let cipher = Cipher::new(master_pass.as_bytes(), &salt);
|
||||||
cipher.encrypt(&mut login);
|
cipher.encrypt(&mut enc_login);
|
||||||
cipher.encrypt(&mut password);
|
cipher.encrypt(&mut enc_password);
|
||||||
|
|
||||||
ActiveModel {
|
Account {
|
||||||
user_id: Set(user_id),
|
user_id,
|
||||||
name: Set(self.name),
|
name: self.name,
|
||||||
salt: Set(salt),
|
salt,
|
||||||
enc_login: Set(login),
|
enc_login,
|
||||||
enc_password: Set(password),
|
enc_password,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use entity::master_pass;
|
use entity::master_pass::MasterPass;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use rand::{rngs::OsRng, RngCore};
|
use rand::{rngs::OsRng, RngCore};
|
||||||
use scrypt::{scrypt, Params};
|
use scrypt::{scrypt, Params};
|
||||||
@ -55,9 +55,9 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a master_pass::Model> for HashedBytes<&'a [u8], &'a [u8]> {
|
impl<'a> From<&'a MasterPass> for HashedBytes<&'a [u8], &'a [u8]> {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn from(value: &'a master_pass::Model) -> Self {
|
fn from(value: &'a MasterPass) -> Self {
|
||||||
HashedBytes {
|
HashedBytes {
|
||||||
hash: &value.password_hash,
|
hash: &value.password_hash,
|
||||||
salt: &value.salt,
|
salt: &value.salt,
|
||||||
@ -65,8 +65,8 @@ impl<'a> From<&'a master_pass::Model> for HashedBytes<&'a [u8], &'a [u8]> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<master_pass::Model> for HashedBytes<Vec<u8>, Vec<u8>> {
|
impl From<MasterPass> for HashedBytes<Vec<u8>, Vec<u8>> {
|
||||||
fn from(value: master_pass::Model) -> Self {
|
fn from(value: MasterPass) -> Self {
|
||||||
Self {
|
Self {
|
||||||
hash: value.password_hash,
|
hash: value.password_hash,
|
||||||
salt: value.salt,
|
salt: value.salt,
|
||||||
|
@ -1,20 +1,19 @@
|
|||||||
use super::hashing::HashedBytes;
|
use super::hashing::HashedBytes;
|
||||||
use entity::master_pass;
|
use entity::master_pass;
|
||||||
use sea_orm::ActiveValue::Set;
|
|
||||||
|
|
||||||
pub trait FromUnencryptedExt {
|
pub trait FromUnencryptedExt {
|
||||||
fn from_unencrypted(user_id: u64, password: &str) -> master_pass::ActiveModel;
|
fn from_unencrypted(user_id: u64, password: &str) -> master_pass::MasterPass;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FromUnencryptedExt for master_pass::ActiveModel {
|
impl FromUnencryptedExt for master_pass::MasterPass {
|
||||||
/// Hashes the password and creates an `ActiveModel` with all fields set to Set variant
|
/// Hashes the password and creates an `ActiveModel` with all fields set to Set variant
|
||||||
#[inline]
|
#[inline]
|
||||||
fn from_unencrypted(user_id: u64, password: &str) -> Self {
|
fn from_unencrypted(user_id: u64, password: &str) -> Self {
|
||||||
let hash = HashedBytes::new(password.as_bytes());
|
let hash = HashedBytes::new(password.as_bytes());
|
||||||
Self {
|
Self {
|
||||||
user_id: Set(user_id),
|
user_id,
|
||||||
password_hash: Set(hash.hash.to_vec()),
|
password_hash: hash.hash.to_vec(),
|
||||||
salt: Set(hash.salt.to_vec()),
|
salt: hash.salt.to_vec(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,4 +10,4 @@ workspace = true
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
sea-orm = "0.12"
|
sqlx = "0.7.2"
|
||||||
|
@ -1,90 +1,96 @@
|
|||||||
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
|
use super::Pool;
|
||||||
|
use futures::{Stream, TryStreamExt};
|
||||||
|
use sqlx::{query, query_as, Executor, FromRow, MySql};
|
||||||
|
|
||||||
use futures::Stream;
|
#[derive(Clone, Debug, PartialEq, Eq, FromRow, Default)]
|
||||||
use sea_orm::{entity::prelude::*, ActiveValue::Set, QueryOrder, QuerySelect, Statement};
|
pub struct Account {
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
|
||||||
#[sea_orm(table_name = "account")]
|
|
||||||
pub struct Model {
|
|
||||||
#[sea_orm(primary_key, auto_increment = false)]
|
|
||||||
pub user_id: u64,
|
pub user_id: u64,
|
||||||
#[sea_orm(primary_key, auto_increment = false)]
|
|
||||||
pub name: String,
|
pub name: String,
|
||||||
#[sea_orm(column_type = "Binary(BlobSize::Blob(Some(64)))")]
|
|
||||||
pub salt: Vec<u8>,
|
pub salt: Vec<u8>,
|
||||||
#[sea_orm(column_type = "VarBinary(256)")]
|
|
||||||
pub enc_login: Vec<u8>,
|
pub enc_login: Vec<u8>,
|
||||||
#[sea_orm(column_type = "VarBinary(256)")]
|
|
||||||
pub enc_password: Vec<u8>,
|
pub enc_password: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
impl Account {
|
||||||
pub enum Relation {}
|
// Inserts the account into DB
|
||||||
|
#[inline]
|
||||||
|
pub async fn insert(&self, pool: &Pool) -> crate::Result<()> {
|
||||||
|
query!(
|
||||||
|
"INSERT INTO account VALUES (?, ?, ?, ?, ?)",
|
||||||
|
self.user_id,
|
||||||
|
self.name,
|
||||||
|
self.salt,
|
||||||
|
self.enc_login,
|
||||||
|
self.enc_password
|
||||||
|
)
|
||||||
|
.execute(pool)
|
||||||
|
.await
|
||||||
|
.map(|_| ())
|
||||||
|
}
|
||||||
|
|
||||||
impl ActiveModelBehavior for ActiveModel {}
|
|
||||||
|
|
||||||
impl Entity {
|
|
||||||
/// Gets all user's account from DB
|
/// Gets all user's account from DB
|
||||||
#[inline]
|
#[inline]
|
||||||
pub async fn get_all(
|
pub fn get_all(user_id: u64, pool: &Pool) -> impl Stream<Item = crate::Result<Self>> + '_ {
|
||||||
user_id: u64,
|
query_as("SELECT * FROM account WHERE user_id = ?")
|
||||||
db: &DatabaseConnection,
|
.bind(user_id)
|
||||||
) -> crate::Result<impl Stream<Item = crate::Result<Model>> + '_> {
|
.fetch(pool)
|
||||||
Self::find()
|
|
||||||
.filter(Column::UserId.eq(user_id))
|
|
||||||
.stream(db)
|
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Streams the names of the user accounts
|
/// Streams the names of the user accounts
|
||||||
#[inline]
|
#[inline]
|
||||||
pub async fn get_names(
|
pub fn get_names(user_id: u64, pool: &Pool) -> impl Stream<Item = crate::Result<String>> + '_ {
|
||||||
user_id: u64,
|
query_as::<_, (String,)>("SELECT name FROM account WHERE user_id = ?")
|
||||||
db: &DatabaseConnection,
|
.bind(user_id)
|
||||||
) -> crate::Result<impl Stream<Item = crate::Result<String>> + '_> {
|
.fetch(pool)
|
||||||
Self::find()
|
.map_ok(|(name,)| name)
|
||||||
.select_only()
|
|
||||||
.column(Column::Name)
|
|
||||||
.filter(Column::UserId.eq(user_id))
|
|
||||||
.order_by_asc(Column::Name)
|
|
||||||
.into_tuple()
|
|
||||||
.stream(db)
|
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if the account exists
|
/// Checks if the account exists
|
||||||
#[inline]
|
#[inline]
|
||||||
pub async fn exists(
|
pub async fn exists(user_id: u64, account_name: &str, pool: &Pool) -> crate::Result<bool> {
|
||||||
user_id: u64,
|
query_as::<_, (bool,)>(
|
||||||
account_name: impl Into<String> + Send,
|
"SELECT EXISTS(SELECT * FROM account WHERE user_id = ? AND name = ? LIMIT 1) as value",
|
||||||
db: &DatabaseConnection,
|
)
|
||||||
) -> crate::Result<bool> {
|
.bind(user_id)
|
||||||
let count = Self::find_by_id((user_id, account_name.into()))
|
.bind(account_name)
|
||||||
.count(db)
|
.fetch_one(pool)
|
||||||
.await?;
|
.await
|
||||||
Ok(count != 0)
|
.map(|(exists,)| exists)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the account from the DB
|
/// Gets the account from the DB
|
||||||
#[inline]
|
#[inline]
|
||||||
pub async fn get(
|
pub async fn get(user_id: u64, account_name: &str, pool: &Pool) -> crate::Result<Option<Self>> {
|
||||||
user_id: u64,
|
query_as("SELECT * FROM account WHERE user_id = ? AND name = ?")
|
||||||
account_name: impl Into<String> + Send,
|
.bind(user_id)
|
||||||
db: &DatabaseConnection,
|
.bind(account_name)
|
||||||
) -> crate::Result<Option<Model>> {
|
.fetch_optional(pool)
|
||||||
Self::find_by_id((user_id, account_name.into()))
|
|
||||||
.one(db)
|
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deletes the account from DB
|
||||||
|
#[inline]
|
||||||
|
pub async fn delete(user_id: u64, name: &str, pool: &Pool) -> crate::Result<()> {
|
||||||
|
query!(
|
||||||
|
"DELETE FROM account WHERE user_id = ? AND name = ?",
|
||||||
|
user_id,
|
||||||
|
name
|
||||||
|
)
|
||||||
|
.execute(pool)
|
||||||
|
.await
|
||||||
|
.map(|_| ())
|
||||||
|
}
|
||||||
|
|
||||||
/// Deletes all the user's accounts from DB
|
/// Deletes all the user's accounts from DB
|
||||||
#[inline]
|
#[inline]
|
||||||
pub async fn delete_all(user_id: u64, db: &impl ConnectionTrait) -> crate::Result<()> {
|
pub async fn delete_all(
|
||||||
Self::delete_many()
|
user_id: u64,
|
||||||
.filter(Column::UserId.eq(user_id))
|
pool: impl Executor<'_, Database = MySql>,
|
||||||
.exec(db)
|
) -> crate::Result<()> {
|
||||||
.await?;
|
query!("DELETE FROM account WHERE user_id = ?", user_id)
|
||||||
Ok(())
|
.execute(pool)
|
||||||
|
.await
|
||||||
|
.map(|_| ())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets a name by a hex of a SHA256 hash of the name
|
/// Gets a name by a hex of a SHA256 hash of the name
|
||||||
@ -92,49 +98,82 @@ impl Entity {
|
|||||||
pub async fn get_name_by_hash(
|
pub async fn get_name_by_hash(
|
||||||
user_id: u64,
|
user_id: u64,
|
||||||
hash: String,
|
hash: String,
|
||||||
db: &DatabaseConnection,
|
pool: &Pool,
|
||||||
) -> crate::Result<Option<String>> {
|
) -> crate::Result<Option<String>> {
|
||||||
db.query_one(Statement::from_sql_and_values(
|
let name = query_as::<_, (String,)>(
|
||||||
sea_orm::DatabaseBackend::MySql,
|
|
||||||
"SELECT `name` FROM `account` WHERE SHA2(`name`, 256) = ? AND `user_id` = ?;",
|
"SELECT `name` FROM `account` WHERE SHA2(`name`, 256) = ? AND `user_id` = ?;",
|
||||||
[hash.into(), user_id.into()],
|
)
|
||||||
))
|
.bind(hash)
|
||||||
.await?
|
.bind(user_id)
|
||||||
.map(|result| result.try_get_by_index(0))
|
.fetch_optional(pool)
|
||||||
.transpose()
|
.await?;
|
||||||
|
|
||||||
|
Ok(name.map(|(name,)| name))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub async fn get_salt(
|
pub async fn get_salt(user_id: u64, name: &str, pool: &Pool) -> crate::Result<Option<Vec<u8>>> {
|
||||||
user_id: u64,
|
let salt =
|
||||||
name: String,
|
query_as::<_, (Vec<u8>,)>("SELECT salt FROM account WHERE user_id = ? AND name = ?")
|
||||||
db: &DatabaseConnection,
|
.bind(user_id)
|
||||||
) -> crate::Result<Option<Vec<u8>>> {
|
.bind(name)
|
||||||
Self::find_by_id((user_id, name))
|
.fetch_optional(pool)
|
||||||
.select_only()
|
.await?;
|
||||||
.column(Column::Salt)
|
|
||||||
.into_tuple()
|
Ok(salt.map(|(salt,)| salt))
|
||||||
.one(db)
|
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub async fn update_name(
|
pub async fn update_name(
|
||||||
user_id: u64,
|
user_id: u64,
|
||||||
original_name: String,
|
original_name: &str,
|
||||||
new_name: String,
|
new_name: &str,
|
||||||
db: &DatabaseConnection,
|
pool: &Pool,
|
||||||
) -> crate::Result<()> {
|
) -> crate::Result<()> {
|
||||||
Self::update_many()
|
query!(
|
||||||
.set(ActiveModel {
|
"UPDATE account SET name = ? WHERE user_id = ? AND name = ?",
|
||||||
name: Set(new_name),
|
new_name,
|
||||||
..Default::default()
|
user_id,
|
||||||
})
|
original_name
|
||||||
.filter(Column::UserId.eq(user_id))
|
)
|
||||||
.filter(Column::Name.eq(original_name))
|
.execute(pool)
|
||||||
.exec(db)
|
.await
|
||||||
.await?;
|
.map(|_| ())
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
#[inline]
|
||||||
|
pub async fn update_login(
|
||||||
|
user_id: u64,
|
||||||
|
name: &str,
|
||||||
|
login: Vec<u8>,
|
||||||
|
pool: &Pool,
|
||||||
|
) -> crate::Result<()> {
|
||||||
|
query!(
|
||||||
|
"UPDATE account SET enc_login = ? WHERE user_id = ? AND name = ?",
|
||||||
|
login,
|
||||||
|
user_id,
|
||||||
|
name
|
||||||
|
)
|
||||||
|
.execute(pool)
|
||||||
|
.await
|
||||||
|
.map(|_| ())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub async fn update_password(
|
||||||
|
user_id: u64,
|
||||||
|
name: &str,
|
||||||
|
password: Vec<u8>,
|
||||||
|
pool: &Pool,
|
||||||
|
) -> crate::Result<()> {
|
||||||
|
query!(
|
||||||
|
"UPDATE account SET enc_password = ? WHERE user_id = ? AND name = ?",
|
||||||
|
password,
|
||||||
|
user_id,
|
||||||
|
name
|
||||||
|
)
|
||||||
|
.execute(pool)
|
||||||
|
.await
|
||||||
|
.map(|_| ())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,6 @@ pub mod account;
|
|||||||
pub mod master_pass;
|
pub mod master_pass;
|
||||||
pub mod prelude;
|
pub mod prelude;
|
||||||
|
|
||||||
use sea_orm::DbErr;
|
pub use sqlx::Result;
|
||||||
|
|
||||||
type Result<T> = std::result::Result<T, DbErr>;
|
pub type Pool = sqlx::mysql::MySqlPool;
|
||||||
|
@ -1,40 +1,57 @@
|
|||||||
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
|
use super::Pool;
|
||||||
|
use sqlx::{prelude::FromRow, query, query_as, Executor, MySql};
|
||||||
|
|
||||||
use sea_orm::entity::prelude::*;
|
#[derive(Clone, Debug, PartialEq, FromRow, Eq)]
|
||||||
|
pub struct MasterPass {
|
||||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
|
|
||||||
#[sea_orm(table_name = "master_pass")]
|
|
||||||
pub struct Model {
|
|
||||||
#[sea_orm(primary_key, auto_increment = false)]
|
|
||||||
pub user_id: u64,
|
pub user_id: u64,
|
||||||
#[sea_orm(column_type = "Binary(BlobSize::Blob(Some(64)))")]
|
|
||||||
pub salt: Vec<u8>,
|
pub salt: Vec<u8>,
|
||||||
#[sea_orm(column_type = "Binary(BlobSize::Blob(Some(64)))")]
|
|
||||||
pub password_hash: Vec<u8>,
|
pub password_hash: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
impl MasterPass {
|
||||||
pub enum Relation {}
|
// Inserts the master password into DB
|
||||||
|
#[inline]
|
||||||
|
pub async fn insert(&self, pool: &Pool) -> crate::Result<()> {
|
||||||
|
query!(
|
||||||
|
"INSERT INTO master_pass VALUES (?, ?, ?)",
|
||||||
|
self.user_id,
|
||||||
|
self.salt,
|
||||||
|
self.password_hash
|
||||||
|
)
|
||||||
|
.execute(pool)
|
||||||
|
.await
|
||||||
|
.map(|_| ())
|
||||||
|
}
|
||||||
|
|
||||||
impl ActiveModelBehavior for ActiveModel {}
|
|
||||||
|
|
||||||
impl Entity {
|
|
||||||
/// Gets the master password from the database
|
/// Gets the master password from the database
|
||||||
#[inline]
|
#[inline]
|
||||||
pub async fn get(user_id: u64, db: &DatabaseConnection) -> crate::Result<Option<Model>> {
|
pub async fn get(user_id: u64, pool: &Pool) -> crate::Result<Option<Self>> {
|
||||||
Self::find_by_id(user_id).one(db).await
|
query_as("SELECT * FROM master_pass WHERE user_id = ?")
|
||||||
|
.bind(user_id)
|
||||||
|
.fetch_optional(pool)
|
||||||
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if the master password for the user exists
|
/// Checks if the master password for the user exists
|
||||||
#[inline]
|
#[inline]
|
||||||
pub async fn exists(user_id: u64, db: &DatabaseConnection) -> Result<bool, DbErr> {
|
pub async fn exists(user_id: u64, pool: &Pool) -> crate::Result<bool> {
|
||||||
let count = Self::find_by_id(user_id).count(db).await?;
|
query_as::<_, (bool,)>(
|
||||||
Ok(count != 0)
|
"SELECT EXISTS(SELECT * FROM master_pass WHERE user_id = ? LIMIT 1) as value",
|
||||||
|
)
|
||||||
|
.bind(user_id)
|
||||||
|
.fetch_one(pool)
|
||||||
|
.await
|
||||||
|
.map(|(exists,)| exists)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes a master password of the user from the database
|
/// Removes a master password of the user from the database
|
||||||
pub async fn remove(user_id: u64, db: &impl ConnectionTrait) -> Result<(), DbErr> {
|
pub async fn remove(
|
||||||
Self::delete_by_id(user_id).exec(db).await?;
|
user_id: u64,
|
||||||
Ok(())
|
pool: impl Executor<'_, Database = MySql>,
|
||||||
|
) -> crate::Result<()> {
|
||||||
|
query!("DELETE FROM master_pass WHERE user_id = ?", user_id)
|
||||||
|
.execute(pool)
|
||||||
|
.await
|
||||||
|
.map(|_| ())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,2 @@
|
|||||||
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.11.3
|
pub use crate::account::Account;
|
||||||
|
pub use crate::master_pass::MasterPass;
|
||||||
pub use crate::account::{self, Entity as Account};
|
|
||||||
pub use crate::master_pass::{self, Entity as MasterPass};
|
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "migration"
|
|
||||||
version = "0.2.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[lints]
|
|
||||||
workspace = true
|
|
||||||
|
|
||||||
[dependencies.sea-orm-migration]
|
|
||||||
version = "0.12"
|
|
||||||
features = ["runtime-tokio-rustls", "sqlx-mysql"]
|
|
||||||
default-features = false
|
|
@ -1,16 +0,0 @@
|
|||||||
pub use sea_orm_migration::prelude::*;
|
|
||||||
|
|
||||||
mod m20220101_000001_create_table;
|
|
||||||
mod m20230427_142510_change_password_hash_size;
|
|
||||||
|
|
||||||
pub struct Migrator;
|
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
|
||||||
impl MigratorTrait for Migrator {
|
|
||||||
fn migrations() -> Vec<Box<dyn MigrationTrait>> {
|
|
||||||
vec![
|
|
||||||
Box::new(m20220101_000001_create_table::Migration),
|
|
||||||
Box::new(m20230427_142510_change_password_hash_size::Migration),
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,80 +0,0 @@
|
|||||||
use sea_orm_migration::prelude::*;
|
|
||||||
|
|
||||||
#[derive(Iden)]
|
|
||||||
enum MasterPass {
|
|
||||||
Table,
|
|
||||||
#[iden = "user_id"]
|
|
||||||
UserId,
|
|
||||||
Salt,
|
|
||||||
#[iden = "password_hash"]
|
|
||||||
PasswordHash,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Iden)]
|
|
||||||
enum Account {
|
|
||||||
Table,
|
|
||||||
#[iden = "user_id"]
|
|
||||||
UserId,
|
|
||||||
Name,
|
|
||||||
Salt,
|
|
||||||
#[iden = "enc_login"]
|
|
||||||
EncLogin,
|
|
||||||
#[iden = "enc_password"]
|
|
||||||
EncPassword,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(DeriveMigrationName)]
|
|
||||||
pub struct Migration;
|
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
|
||||||
impl MigrationTrait for Migration {
|
|
||||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
|
||||||
manager
|
|
||||||
.create_table(
|
|
||||||
Table::create()
|
|
||||||
.table(MasterPass::Table)
|
|
||||||
.if_not_exists()
|
|
||||||
.col(
|
|
||||||
ColumnDef::new(MasterPass::UserId)
|
|
||||||
.big_unsigned()
|
|
||||||
.primary_key()
|
|
||||||
.not_null(),
|
|
||||||
)
|
|
||||||
.col(ColumnDef::new(MasterPass::Salt).binary_len(64).not_null())
|
|
||||||
.col(
|
|
||||||
ColumnDef::new(MasterPass::PasswordHash)
|
|
||||||
.binary_len(128)
|
|
||||||
.not_null(),
|
|
||||||
)
|
|
||||||
.to_owned(),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
manager
|
|
||||||
.create_table(
|
|
||||||
Table::create()
|
|
||||||
.table(Account::Table)
|
|
||||||
.if_not_exists()
|
|
||||||
.col(ColumnDef::new(Account::UserId).big_unsigned().not_null())
|
|
||||||
.col(ColumnDef::new(Account::Name).string_len(256).not_null())
|
|
||||||
.col(ColumnDef::new(Account::Salt).binary_len(64).not_null())
|
|
||||||
.col(ColumnDef::new(Account::EncLogin).var_binary(256).not_null())
|
|
||||||
.col(
|
|
||||||
ColumnDef::new(Account::EncPassword)
|
|
||||||
.var_binary(256)
|
|
||||||
.not_null(),
|
|
||||||
)
|
|
||||||
.primary_key(Index::create().col(Account::UserId).col(Account::Name))
|
|
||||||
.to_owned(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
|
||||||
manager
|
|
||||||
.drop_table(sea_query::Table::drop().table(MasterPass::Table).to_owned())
|
|
||||||
.await?;
|
|
||||||
manager
|
|
||||||
.drop_table(sea_query::Table::drop().table(Account::Table).to_owned())
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
use sea_orm_migration::prelude::*;
|
|
||||||
|
|
||||||
#[derive(DeriveMigrationName)]
|
|
||||||
pub struct Migration;
|
|
||||||
|
|
||||||
#[derive(Iden)]
|
|
||||||
enum MasterPass {
|
|
||||||
Table,
|
|
||||||
#[iden = "password_hash"]
|
|
||||||
PasswordHash,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait::async_trait]
|
|
||||||
impl MigrationTrait for Migration {
|
|
||||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
|
||||||
manager
|
|
||||||
.alter_table(
|
|
||||||
sea_query::Table::alter()
|
|
||||||
.table(MasterPass::Table)
|
|
||||||
.modify_column(
|
|
||||||
ColumnDef::new(MasterPass::PasswordHash)
|
|
||||||
.binary_len(64)
|
|
||||||
.not_null(),
|
|
||||||
)
|
|
||||||
.to_owned(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
|
||||||
manager
|
|
||||||
.alter_table(
|
|
||||||
sea_query::Table::alter()
|
|
||||||
.table(MasterPass::Table)
|
|
||||||
.modify_column(
|
|
||||||
ColumnDef::new(MasterPass::PasswordHash)
|
|
||||||
.binary_len(128)
|
|
||||||
.not_null(),
|
|
||||||
)
|
|
||||||
.to_owned(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
|
||||||
}
|
|
3
migrations/0001_init.down.sql
Normal file
3
migrations/0001_init.down.sql
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
DROP TABLE account;
|
||||||
|
|
||||||
|
DROP TABLE master_pass;
|
16
migrations/0001_init.up.sql
Normal file
16
migrations/0001_init.up.sql
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
CREATE TABLE
|
||||||
|
master_pass (
|
||||||
|
user_id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
|
||||||
|
salt BINARY(64) NOT NULL,
|
||||||
|
password_hash BINARY(64) NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE
|
||||||
|
account (
|
||||||
|
user_id BIGINT UNSIGNED NOT NULL,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
salt BINARY(64) NOT NULL,
|
||||||
|
enc_login VARBINARY(256) NOT NULL,
|
||||||
|
enc_password VARBINARY(256) NOT NULL,
|
||||||
|
PRIMARY KEY (user_id, name)
|
||||||
|
);
|
@ -1,9 +1,7 @@
|
|||||||
use super::AlterableField::{self, Login, Name, Pass};
|
use super::AlterableField::{self, Login, Name, Pass};
|
||||||
use crate::{change_state, prelude::*};
|
use crate::{change_state, prelude::*};
|
||||||
use account::ActiveModel;
|
|
||||||
use cryptography::account::Cipher;
|
use cryptography::account::Cipher;
|
||||||
use futures::TryFutureExt;
|
use futures::TryFutureExt;
|
||||||
use sea_orm::ActiveValue::Set;
|
|
||||||
use tokio::{task::spawn_blocking, try_join};
|
use tokio::{task::spawn_blocking, try_join};
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@ -16,12 +14,12 @@ async fn update_account(
|
|||||||
master_pass: String,
|
master_pass: String,
|
||||||
) -> crate::Result<()> {
|
) -> crate::Result<()> {
|
||||||
if field == Name {
|
if field == Name {
|
||||||
Account::update_name(user_id, name, field_value, db).await?;
|
Account::update_name(user_id, &name, &field_value, db).await?;
|
||||||
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let salt = Account::get_salt(user_id, name.clone(), db).await?.unwrap();
|
let salt = Account::get_salt(user_id, &name, db).await?.unwrap();
|
||||||
|
|
||||||
let field_value = spawn_blocking(move || {
|
let field_value = spawn_blocking(move || {
|
||||||
let cipher = Cipher::new(master_pass.as_bytes(), &salt);
|
let cipher = Cipher::new(master_pass.as_bytes(), &salt);
|
||||||
@ -31,20 +29,12 @@ async fn update_account(
|
|||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let mut model = ActiveModel {
|
|
||||||
user_id: Set(user_id),
|
|
||||||
name: Set(name),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
match field {
|
match field {
|
||||||
Login => model.enc_login = Set(field_value),
|
Login => Account::update_login(user_id, &name, field_value, db).await?,
|
||||||
Pass => model.enc_password = Set(field_value),
|
Pass => Account::update_password(user_id, &name, field_value, db).await?,
|
||||||
Name => unreachable!(),
|
Name => unreachable!(),
|
||||||
}
|
}
|
||||||
|
|
||||||
model.update(db).await?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ async fn get_master_pass(
|
|||||||
dialogue.exit().await?;
|
dialogue.exit().await?;
|
||||||
|
|
||||||
let user_id = msg.from().ok_or(NoUserInfo)?.id.0;
|
let user_id = msg.from().ok_or(NoUserInfo)?.id.0;
|
||||||
Account::delete_by_id((user_id, name)).exec(&db).await?;
|
Account::delete(user_id, &name, &db).await?;
|
||||||
|
|
||||||
ids.alter_message(
|
ids.alter_message(
|
||||||
&bot,
|
&bot,
|
||||||
|
@ -5,10 +5,7 @@ use tokio::task::spawn_blocking;
|
|||||||
pub async fn delete(bot: Throttle<Bot>, msg: Message, db: DatabaseConnection) -> crate::Result<()> {
|
pub async fn delete(bot: Throttle<Bot>, msg: Message, db: DatabaseConnection) -> crate::Result<()> {
|
||||||
let user_id = msg.from().ok_or(NoUserInfo)?.id.0;
|
let user_id = msg.from().ok_or(NoUserInfo)?.id.0;
|
||||||
|
|
||||||
let names: Vec<String> = Account::get_names(user_id, &db)
|
let names: Vec<String> = Account::get_names(user_id, &db).try_collect().await?;
|
||||||
.await?
|
|
||||||
.try_collect()
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if names.is_empty() {
|
if names.is_empty() {
|
||||||
bot.send_message(msg.chat.id, "You don't have any accounts")
|
bot.send_message(msg.chat.id, "You don't have any accounts")
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use log::error;
|
use log::error;
|
||||||
use sea_orm::TransactionTrait;
|
|
||||||
use tokio::try_join;
|
|
||||||
|
|
||||||
/// Gets the master password, deletes the accounts and the master password from DB.
|
/// Gets the master password, deletes the accounts and the master password from DB.
|
||||||
/// Although it doesn't use the master password, we get it to be sure that it's the user who used that command
|
/// Although it doesn't use the master password, we get it to be sure that it's the user who used that command
|
||||||
@ -16,17 +14,17 @@ async fn get_master_pass(
|
|||||||
) -> crate::Result<()> {
|
) -> crate::Result<()> {
|
||||||
dialogue.exit().await?;
|
dialogue.exit().await?;
|
||||||
let user_id = msg.from().ok_or(NoUserInfo)?.id.0;
|
let user_id = msg.from().ok_or(NoUserInfo)?.id.0;
|
||||||
let txn = db.begin().await?;
|
let mut txn = db.begin().await?;
|
||||||
let result = try_join!(
|
let result = (
|
||||||
Account::delete_all(user_id, &txn),
|
Account::delete_all(user_id, &mut *txn).await,
|
||||||
MasterPass::remove(user_id, &txn),
|
MasterPass::remove(user_id, &mut *txn).await,
|
||||||
);
|
);
|
||||||
let text = match result {
|
let text = match result {
|
||||||
Ok(_) => {
|
(Ok(()), Ok(())) => {
|
||||||
txn.commit().await?;
|
txn.commit().await?;
|
||||||
"Everything was deleted"
|
"Everything was deleted"
|
||||||
}
|
}
|
||||||
Err(err) => {
|
(Err(err), _) | (_, Err(err)) => {
|
||||||
error!("{}", crate::Error::from(err));
|
error!("{}", crate::Error::from(err));
|
||||||
txn.rollback().await?;
|
txn.rollback().await?;
|
||||||
"Something went wrong. Try again later"
|
"Something went wrong. Try again later"
|
||||||
|
@ -7,7 +7,7 @@ use tokio::task::spawn_blocking;
|
|||||||
/// Decryptes the account on a worker thread and adds it to the accounts vector
|
/// Decryptes the account on a worker thread and adds it to the accounts vector
|
||||||
#[inline]
|
#[inline]
|
||||||
async fn decrypt_account(
|
async fn decrypt_account(
|
||||||
account: account::Model,
|
account: Account,
|
||||||
master_pass: Arc<str>,
|
master_pass: Arc<str>,
|
||||||
accounts: &Mutex<&mut Vec<DecryptedAccount>>,
|
accounts: &Mutex<&mut Vec<DecryptedAccount>>,
|
||||||
) -> crate::Result<()> {
|
) -> crate::Result<()> {
|
||||||
@ -38,7 +38,6 @@ async fn get_master_pass(
|
|||||||
let master_pass: Arc<str> = master_pass.into();
|
let master_pass: Arc<str> = master_pass.into();
|
||||||
|
|
||||||
Account::get_all(user_id, &db)
|
Account::get_all(user_id, &db)
|
||||||
.await?
|
|
||||||
.err_into::<crate::Error>()
|
.err_into::<crate::Error>()
|
||||||
.try_for_each_concurrent(3, |account| {
|
.try_for_each_concurrent(3, |account| {
|
||||||
decrypt_account(account, master_pass.clone(), &accounts)
|
decrypt_account(account, master_pass.clone(), &accounts)
|
||||||
|
@ -9,10 +9,7 @@ pub async fn get_account(
|
|||||||
) -> crate::Result<()> {
|
) -> crate::Result<()> {
|
||||||
let user_id = msg.from().ok_or(NoUserInfo)?.id.0;
|
let user_id = msg.from().ok_or(NoUserInfo)?.id.0;
|
||||||
|
|
||||||
let names: Vec<String> = Account::get_names(user_id, &db)
|
let names: Vec<String> = Account::get_names(user_id, &db).try_collect().await?;
|
||||||
.await?
|
|
||||||
.try_collect()
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if names.is_empty() {
|
if names.is_empty() {
|
||||||
bot.send_message(msg.chat.id, "You don't have any accounts")
|
bot.send_message(msg.chat.id, "You don't have any accounts")
|
||||||
|
@ -11,7 +11,7 @@ pub async fn get_accounts(
|
|||||||
db: DatabaseConnection,
|
db: DatabaseConnection,
|
||||||
) -> crate::Result<()> {
|
) -> crate::Result<()> {
|
||||||
let user_id = msg.from().ok_or(NoUserInfo)?.id.0;
|
let user_id = msg.from().ok_or(NoUserInfo)?.id.0;
|
||||||
let mut account_names = Account::get_names(user_id, &db).await?;
|
let mut account_names = Account::get_names(user_id, &db);
|
||||||
|
|
||||||
let mut text = if let Some(name) = account_names.try_next().await? {
|
let mut text = if let Some(name) = account_names.try_next().await? {
|
||||||
format!("Accounts:\n`{name}`")
|
format!("Accounts:\n`{name}`")
|
||||||
|
@ -18,7 +18,7 @@ async fn encrypt_account(
|
|||||||
let name = account.name.clone();
|
let name = account.name.clone();
|
||||||
match spawn_blocking(move || account.into_account(user_id, &master_pass)).await {
|
match spawn_blocking(move || account.into_account(user_id, &master_pass)).await {
|
||||||
Ok(account) => match account.insert(db).await {
|
Ok(account) => match account.insert(db).await {
|
||||||
Ok(_) => (),
|
Ok(()) => (),
|
||||||
Err(_) => failed.lock().push(name),
|
Err(_) => failed.lock().push(name),
|
||||||
},
|
},
|
||||||
_ => failed.lock().push(name),
|
_ => failed.lock().push(name),
|
||||||
|
@ -5,10 +5,7 @@ use tokio::task::spawn_blocking;
|
|||||||
pub async fn menu(bot: Throttle<Bot>, msg: Message, db: DatabaseConnection) -> crate::Result<()> {
|
pub async fn menu(bot: Throttle<Bot>, msg: Message, db: DatabaseConnection) -> crate::Result<()> {
|
||||||
let user_id = msg.from().ok_or(NoUserInfo)?.id.0;
|
let user_id = msg.from().ok_or(NoUserInfo)?.id.0;
|
||||||
|
|
||||||
let names: Vec<String> = Account::get_names(user_id, &db)
|
let names: Vec<String> = Account::get_names(user_id, &db).try_collect().await?;
|
||||||
.await?
|
|
||||||
.try_collect()
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if names.is_empty() {
|
if names.is_empty() {
|
||||||
bot.send_message(msg.chat.id, "You don't have any accounts")
|
bot.send_message(msg.chat.id, "You don't have any accounts")
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
use crate::{change_state, prelude::*};
|
use crate::{change_state, prelude::*};
|
||||||
use cryptography::hashing::HashedBytes;
|
use cryptography::hashing::HashedBytes;
|
||||||
use sea_orm::ActiveValue::Set;
|
|
||||||
use tokio::task::spawn_blocking;
|
use tokio::task::spawn_blocking;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@ -14,7 +13,7 @@ async fn get_master_pass2(
|
|||||||
master_pass: String,
|
master_pass: String,
|
||||||
) -> crate::Result<()> {
|
) -> crate::Result<()> {
|
||||||
dialogue.exit().await?;
|
dialogue.exit().await?;
|
||||||
let user_id = Set(msg.from().ok_or(NoUserInfo)?.id.0);
|
let user_id = msg.from().ok_or(NoUserInfo)?.id.0;
|
||||||
|
|
||||||
if !hash.verify(master_pass.as_bytes()) {
|
if !hash.verify(master_pass.as_bytes()) {
|
||||||
ids.alter_message(
|
ids.alter_message(
|
||||||
@ -28,10 +27,10 @@ async fn get_master_pass2(
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let model = master_pass::ActiveModel {
|
let model = MasterPass {
|
||||||
user_id,
|
user_id,
|
||||||
password_hash: Set(hash.hash.to_vec()),
|
password_hash: hash.hash.to_vec(),
|
||||||
salt: Set(hash.salt.to_vec()),
|
salt: hash.salt.to_vec(),
|
||||||
};
|
};
|
||||||
model.insert(&db).await?;
|
model.insert(&db).await?;
|
||||||
|
|
||||||
|
13
src/main.rs
13
src/main.rs
@ -1,6 +1,3 @@
|
|||||||
// #![warn(clippy::pedantic, clippy::all, clippy::nursery)]
|
|
||||||
// #![allow(clippy::single_match_else)]
|
|
||||||
|
|
||||||
mod callbacks;
|
mod callbacks;
|
||||||
mod commands;
|
mod commands;
|
||||||
mod default;
|
mod default;
|
||||||
@ -16,9 +13,7 @@ mod utils;
|
|||||||
|
|
||||||
use anyhow::{Error, Result};
|
use anyhow::{Error, Result};
|
||||||
use dotenvy::dotenv;
|
use dotenvy::dotenv;
|
||||||
use migration::{Migrator, MigratorTrait};
|
|
||||||
use prelude::*;
|
use prelude::*;
|
||||||
use sea_orm::Database;
|
|
||||||
use std::env;
|
use std::env;
|
||||||
use teloxide::{adaptors::throttle::Limits, dispatching::dialogue::InMemStorage, filter_command};
|
use teloxide::{adaptors::throttle::Limits, dispatching::dialogue::InMemStorage, filter_command};
|
||||||
|
|
||||||
@ -92,8 +87,10 @@ async fn main() -> Result<()> {
|
|||||||
|
|
||||||
let token = env::var("TOKEN").expect("expected TOKEN in the enviroment");
|
let token = env::var("TOKEN").expect("expected TOKEN in the enviroment");
|
||||||
let database_url = env::var("DATABASE_URL").expect("expected DATABASE_URL in the enviroment");
|
let database_url = env::var("DATABASE_URL").expect("expected DATABASE_URL in the enviroment");
|
||||||
let db = Database::connect(database_url).await?;
|
let pool = sqlx::mysql::MySqlPool::connect(&database_url).await?;
|
||||||
Migrator::up(&db, None).await?;
|
|
||||||
get_dispatcher(token, db).dispatch().await;
|
sqlx::migrate!().run(&pool).await?;
|
||||||
|
|
||||||
|
get_dispatcher(token, pool).dispatch().await;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -32,7 +32,7 @@ pub async fn menu_markup(
|
|||||||
db: &DatabaseConnection,
|
db: &DatabaseConnection,
|
||||||
) -> crate::Result<InlineKeyboardMarkup> {
|
) -> crate::Result<InlineKeyboardMarkup> {
|
||||||
let command: String = command.into();
|
let command: String = command.into();
|
||||||
let names: Vec<String> = Account::get_names(user_id, db).await?.try_collect().await?;
|
let names: Vec<String> = Account::get_names(user_id, db).try_collect().await?;
|
||||||
|
|
||||||
spawn_blocking(move || menu_markup_sync(&command, names))
|
spawn_blocking(move || menu_markup_sync(&command, names))
|
||||||
.await
|
.await
|
||||||
|
@ -8,7 +8,6 @@ pub use crate::{
|
|||||||
utils::*,
|
utils::*,
|
||||||
};
|
};
|
||||||
pub use cryptography::prelude::*;
|
pub use cryptography::prelude::*;
|
||||||
pub use entity::prelude::*;
|
pub use entity::{prelude::*, Pool as DatabaseConnection};
|
||||||
pub use futures::{StreamExt, TryStreamExt};
|
pub use futures::{StreamExt, TryStreamExt};
|
||||||
pub use sea_orm::prelude::*;
|
|
||||||
pub use teloxide::{adaptors::Throttle, prelude::*};
|
pub use teloxide::{adaptors::Throttle, prelude::*};
|
||||||
|
@ -165,7 +165,6 @@ pub async fn get_user(
|
|||||||
|
|
||||||
let existing_names = async {
|
let existing_names = async {
|
||||||
Account::get_names(user_id, &db)
|
Account::get_names(user_id, &db)
|
||||||
.await?
|
|
||||||
.try_collect()
|
.try_collect()
|
||||||
.await
|
.await
|
||||||
.map_err(Into::into)
|
.map_err(Into::into)
|
||||||
|
Loading…
Reference in New Issue
Block a user