Updated cryptography functions to be in-place
This commit is contained in:
		
							
								
								
									
										5
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										5
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							@@ -462,6 +462,7 @@ dependencies = [
 | 
				
			|||||||
 "rand",
 | 
					 "rand",
 | 
				
			||||||
 "scrypt",
 | 
					 "scrypt",
 | 
				
			||||||
 "sea-orm",
 | 
					 "sea-orm",
 | 
				
			||||||
 | 
					 "serde",
 | 
				
			||||||
 "sha2",
 | 
					 "sha2",
 | 
				
			||||||
 "subtle",
 | 
					 "subtle",
 | 
				
			||||||
 "thiserror",
 | 
					 "thiserror",
 | 
				
			||||||
@@ -1865,9 +1866,9 @@ dependencies = [
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "rust_decimal"
 | 
					name = "rust_decimal"
 | 
				
			||||||
version = "1.33.0"
 | 
					version = "1.33.1"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "076ba1058b036d3ca8bcafb1d54d0b0572e99d7ecd3e4222723e18ca8e9ca9a8"
 | 
					checksum = "06676aec5ccb8fc1da723cc8c0f9a46549f21ebb8753d3915c6c41db1e7f1dc4"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "arrayvec",
 | 
					 "arrayvec",
 | 
				
			||||||
 "borsh",
 | 
					 "borsh",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -24,3 +24,4 @@ bitflags = "2"
 | 
				
			|||||||
arrayvec = "0.7"
 | 
					arrayvec = "0.7"
 | 
				
			||||||
subtle = "2"
 | 
					subtle = "2"
 | 
				
			||||||
once_cell = "1"
 | 
					once_cell = "1"
 | 
				
			||||||
 | 
					serde = { version = "1", features = ["derive"] }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,5 @@
 | 
				
			|||||||
use chacha20poly1305::{aead::Aead, AeadCore, ChaCha20Poly1305, KeyInit};
 | 
					use chacha20poly1305::{AeadCore, AeadInPlace, ChaCha20Poly1305, KeyInit};
 | 
				
			||||||
use entity::account;
 | 
					use entity::account::{self, ActiveModel};
 | 
				
			||||||
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 sea_orm::ActiveValue::Set;
 | 
				
			||||||
@@ -23,68 +23,89 @@ impl Cipher {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    /// Encrypts the value with the current cipher. The 12 byte nonce is appended to the result
 | 
					    /// Encrypts the value with the current cipher. The 12 byte nonce is appended to the result
 | 
				
			||||||
    #[inline]
 | 
					    #[inline]
 | 
				
			||||||
    pub fn encrypt(&self, value: &[u8]) -> crate::Result<Vec<u8>> {
 | 
					    #[allow(clippy::missing_panics_doc)]
 | 
				
			||||||
 | 
					    pub fn encrypt(&self, value: &mut Vec<u8>) {
 | 
				
			||||||
        let nonce = ChaCha20Poly1305::generate_nonce(&mut OsRng);
 | 
					        let nonce = ChaCha20Poly1305::generate_nonce(&mut OsRng);
 | 
				
			||||||
        let mut result = self.chacha.encrypt(&nonce, value)?;
 | 
					        self.chacha.encrypt_in_place(&nonce, b"", value).unwrap();
 | 
				
			||||||
        result.extend(nonce);
 | 
					        value.extend_from_slice(&nonce);
 | 
				
			||||||
        Ok(result)
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Decrypts the value with the current cipher. The 12 byte nonce is expected to be at the end of the value
 | 
					    /// Decrypts the value with the current cipher. The 12 byte nonce is expected to be at the end of the value
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// # Errors
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// Returns an error if the tag doesn't match the ciphertext
 | 
				
			||||||
    #[inline]
 | 
					    #[inline]
 | 
				
			||||||
    pub fn decrypt(&self, value: &[u8]) -> crate::Result<Vec<u8>> {
 | 
					    pub fn decrypt(&self, value: &mut Vec<u8>) -> crate::Result<()> {
 | 
				
			||||||
        let (data, nonce) = value.split_at(value.len() - 12);
 | 
					        let nonce: [u8; 12] = value[value.len() - 12..]
 | 
				
			||||||
 | 
					            .try_into()
 | 
				
			||||||
 | 
					            .map_err(|_| crate::Error::InvalidInputLength)?;
 | 
				
			||||||
 | 
					        value.truncate(value.len() - 12);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        self.chacha.decrypt(nonce.into(), data).map_err(Into::into)
 | 
					        self.chacha
 | 
				
			||||||
 | 
					            .decrypt_in_place(nonce.as_slice().into(), b"", value)
 | 
				
			||||||
 | 
					            .map_err(Into::into)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub trait FromUnencryptedExt {
 | 
					#[derive(serde::Serialize, serde::Deserialize)]
 | 
				
			||||||
    fn from_unencrypted(
 | 
					pub struct Decrypted {
 | 
				
			||||||
        user_id: u64,
 | 
					    pub name: String,
 | 
				
			||||||
        name: String,
 | 
					    pub login: String,
 | 
				
			||||||
        login: &str,
 | 
					    pub password: String,
 | 
				
			||||||
        password: &str,
 | 
					 | 
				
			||||||
        master_pass: &str,
 | 
					 | 
				
			||||||
    ) -> crate::Result<account::ActiveModel>;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl FromUnencryptedExt for account::ActiveModel {
 | 
					impl Decrypted {
 | 
				
			||||||
    /// Encryptes the provided data by the master password and creates the `ActiveModel` with all fields set to Set variant
 | 
					    /// Constructs `DecryptedAccount` by decrypting the provided account
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// # Errors
 | 
				
			||||||
 | 
					    ///
 | 
				
			||||||
 | 
					    /// Returns an error if the tag doesn't match the ciphertext or if the decrypted data isn't valid UTF-8
 | 
				
			||||||
    #[inline]
 | 
					    #[inline]
 | 
				
			||||||
    fn from_unencrypted(
 | 
					    pub fn from_account(mut account: account::Model, master_pass: &str) -> crate::Result<Self> {
 | 
				
			||||||
        user_id: u64,
 | 
					        let cipher = Cipher::new(master_pass.as_bytes(), &account.salt);
 | 
				
			||||||
        name: String,
 | 
					        cipher.decrypt(&mut account.enc_login)?;
 | 
				
			||||||
        login: &str,
 | 
					        cipher.decrypt(&mut account.enc_password)?;
 | 
				
			||||||
        password: &str,
 | 
					
 | 
				
			||||||
        master_pass: &str,
 | 
					 | 
				
			||||||
    ) -> crate::Result<Self> {
 | 
					 | 
				
			||||||
        let mut salt = vec![0; 64];
 | 
					 | 
				
			||||||
        OsRng.fill_bytes(&mut salt);
 | 
					 | 
				
			||||||
        let cipher = Cipher::new(master_pass.as_bytes(), &salt);
 | 
					 | 
				
			||||||
        let enc_login = Set(cipher.encrypt(login.as_bytes())?);
 | 
					 | 
				
			||||||
        let enc_password = Set(cipher.encrypt(password.as_bytes())?);
 | 
					 | 
				
			||||||
        Ok(Self {
 | 
					        Ok(Self {
 | 
				
			||||||
            name: Set(name),
 | 
					            name: account.name,
 | 
				
			||||||
            user_id: Set(user_id),
 | 
					            login: String::from_utf8(account.enc_login)?,
 | 
				
			||||||
            salt: Set(salt),
 | 
					            password: String::from_utf8(account.enc_password)?,
 | 
				
			||||||
            enc_login,
 | 
					 | 
				
			||||||
            enc_password,
 | 
					 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub trait DecryptAccountExt {
 | 
					    /// Constructs `ActiveModel` with eath field Set by encrypting `self`
 | 
				
			||||||
    fn decrypt(&self, master_pass: &str) -> crate::Result<(String, String)>;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl DecryptAccountExt for account::Model {
 | 
					 | 
				
			||||||
    /// Returns the decrypted login and password of the account
 | 
					 | 
				
			||||||
    #[inline]
 | 
					    #[inline]
 | 
				
			||||||
    fn decrypt(&self, master_pass: &str) -> crate::Result<(String, String)> {
 | 
					    #[must_use]
 | 
				
			||||||
        let cipher = Cipher::new(master_pass.as_bytes(), &self.salt);
 | 
					    pub fn into_account(self, user_id: u64, master_pass: &str) -> account::ActiveModel {
 | 
				
			||||||
        let login = String::from_utf8(cipher.decrypt(&self.enc_login)?)?;
 | 
					        let mut login = self.login.into_bytes();
 | 
				
			||||||
        let password = String::from_utf8(cipher.decrypt(&self.enc_password)?)?;
 | 
					        let mut password = self.password.into_bytes();
 | 
				
			||||||
        Ok((login, password))
 | 
					        let mut salt = vec![0; 64];
 | 
				
			||||||
 | 
					        OsRng.fill_bytes(&mut salt);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let cipher = Cipher::new(master_pass.as_bytes(), &salt);
 | 
				
			||||||
 | 
					        cipher.encrypt(&mut login);
 | 
				
			||||||
 | 
					        cipher.encrypt(&mut password);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ActiveModel {
 | 
				
			||||||
 | 
					            user_id: Set(user_id),
 | 
				
			||||||
 | 
					            name: Set(self.name),
 | 
				
			||||||
 | 
					            salt: Set(salt),
 | 
				
			||||||
 | 
					            enc_login: Set(login),
 | 
				
			||||||
 | 
					            enc_password: Set(password),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Returns true if the account's fields are valid
 | 
				
			||||||
 | 
					    #[inline]
 | 
				
			||||||
 | 
					    #[must_use]
 | 
				
			||||||
 | 
					    pub fn validate(&self) -> bool {
 | 
				
			||||||
 | 
					        [
 | 
				
			||||||
 | 
					            self.name.as_str(),
 | 
				
			||||||
 | 
					            self.login.as_str(),
 | 
				
			||||||
 | 
					            self.password.as_str(),
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					        .into_iter()
 | 
				
			||||||
 | 
					        .all(super::validate_field)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,4 @@
 | 
				
			|||||||
//! Functions to encrypt the database models
 | 
					//! Functions to encrypt the database models
 | 
				
			||||||
#![allow(clippy::missing_errors_doc)]
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub mod account;
 | 
					pub mod account;
 | 
				
			||||||
pub mod hashing;
 | 
					pub mod hashing;
 | 
				
			||||||
@@ -7,8 +6,23 @@ pub mod master_pass;
 | 
				
			|||||||
pub mod passwords;
 | 
					pub mod passwords;
 | 
				
			||||||
pub mod prelude;
 | 
					pub mod prelude;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Returns true if the field is valid
 | 
				
			||||||
 | 
					#[inline]
 | 
				
			||||||
 | 
					#[must_use]
 | 
				
			||||||
 | 
					pub fn validate_field(field: &str) -> bool {
 | 
				
			||||||
 | 
					    if !(1..255).contains(&field.len()) {
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    field
 | 
				
			||||||
 | 
					        .chars()
 | 
				
			||||||
 | 
					        .all(|char| !['`', '\\', '\n', '\t'].contains(&char))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(thiserror::Error, Debug)]
 | 
					#[derive(thiserror::Error, Debug)]
 | 
				
			||||||
pub enum Error {
 | 
					pub enum Error {
 | 
				
			||||||
 | 
					    #[error("Invalid input length")]
 | 
				
			||||||
 | 
					    InvalidInputLength,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    #[error(transparent)]
 | 
					    #[error(transparent)]
 | 
				
			||||||
    ChaChaError(#[from] chacha20poly1305::Error),
 | 
					    ChaChaError(#[from] chacha20poly1305::Error),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,2 +1,3 @@
 | 
				
			|||||||
pub use crate::account::{DecryptAccountExt as _, FromUnencryptedExt as _};
 | 
					pub use crate::{
 | 
				
			||||||
pub use crate::master_pass::FromUnencryptedExt as _;
 | 
					    account::Decrypted as DecryptedAccount, master_pass::FromUnencryptedExt as _, validate_field,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -25,7 +25,9 @@ async fn update_account(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    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);
 | 
				
			||||||
        cipher.encrypt(field_value.as_bytes()).unwrap()
 | 
					        let mut field = field_value.into_bytes();
 | 
				
			||||||
 | 
					        cipher.encrypt(&mut field);
 | 
				
			||||||
 | 
					        field
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
    .await?;
 | 
					    .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,8 +23,13 @@ async fn get_master_pass(
 | 
				
			|||||||
        return Ok(());
 | 
					        return Ok(());
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let (login, password) = spawn_blocking(move || account.decrypt(&master_pass)).await??;
 | 
					    let account =
 | 
				
			||||||
    let text = format!("Name:\n`{name}`\nLogin:\n`{login}`\nPassword:\n`{password}`");
 | 
					        spawn_blocking(move || DecryptedAccount::from_account(account, &master_pass)).await??;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let text = format!(
 | 
				
			||||||
 | 
					        "Name:\n`{name}`\nLogin:\n`{}`\nPassword:\n`{}`",
 | 
				
			||||||
 | 
					        account.login, account.password
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ids.alter_message(
 | 
					    ids.alter_message(
 | 
				
			||||||
        &bot,
 | 
					        &bot,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,9 +20,14 @@ async fn get_master_pass(
 | 
				
			|||||||
    let user_id = msg.from().ok_or(NoUserInfo)?.id.0;
 | 
					    let user_id = msg.from().ok_or(NoUserInfo)?.id.0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let account = spawn_blocking(move || {
 | 
					    let account = spawn_blocking(move || {
 | 
				
			||||||
        account::ActiveModel::from_unencrypted(user_id, name, &login, &password, &master_pass)
 | 
					        DecryptedAccount {
 | 
				
			||||||
 | 
					            name,
 | 
				
			||||||
 | 
					            login,
 | 
				
			||||||
 | 
					            password,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        .into_account(user_id, &master_pass)
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
    .await??;
 | 
					    .await?;
 | 
				
			||||||
    account.insert(&db).await?;
 | 
					    account.insert(&db).await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ids.alter_message(&bot, "Success", deletion_markup(), None)
 | 
					    ids.alter_message(&bot, "Success", deletion_markup(), None)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,7 +17,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(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),
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,58 +1,8 @@
 | 
				
			|||||||
//! Models to export and import the accounts
 | 
					//! Models to export and import the accounts
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::prelude::*;
 | 
					use crate::prelude::*;
 | 
				
			||||||
use serde::{Deserialize, Serialize};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Serialize, Deserialize)]
 | 
					#[derive(serde::Serialize, serde::Deserialize)]
 | 
				
			||||||
pub struct DecryptedAccount {
 | 
					 | 
				
			||||||
    pub name: String,
 | 
					 | 
				
			||||||
    pub login: String,
 | 
					 | 
				
			||||||
    pub password: String,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl DecryptedAccount {
 | 
					 | 
				
			||||||
    /// Constructs `DecryptedAccount` by decrypting the provided account
 | 
					 | 
				
			||||||
    #[inline]
 | 
					 | 
				
			||||||
    pub fn from_account(account: account::Model, master_pass: &str) -> crate::Result<Self> {
 | 
					 | 
				
			||||||
        let (login, password) = account.decrypt(master_pass)?;
 | 
					 | 
				
			||||||
        Ok(Self {
 | 
					 | 
				
			||||||
            name: account.name,
 | 
					 | 
				
			||||||
            login,
 | 
					 | 
				
			||||||
            password,
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// Constructs `ActiveModel` with eath field Set by encrypting `self`
 | 
					 | 
				
			||||||
    #[inline]
 | 
					 | 
				
			||||||
    pub fn into_account(
 | 
					 | 
				
			||||||
        self,
 | 
					 | 
				
			||||||
        user_id: u64,
 | 
					 | 
				
			||||||
        master_pass: &str,
 | 
					 | 
				
			||||||
    ) -> crate::Result<account::ActiveModel> {
 | 
					 | 
				
			||||||
        account::ActiveModel::from_unencrypted(
 | 
					 | 
				
			||||||
            user_id,
 | 
					 | 
				
			||||||
            self.name,
 | 
					 | 
				
			||||||
            &self.login,
 | 
					 | 
				
			||||||
            &self.password,
 | 
					 | 
				
			||||||
            master_pass,
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        .map_err(Into::into)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /// Returns true if the account's fields are valid
 | 
					 | 
				
			||||||
    #[inline]
 | 
					 | 
				
			||||||
    pub fn validate(&self) -> bool {
 | 
					 | 
				
			||||||
        [
 | 
					 | 
				
			||||||
            self.name.as_str(),
 | 
					 | 
				
			||||||
            self.login.as_str(),
 | 
					 | 
				
			||||||
            self.password.as_str(),
 | 
					 | 
				
			||||||
        ]
 | 
					 | 
				
			||||||
        .into_iter()
 | 
					 | 
				
			||||||
        .all(validate_field)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Serialize, Deserialize)]
 | 
					 | 
				
			||||||
#[repr(transparent)]
 | 
					#[repr(transparent)]
 | 
				
			||||||
pub struct User {
 | 
					pub struct User {
 | 
				
			||||||
    pub accounts: Vec<DecryptedAccount>,
 | 
					    pub accounts: Vec<DecryptedAccount>,
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										11
									
								
								src/utils.rs
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								src/utils.rs
									
									
									
									
									
								
							@@ -6,17 +6,6 @@ pub async fn delete_message(bot: Throttle<Bot>, msg: Message) {
 | 
				
			|||||||
    let _ = bot.delete_message(msg.chat.id, msg.id).await;
 | 
					    let _ = bot.delete_message(msg.chat.id, msg.id).await;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Returns true if the field is valid
 | 
					 | 
				
			||||||
#[inline]
 | 
					 | 
				
			||||||
pub fn validate_field(field: &str) -> bool {
 | 
					 | 
				
			||||||
    if !(1..255).contains(&field.len()) {
 | 
					 | 
				
			||||||
        return false;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    field
 | 
					 | 
				
			||||||
        .chars()
 | 
					 | 
				
			||||||
        .all(|char| !['`', '\\', '\n', '\t'].contains(&char))
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[inline]
 | 
					#[inline]
 | 
				
			||||||
pub async fn name_from_hash(
 | 
					pub async fn name_from_hash(
 | 
				
			||||||
    db: &DatabaseConnection,
 | 
					    db: &DatabaseConnection,
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user