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}; 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!(r#"SELECT * FROM Accounts WHERE username = ?;"#, username) .fetch_one(pool) .await { Ok(row) => { let account = Account { id: row.id, username: row.username, email: row.email, salt: row.salt, password: row.password, joined: row.joined, verified: row.verified != 0, }; 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!(r#"SELECT * FROM Accounts WHERE id = ?;"#, id) .fetch_one(pool) .await { Ok(row) => { let account = Account { id: row.id, username: row.username, email: row.email, salt: row.salt, password: row.password, joined: row.joined, verified: row.verified != 0, }; 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!(r#"SELECT * FROM Accounts WHERE email = ?;"#, email) .fetch_one(pool) .await { Ok(row) => { let account = Account { id: row.id, username: row.username, email: row.email, salt: row.salt, password: row.password, joined: row.joined, verified: row.verified != 0, }; Ok(Some(account)) } Err(sqlx::Error::RowNotFound) => Ok(None), Err(e) => Err(Error::new(e)), } } }