api/src/accounts.rs

109 lines
3.3 KiB
Rust

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<Self> {
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<Option<Self>> {
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<Option<Self>> {
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<Option<Self>> {
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<bool> {
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),
}
}
}