/*
* This file is part of laurelin/api
* Copyright (C) 2023 Jonni Liljamo <jonni@liljamo.com>
*
* 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<User, APIError> {
// 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::<User>(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<User, diesel::result::Error> = diesel::insert_into(users::table)
.values(&new_user)
.get_result::<User>(conn);
match user {
Err(_) => {
// TODO: we certainly could handle Diesel errors here...
Err(APIError::UserCreationFailed)
}
Ok(u) => Ok(u),
}
}