DEVELOPMENT ENVIRONMENT

~liljamo/deck-builder

ref: 82c3e0c9e677e9ed01e723882d7175079841a8f4 deck-builder/api/src/actions/user/create.rs -rw-r--r-- 1.8 KiB
82c3e0c9Jonni Liljamo feat(api): login password verification 1 year, 7 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/*
 * 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_shared::error::api::APIError;

use crate::{
    models::{InsertableUser, User},
    schema::users,
};

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...
            return Err(APIError::UserCreationFailed);
        }
        Ok(u) => return Ok(u),
    }
}