use anyhow::{Error, Result}; use pbkdf2::{ password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString}, Pbkdf2, }; use sqlx::{mysql::MySqlPool, types::chrono as sqlx_chrono}; #[derive(Debug)] pub struct Account { pub id: u64, pub username: String, pub email: String, pub salt: String, pub password: String, pub joined: sqlx_chrono::NaiveDateTime, pub verified: bool, } impl Account { /// This doesn't check if an account with that name is already existing! pub async fn new( pool: &MySqlPool, username: &String, email: &String, password: &String, ) -> Result { let salt = SaltString::generate(&mut OsRng); let hash = Pbkdf2 .hash_password(password.as_bytes(), &salt) .map_err(|_| anyhow::Error::msg("Failed to hash the password"))? .to_string(); let joined = sqlx_chrono::Utc::now().naive_utc(); sqlx::query!( r#"INSERT INTO Accounts (username, email, salt, password, joined, verified) VALUES (?, ?, ?, ?, ?, false);"#, username, email, salt.to_string(), hash, joined, ) .execute(pool) .await?; match Account::from_username(pool, &username).await? { Some(a) => Ok(a), None => Err(Error::msg( "The just created account can't be found in the database!", )), } } pub async fn from_username(pool: &MySqlPool, username: &String) -> Result> { match sqlx::query_as!( Self, r#"SELECT id, username, email, salt, password, joined, verified as `verified: bool` FROM Accounts WHERE username = ?;"#, username ) .fetch_one(pool) .await { Ok(account) => Ok(Some(account)), Err(sqlx::Error::RowNotFound) => Ok(None), Err(e) => Err(Error::new(e)), } } pub async fn from_id(pool: &MySqlPool, id: u64) -> Result> { match sqlx::query_as!( Self, r#"SELECT id, username, email, salt, password, joined, verified as `verified: bool` FROM Accounts WHERE id = ?;"#, id ) .fetch_one(pool) .await { Ok(account) => Ok(Some(account)), Err(sqlx::Error::RowNotFound) => Ok(None), Err(e) => Err(Error::new(e)), } } pub async fn from_email(pool: &MySqlPool, email: &String) -> Result> { match sqlx::query_as!( Self, r#"SELECT id, username, email, salt, password, joined, verified as `verified: bool` FROM Accounts WHERE email = ?;"#, email ) .fetch_one(pool) .await { Ok(account) => Ok(Some(account)), Err(sqlx::Error::RowNotFound) => Ok(None), Err(e) => Err(Error::new(e)), } } pub async fn check_password(&self, password: String) -> Result { let hash = PasswordHash::new(self.password.as_str()) .map_err(|_| anyhow::Error::msg("Failed to parse the password hash"))?; match Pbkdf2.verify_password(password.as_bytes(), &hash) { Ok(_) => Ok(true), Err(_) => Ok(false), } } }