2022-10-04 11:06:48 +02:00
|
|
|
use argon2::{password_hash::SaltString, Argon2, PasswordHash, PasswordHasher, PasswordVerifier};
|
|
|
|
use axum::async_trait;
|
2022-10-03 23:00:31 +02:00
|
|
|
use como_core::users::UserService;
|
2023-06-04 11:02:51 +02:00
|
|
|
use como_domain::Context;
|
2022-10-04 11:06:48 +02:00
|
|
|
use rand_core::OsRng;
|
2022-10-03 23:00:31 +02:00
|
|
|
|
2022-10-04 11:06:48 +02:00
|
|
|
use crate::database::ConnectionPool;
|
|
|
|
|
|
|
|
pub struct DefaultUserService {
|
|
|
|
pool: ConnectionPool,
|
|
|
|
}
|
2022-10-03 23:00:31 +02:00
|
|
|
|
|
|
|
impl DefaultUserService {
|
2022-10-04 11:06:48 +02:00
|
|
|
pub fn new(pool: ConnectionPool) -> Self {
|
|
|
|
Self { pool }
|
|
|
|
}
|
|
|
|
|
2023-06-04 11:02:51 +02:00
|
|
|
fn hash_password(&self, _context: &Context, password: String) -> anyhow::Result<String> {
|
2022-10-04 11:06:48 +02:00
|
|
|
let salt = SaltString::generate(&mut OsRng);
|
|
|
|
let argon2 = Argon2::default();
|
|
|
|
|
|
|
|
let password_hash = argon2
|
|
|
|
.hash_password(password.as_bytes(), &salt)
|
|
|
|
.map_err(|e| anyhow::anyhow!(e))?
|
|
|
|
.to_string();
|
|
|
|
|
|
|
|
Ok(password_hash)
|
|
|
|
}
|
|
|
|
|
2023-06-04 11:02:51 +02:00
|
|
|
fn validate_password(
|
|
|
|
&self,
|
|
|
|
_context: &Context,
|
|
|
|
password: String,
|
|
|
|
hashed_password: String,
|
|
|
|
) -> anyhow::Result<bool> {
|
2022-10-04 11:06:48 +02:00
|
|
|
let argon2 = Argon2::default();
|
|
|
|
|
|
|
|
let parsed_hash = PasswordHash::new(&hashed_password).map_err(|e| anyhow::anyhow!(e))?;
|
|
|
|
match argon2.verify_password(password.as_bytes(), &parsed_hash) {
|
|
|
|
Ok(..) => Ok(true),
|
|
|
|
Err(..) => Ok(false),
|
|
|
|
}
|
2022-10-03 23:00:31 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-04 11:06:48 +02:00
|
|
|
#[async_trait]
|
|
|
|
impl UserService for DefaultUserService {
|
2023-06-04 11:02:51 +02:00
|
|
|
async fn add_user(
|
|
|
|
&self,
|
|
|
|
context: &Context,
|
|
|
|
username: String,
|
|
|
|
password: String,
|
|
|
|
) -> anyhow::Result<String> {
|
|
|
|
let hashed_password = self.hash_password(context, password)?;
|
2022-10-04 11:06:48 +02:00
|
|
|
|
|
|
|
let rec = sqlx::query!(
|
|
|
|
r#"
|
|
|
|
INSERT INTO users (username, password_hash)
|
|
|
|
VALUES ( $1, $2 )
|
|
|
|
RETURNING id
|
|
|
|
"#,
|
|
|
|
username,
|
|
|
|
hashed_password
|
|
|
|
)
|
|
|
|
.fetch_one(&self.pool)
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
Ok(rec.id.to_string())
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn validate_user(
|
|
|
|
&self,
|
2023-06-04 11:02:51 +02:00
|
|
|
context: &Context,
|
2022-10-04 11:06:48 +02:00
|
|
|
username: String,
|
|
|
|
password: String,
|
|
|
|
) -> anyhow::Result<Option<String>> {
|
|
|
|
let rec = sqlx::query!(
|
|
|
|
r#"
|
|
|
|
SELECT * from users
|
|
|
|
where username=$1
|
|
|
|
"#,
|
|
|
|
username,
|
|
|
|
)
|
|
|
|
.fetch_optional(&self.pool)
|
|
|
|
.await?;
|
|
|
|
|
|
|
|
match rec {
|
2023-06-04 11:02:51 +02:00
|
|
|
Some(user) => match self.validate_password(context, password, user.password_hash)? {
|
2022-10-04 11:06:48 +02:00
|
|
|
true => Ok(Some(user.id.to_string())),
|
|
|
|
false => Ok(None),
|
|
|
|
},
|
|
|
|
None => Ok(None),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|