use argon2::password_hash::Salt; use diesel::Connection; use serde::{Serialize, Deserialize}; use uuid::Uuid; use diesel::{ExpressionMethods, QueryDsl, RunQueryDsl, Insertable, Queryable}; use diesel_derive_enum::DbEnum; use argon2::password_hash::SaltString; use argon2::PasswordHash; use argon2::PasswordVerifier; use argon2::Argon2; use argon2::password_hash::PasswordHasher; use argon2::password_hash::rand_core::OsRng; use argon2::password_hash::rand_core::RngCore; use crate::DbConnection; use super::schema::{pwd, users}; pub use super::error::DatabaseError; #[derive(Serialize, Deserialize, Debug, Insertable, Queryable)] #[diesel(table_name = pwd)] struct Pwd { user_id: Uuid, password: String, } #[derive(Serialize, Deserialize, Debug, Insertable, Queryable)] #[diesel(table_name = users)] pub struct User { pub id: Uuid, pub username: String, pub email: String, pub role: Role, } #[derive(DbEnum, Debug, Serialize, Deserialize, Clone, Copy, PartialEq)] #[ExistingTypePath = "crate::schema::sql_types::Role"] pub enum Role { Admin, User } pub struct LoginUser { pub username: String, pub password: String } #[derive(Serialize, Deserialize)] pub struct LoginResult { pub result: bool, pub user: Option, } #[derive(Serialize, Deserialize)] pub struct RegisterResult { pub result: bool, } #[derive(Serialize, Deserialize)] pub struct Register { pub username: String, pub email: String, pub password: String } pub fn login(conn: &mut DbConnection, user: LoginUser) -> Result, DatabaseError> { let id: Uuid = users::table .filter(users::username.eq(&user.username)) .or_filter(users::email.eq(&user.username)) .select(users::id) .first(conn)?; let pwd: String = pwd::table .filter(pwd::user_id.eq(id)) .select(pwd::password) .first(conn)?; let parsed_hash = PasswordHash::new(&pwd)?; if Argon2::default() .verify_password(user.password.as_bytes(), &parsed_hash) .is_ok() { Ok(Some(users::table.find(id).first(conn)?)) } else { Ok(None) } } pub fn get_user(conn: &mut DbConnection, id: Uuid) -> Result { Ok(users::table.find(id).first(conn)?) } pub fn register(conn: &mut DbConnection, register: Register) -> Result<(), DatabaseError> { let mut bytes = [0u8; Salt::RECOMMENDED_LENGTH]; OsRng.try_fill_bytes(&mut bytes).unwrap(); let salt = SaltString::encode_b64(&bytes).unwrap(); let argon2 = Argon2::default(); let password_hash = argon2 .hash_password(register.password.as_bytes(), &salt)? .to_string(); conn.transaction(|c| { let id = Uuid::new_v4(); diesel::insert_into(users::table) .values(User { id, username: register.username, email: register.email, role: Role::User, }) .execute(c)?; diesel::insert_into(pwd::table) .values(Pwd { user_id: id, password: password_hash, }) .execute(c)?; Ok(()) }) } pub fn count_users_with_username(conn: &mut DbConnection, username: &String) -> Result { Ok(users::table .count() .filter(users::username.eq(username)) .get_result::(conn)?) } pub fn count_users_with_email(conn: &mut DbConnection, email: &String) -> Result { Ok(users::table .count() .filter(users::email.eq(email)) .get_result::(conn)?) }