use argon2::password_hash::Salt; use diesel::Connection; use serde::{Serialize, Deserialize}; use uuid::Uuid; use diesel::{PgConnection, 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 validator::ValidationError; use crate::util::GetConnection; use super::schema::{pwd, users}; pub use super::error::DatabaseError; use ::rand_core::{OsRng,TryRngCore}; use crate::request::requests::RegisterContext; #[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::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 PgConnection, 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 PgConnection, id: Uuid) -> Result { Ok(users::table.find(id).first(conn)?) } pub fn unique_username(username: &String, context: &RegisterContext) -> Result<(), ValidationError> { let mut conn = context.pool.get().expect("Couldn't get db connection from pool"); match users::table .count() .filter(users::username.eq(username)) .get_result(&mut conn) { Ok(0) => Ok(()), Ok(_) => Err(ValidationError::new("User already exists")), Err(_) => Err(ValidationError::new("Database error while validating user")), } } pub fn unique_email(email: &String, context: &RegisterContext) -> Result<(), ValidationError> { let mut conn = context.pool.get_conn(); match users::table .count() .filter(users::email.eq(email)) .get_result(&mut conn) { Ok(0) => Ok(()), Ok(_) => Err(ValidationError::new("email already exists")), Err(_) => Err(ValidationError::new( "Database error while validating email", )), } } pub fn register(conn: &mut PgConnection, 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(()) }) }