/* * This file is part of laurelin/api * Copyright (C) 2023 Jonni Liljamo * * Licensed under GPL-3.0-only. * See LICENSE for licensing information. */ use argon2::{ password_hash::{rand_core::OsRng, SaltString}, Argon2, PasswordHasher, }; use diesel::{ExpressionMethods, PgConnection, QueryDsl, RunQueryDsl}; use laurelin_schema::schema::users; use laurelin_shared::{ error::api::APIError, types::user::{InsertableUser, User}, }; pub(crate) fn create(conn: &mut PgConnection, user: &InsertableUser) -> Result { // NOTE: password hashing is expensive, a query at the start is about 170 times faster, // compared to hashing the password and finding out the user exists and erroring out on the insert // (according to quick maths and like 5 tests) if users::table .filter(users::email.eq(&user.email)) .first::(conn) .is_ok() { return Err(APIError::UserCreationFailed); } let salt = SaltString::generate(&mut OsRng); let password_hash_result = Argon2::default().hash_password(user.password.as_bytes(), &salt); let password_hash = match password_hash_result { Err(_) => return Err(APIError::UserPasswordHashFailed), Ok(password_hash) => password_hash.to_string(), }; let new_user = InsertableUser { username: user.username.clone(), email: user.email.clone(), password: password_hash, }; let user: Result = diesel::insert_into(users::table) .values(&new_user) .get_result::(conn); match user { Err(_) => { // TODO: we certainly could handle Diesel errors here... Err(APIError::UserCreationFailed) } Ok(u) => Ok(u), } }