109 lines
3.3 KiB
Rust
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),
|
|
}
|
|
}
|
|
}
|