More clippy fixes
This commit is contained in:
parent
6ae745fcd4
commit
bfd68194e6
@ -11,6 +11,14 @@ strip = true
|
|||||||
[workspace]
|
[workspace]
|
||||||
members = [".", "migration", "entity", "cryptography"]
|
members = [".", "migration", "entity", "cryptography"]
|
||||||
|
|
||||||
|
[workspace.lints.clippy]
|
||||||
|
pedantic = "warn"
|
||||||
|
all = "warn"
|
||||||
|
nursery = "warn"
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
ahash = "0.8.3"
|
ahash = "0.8.3"
|
||||||
anyhow = "1"
|
anyhow = "1"
|
||||||
|
@ -5,6 +5,9 @@ edition = "2021"
|
|||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
sha2 = "0.10"
|
sha2 = "0.10"
|
||||||
scrypt = { version = "0.11", default-features = false, features = ["std"] }
|
scrypt = { version = "0.11", default-features = false, features = ["std"] }
|
||||||
|
@ -12,8 +12,9 @@ pub struct Cipher {
|
|||||||
impl Cipher {
|
impl Cipher {
|
||||||
/// Creates a new cipher from a master password and the salt
|
/// Creates a new cipher from a master password and the salt
|
||||||
#[inline]
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
pub fn new(password: &[u8], salt: &[u8]) -> Self {
|
pub fn new(password: &[u8], salt: &[u8]) -> Self {
|
||||||
let key = pbkdf2_hmac_array::<Sha256, 32>(password, salt, 480000);
|
let key = pbkdf2_hmac_array::<Sha256, 32>(password, salt, 480_000);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
chacha: ChaCha20Poly1305::new(&key.into()),
|
chacha: ChaCha20Poly1305::new(&key.into()),
|
||||||
@ -33,11 +34,12 @@ impl Cipher {
|
|||||||
#[inline]
|
#[inline]
|
||||||
pub fn decrypt(&self, value: &[u8]) -> crate::Result<Vec<u8>> {
|
pub fn decrypt(&self, value: &[u8]) -> crate::Result<Vec<u8>> {
|
||||||
let (data, nonce) = value.split_at(value.len() - 12);
|
let (data, nonce) = value.split_at(value.len() - 12);
|
||||||
|
|
||||||
self.chacha.decrypt(nonce.into(), data).map_err(Into::into)
|
self.chacha.decrypt(nonce.into(), data).map_err(Into::into)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait AccountFromUnencryptedExt {
|
pub trait FromUnencryptedExt {
|
||||||
fn from_unencrypted(
|
fn from_unencrypted(
|
||||||
user_id: u64,
|
user_id: u64,
|
||||||
name: String,
|
name: String,
|
||||||
@ -47,8 +49,8 @@ pub trait AccountFromUnencryptedExt {
|
|||||||
) -> crate::Result<account::ActiveModel>;
|
) -> crate::Result<account::ActiveModel>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AccountFromUnencryptedExt for account::ActiveModel {
|
impl FromUnencryptedExt for account::ActiveModel {
|
||||||
/// Encryptes the provided data by the master password and creates the ActiveModel with all fields set to Set variant
|
/// Encryptes the provided data by the master password and creates the `ActiveModel` with all fields set to Set variant
|
||||||
#[inline]
|
#[inline]
|
||||||
fn from_unencrypted(
|
fn from_unencrypted(
|
||||||
user_id: u64,
|
user_id: u64,
|
||||||
|
@ -11,6 +11,8 @@ static PARAMS: Lazy<Params> = Lazy::new(|| Params::new(14, 8, 1, HASH_LENGTH).un
|
|||||||
|
|
||||||
/// Hashes the bytes with Scrypt with the given salt
|
/// Hashes the bytes with Scrypt with the given salt
|
||||||
#[inline]
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
|
#[allow(clippy::missing_panics_doc)]
|
||||||
pub fn hash_scrypt(bytes: &[u8], salt: &[u8]) -> [u8; HASH_LENGTH] {
|
pub fn hash_scrypt(bytes: &[u8], salt: &[u8]) -> [u8; HASH_LENGTH] {
|
||||||
let mut hash = [0; HASH_LENGTH];
|
let mut hash = [0; HASH_LENGTH];
|
||||||
scrypt(bytes, salt, &PARAMS, &mut hash).unwrap();
|
scrypt(bytes, salt, &PARAMS, &mut hash).unwrap();
|
||||||
@ -29,6 +31,7 @@ where
|
|||||||
|
|
||||||
impl HashedBytes<[u8; HASH_LENGTH], [u8; SALT_LENGTH]> {
|
impl HashedBytes<[u8; HASH_LENGTH], [u8; SALT_LENGTH]> {
|
||||||
#[inline]
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
pub fn new(bytes: &[u8]) -> Self {
|
pub fn new(bytes: &[u8]) -> Self {
|
||||||
let mut salt = [0; 64];
|
let mut salt = [0; 64];
|
||||||
OsRng.fill_bytes(&mut salt);
|
OsRng.fill_bytes(&mut salt);
|
||||||
@ -45,6 +48,7 @@ where
|
|||||||
U: AsRef<[u8]>,
|
U: AsRef<[u8]>,
|
||||||
{
|
{
|
||||||
#[inline]
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
pub fn verify(&self, bytes: &[u8]) -> bool {
|
pub fn verify(&self, bytes: &[u8]) -> bool {
|
||||||
let hash = hash_scrypt(bytes, self.salt.as_ref());
|
let hash = hash_scrypt(bytes, self.salt.as_ref());
|
||||||
hash.ct_eq(self.hash.as_ref()).into()
|
hash.ct_eq(self.hash.as_ref()).into()
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
//! 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;
|
||||||
|
@ -2,12 +2,12 @@ use super::hashing::HashedBytes;
|
|||||||
use entity::master_pass;
|
use entity::master_pass;
|
||||||
use sea_orm::ActiveValue::Set;
|
use sea_orm::ActiveValue::Set;
|
||||||
|
|
||||||
pub trait MasterPassFromUnencryptedExt {
|
pub trait FromUnencryptedExt {
|
||||||
fn from_unencrypted(user_id: u64, password: &str) -> master_pass::ActiveModel;
|
fn from_unencrypted(user_id: u64, password: &str) -> master_pass::ActiveModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MasterPassFromUnencryptedExt for master_pass::ActiveModel {
|
impl FromUnencryptedExt for master_pass::ActiveModel {
|
||||||
/// 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());
|
||||||
|
@ -25,6 +25,7 @@ bitflags::bitflags! {
|
|||||||
/// Returns true if the generated master password is valid.
|
/// Returns true if the generated master password is valid.
|
||||||
/// It checks that it has at least one lowercase, one uppercase, one number and one punctuation char
|
/// It checks that it has at least one lowercase, one uppercase, one number and one punctuation char
|
||||||
#[inline]
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
fn check_generated_password<const LENGTH: usize>(password: &[u8; LENGTH]) -> bool {
|
fn check_generated_password<const LENGTH: usize>(password: &[u8; LENGTH]) -> bool {
|
||||||
let mut flags = PasswordFlags::empty();
|
let mut flags = PasswordFlags::empty();
|
||||||
for &byte in password {
|
for &byte in password {
|
||||||
@ -33,7 +34,7 @@ fn check_generated_password<const LENGTH: usize>(password: &[u8; LENGTH]) -> boo
|
|||||||
b'A'..=b'Z' => flags |= PasswordFlags::UPPERCASE,
|
b'A'..=b'Z' => flags |= PasswordFlags::UPPERCASE,
|
||||||
b'0'..=b'9' => flags |= PasswordFlags::NUMBER,
|
b'0'..=b'9' => flags |= PasswordFlags::NUMBER,
|
||||||
b'!'..=b'/' | b':'..=b'@' | b'['..=b'`' | b'{'..=b'~' => {
|
b'!'..=b'/' | b':'..=b'@' | b'['..=b'`' | b'{'..=b'~' => {
|
||||||
flags |= PasswordFlags::SPECIAL_CHARACTER
|
flags |= PasswordFlags::SPECIAL_CHARACTER;
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
@ -46,6 +47,7 @@ fn check_generated_password<const LENGTH: usize>(password: &[u8; LENGTH]) -> boo
|
|||||||
|
|
||||||
/// Continuously generates the password until it passes the checks
|
/// Continuously generates the password until it passes the checks
|
||||||
#[inline]
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
fn generate_password<R, const LENGTH: usize>(rng: &mut R) -> ArrayString<LENGTH>
|
fn generate_password<R, const LENGTH: usize>(rng: &mut R) -> ArrayString<LENGTH>
|
||||||
where
|
where
|
||||||
R: Rng + CryptoRng,
|
R: Rng + CryptoRng,
|
||||||
@ -59,6 +61,8 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
|
#[allow(clippy::module_name_repetitions)]
|
||||||
pub fn generate_passwords<const AMOUNT: usize, const LENGTH: usize>(
|
pub fn generate_passwords<const AMOUNT: usize, const LENGTH: usize>(
|
||||||
) -> [ArrayString<LENGTH>; AMOUNT] {
|
) -> [ArrayString<LENGTH>; AMOUNT] {
|
||||||
let mut rng = thread_rng();
|
let mut rng = thread_rng();
|
||||||
@ -66,6 +70,7 @@ pub fn generate_passwords<const AMOUNT: usize, const LENGTH: usize>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
#[must_use]
|
||||||
pub fn check_master_pass(password: &str) -> PasswordValidity {
|
pub fn check_master_pass(password: &str) -> PasswordValidity {
|
||||||
let mut count = 0;
|
let mut count = 0;
|
||||||
let mut chars = password.chars();
|
let mut chars = password.chars();
|
||||||
@ -74,13 +79,13 @@ pub fn check_master_pass(password: &str) -> PasswordValidity {
|
|||||||
for char in &mut chars {
|
for char in &mut chars {
|
||||||
count += 1;
|
count += 1;
|
||||||
if char.is_lowercase() {
|
if char.is_lowercase() {
|
||||||
flags.remove(PasswordValidity::NO_LOWERCASE)
|
flags.remove(PasswordValidity::NO_LOWERCASE);
|
||||||
} else if char.is_uppercase() {
|
} else if char.is_uppercase() {
|
||||||
flags.remove(PasswordValidity::NO_UPPERCASE)
|
flags.remove(PasswordValidity::NO_UPPERCASE);
|
||||||
} else if char.is_ascii_digit() {
|
} else if char.is_ascii_digit() {
|
||||||
flags.remove(PasswordValidity::NO_NUMBER)
|
flags.remove(PasswordValidity::NO_NUMBER);
|
||||||
} else if char.is_ascii_punctuation() {
|
} else if char.is_ascii_punctuation() {
|
||||||
flags.remove(PasswordValidity::NO_SPECIAL_CHARACTER)
|
flags.remove(PasswordValidity::NO_SPECIAL_CHARACTER);
|
||||||
}
|
}
|
||||||
|
|
||||||
if flags == PasswordValidity::TOO_SHORT {
|
if flags == PasswordValidity::TOO_SHORT {
|
||||||
@ -90,7 +95,7 @@ pub fn check_master_pass(password: &str) -> PasswordValidity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if count >= 8 {
|
if count >= 8 {
|
||||||
flags.remove(PasswordValidity::TOO_SHORT)
|
flags.remove(PasswordValidity::TOO_SHORT);
|
||||||
}
|
}
|
||||||
|
|
||||||
flags
|
flags
|
||||||
@ -102,6 +107,6 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn chars_must_be_ascii() {
|
fn chars_must_be_ascii() {
|
||||||
assert!(CHARS.is_ascii())
|
assert!(CHARS.is_ascii());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,2 +1,2 @@
|
|||||||
pub use crate::account::*;
|
pub use crate::account::{DecryptAccountExt as _, FromUnencryptedExt as _};
|
||||||
pub use crate::master_pass::*;
|
pub use crate::master_pass::FromUnencryptedExt as _;
|
||||||
|
@ -5,6 +5,9 @@ edition = "2021"
|
|||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
sea-orm = "0.12"
|
sea-orm = "0.12"
|
||||||
|
@ -56,7 +56,7 @@ impl Entity {
|
|||||||
#[inline]
|
#[inline]
|
||||||
pub async fn exists(
|
pub async fn exists(
|
||||||
user_id: u64,
|
user_id: u64,
|
||||||
account_name: impl Into<String>,
|
account_name: impl Into<String> + Send,
|
||||||
db: &DatabaseConnection,
|
db: &DatabaseConnection,
|
||||||
) -> crate::Result<bool> {
|
) -> crate::Result<bool> {
|
||||||
let count = Self::find_by_id((user_id, account_name.into()))
|
let count = Self::find_by_id((user_id, account_name.into()))
|
||||||
@ -69,7 +69,7 @@ impl Entity {
|
|||||||
#[inline]
|
#[inline]
|
||||||
pub async fn get(
|
pub async fn get(
|
||||||
user_id: u64,
|
user_id: u64,
|
||||||
account_name: impl Into<String>,
|
account_name: impl Into<String> + Send,
|
||||||
db: &DatabaseConnection,
|
db: &DatabaseConnection,
|
||||||
) -> crate::Result<Option<Model>> {
|
) -> crate::Result<Option<Model>> {
|
||||||
Self::find_by_id((user_id, account_name.into()))
|
Self::find_by_id((user_id, account_name.into()))
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
// This is fine, because all errors can only be caused by the database errors and the docs would get repetative very quickly
|
||||||
|
#![allow(clippy::missing_errors_doc)]
|
||||||
|
|
||||||
pub mod account;
|
pub mod account;
|
||||||
pub mod master_pass;
|
pub mod master_pass;
|
||||||
pub mod prelude;
|
pub mod prelude;
|
||||||
|
@ -3,6 +3,9 @@ name = "migration"
|
|||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
[dependencies.sea-orm-migration]
|
[dependencies.sea-orm-migration]
|
||||||
version = "0.12"
|
version = "0.12"
|
||||||
features = ["runtime-tokio-rustls", "sqlx-mysql"]
|
features = ["runtime-tokio-rustls", "sqlx-mysql"]
|
||||||
|
@ -1,6 +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 account::ActiveModel;
|
||||||
|
use cryptography::account::Cipher;
|
||||||
use futures::TryFutureExt;
|
use futures::TryFutureExt;
|
||||||
use sea_orm::ActiveValue::Set;
|
use sea_orm::ActiveValue::Set;
|
||||||
use tokio::{task::spawn_blocking, try_join};
|
use tokio::{task::spawn_blocking, try_join};
|
||||||
|
@ -12,15 +12,16 @@ pub async fn get_accounts(
|
|||||||
) -> 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).await?;
|
||||||
let mut text = match account_names.try_next().await? {
|
|
||||||
Some(name) => format!("Accounts:\n`{name}`"),
|
let mut text = if let Some(name) = account_names.try_next().await? {
|
||||||
None => {
|
format!("Accounts:\n`{name}`")
|
||||||
bot.send_message(msg.chat.id, "No accounts found")
|
} else {
|
||||||
.reply_markup(deletion_markup())
|
bot.send_message(msg.chat.id, "No accounts found")
|
||||||
.await?;
|
.reply_markup(deletion_markup())
|
||||||
return Ok(());
|
.await?;
|
||||||
}
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
account_names
|
account_names
|
||||||
.map_err(crate::Error::from)
|
.map_err(crate::Error::from)
|
||||||
.try_for_each(|name| {
|
.try_for_each(|name| {
|
||||||
@ -28,6 +29,7 @@ pub async fn get_accounts(
|
|||||||
future::ready(result)
|
future::ready(result)
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
bot.send_message(msg.chat.id, text)
|
bot.send_message(msg.chat.id, text)
|
||||||
.parse_mode(ParseMode::MarkdownV2)
|
.parse_mode(ParseMode::MarkdownV2)
|
||||||
.reply_markup(deletion_markup())
|
.reply_markup(deletion_markup())
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
#![warn(clippy::pedantic, clippy::all, clippy::nursery)]
|
// #![warn(clippy::pedantic, clippy::all, clippy::nursery)]
|
||||||
#![allow(clippy::single_match_else)]
|
// #![allow(clippy::single_match_else)]
|
||||||
|
|
||||||
mod callbacks;
|
mod callbacks;
|
||||||
mod commands;
|
mod commands;
|
||||||
|
@ -26,15 +26,12 @@ where
|
|||||||
return Err(HandlerUsed.into());
|
return Err(HandlerUsed.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let text = match msg.text() {
|
let Some(text) = msg.text().map(str::trim) else {
|
||||||
Some(text) => text.trim(),
|
handler
|
||||||
None => {
|
.previous
|
||||||
handler
|
.alter_message(&bot, no_text_message, None, None)
|
||||||
.previous
|
.await?;
|
||||||
.alter_message(&bot, no_text_message, None, None)
|
return Ok(());
|
||||||
.await?;
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if text == "/cancel" {
|
if text == "/cancel" {
|
||||||
|
@ -11,21 +11,17 @@ async fn check_master_pass(
|
|||||||
master_pass: &str,
|
master_pass: &str,
|
||||||
) -> crate::Result<Option<String>> {
|
) -> crate::Result<Option<String>> {
|
||||||
let user_id = msg.from().ok_or(NoUserInfo)?.id.0;
|
let user_id = msg.from().ok_or(NoUserInfo)?.id.0;
|
||||||
let model = MasterPass::get(user_id, db).await?;
|
let Some(model) = MasterPass::get(user_id, db).await? else {
|
||||||
|
error!("User was put into the GetMasterPass state with no master password set");
|
||||||
|
return Ok(Some(
|
||||||
|
"No master password set. Use /cancel and set it by using /set_master_pass".to_owned(),
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
let is_valid = match model {
|
let is_valid = {
|
||||||
Some(model) => {
|
let hash = HashedBytes::from(model);
|
||||||
let hash: HashedBytes<_, _> = model.into();
|
let master_pass = master_pass.to_owned();
|
||||||
let master_pass = master_pass.to_owned();
|
spawn_blocking(move || hash.verify(master_pass.as_bytes())).await?
|
||||||
spawn_blocking(move || hash.verify(master_pass.as_bytes())).await?
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
error!("User was put into the GetMasterPass state with no master password set");
|
|
||||||
return Ok(Some(
|
|
||||||
"No master password set. Use /cancel and set it by using /set_master_pass"
|
|
||||||
.to_owned(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if !is_valid {
|
if !is_valid {
|
||||||
|
Loading…
Reference in New Issue
Block a user