restapi/requests/user/
create_user.rs

1//! Module for creating a user
2//!
3//! ## Create User
4//!
5//! Create a single ``users`` record for the new user
6//!
7//! - URL path: ``/user``
8//! - Method: ``POST``
9//! - Handler: [`create_user`](crate::requests::user::create_user::create_user)
10//! - Request: [`ApiReqUserCreate`](crate::requests::user::create_user::ApiReqUserCreate)
11//! - Response: [`ApiResUserCreate`](crate::requests::user::create_user::ApiResUserCreate)
12//!
13
14use std::convert::Infallible;
15
16use postgres_native_tls::MakeTlsConnector;
17
18use bb8::Pool;
19use bb8_postgres::PostgresConnectionManager;
20
21use hyper::Body;
22use hyper::Response;
23
24use serde::Deserialize;
25use serde::Serialize;
26
27use argon2::hash_encoded as argon_hash_encoded;
28use argon2::Config as argon_config;
29
30use kafka_threadpool::kafka_publisher::KafkaPublisher;
31
32use crate::core::core_config::CoreConfig;
33use crate::kafka::publish_msg::publish_msg;
34use crate::requests::auth::create_user_token::create_user_token;
35use crate::requests::auth::login_user::ApiResUserLogin;
36use crate::requests::user::is_verification_enabled::is_verification_enabled;
37use crate::requests::user::upsert_user_verification::upsert_user_verification;
38use crate::utils::get_server_address::get_server_address;
39
40/// ApiReqUserCreate
41///
42/// # Request Type For create_user
43///
44/// Create a new user in the db
45///
46/// This type is the deserialized input for:
47/// [`create_user`](crate::requests::user::create_user::create_user]
48///
49/// # Usage
50///
51/// This type is constructed from the deserialized
52/// `bytes` (`&[u8]`) argument
53/// on the
54/// [`create_user`](crate::requests::user::create_user::create_user)
55/// function.
56///
57/// # Arguments
58///
59/// * `email` - `String` - user email
60/// * `password` - `String` - new user password
61///
62#[derive(Serialize, Deserialize, Clone)]
63pub struct ApiReqUserCreate {
64    pub email: String,
65    pub password: String,
66}
67
68/// ApiResUserCreate
69///
70/// # Response type for create_user
71///
72/// Return users's db record with encrypted jwt
73///
74/// # Usage
75///
76/// This type is the serialized output for the function:
77/// [`create_user`](crate::requests::user::create_user::create_user]
78/// and contained within the
79/// hyper [`Body`](hyper::Body)
80/// of the
81/// hyper [`Response`](hyper::Response)
82/// sent back to the client.
83///
84/// # Arguments
85///
86/// * `user_id` - `i32` - user id
87/// * `email` - `String` - user email
88/// * `state` - `i32` - user state where
89///   (`0` - active, `1` - inactive)
90/// * `role` - `String` - user role
91/// * `token` - `String` - user jwt
92/// * `msg` - `String` - help message
93///
94#[derive(Serialize, Deserialize, Default, Clone)]
95pub struct ApiResUserCreate {
96    pub user_id: i32,
97    pub email: String,
98    pub state: i32,
99    pub role: String,
100    pub token: String,
101    pub msg: String,
102}
103
104/// create_user
105///
106/// Create a new user from the deserialized
107/// [`ApiReqUserCreate`](crate::requests::user::create_user::ApiReqUserCreate)
108/// json values from the `bytes` argument.
109///
110/// Also create a new user jwt and
111/// email verification record (if enabled).
112///
113/// # Arguments
114///
115/// * `tracking_label` - `&str` - caller logging label
116/// * `config` - [`CoreConfig`](crate::core::core_config::CoreConfig)
117/// * `db_pool` - [`Pool`](bb8::Pool) - postgres client
118///   db threadpool with required tls encryption
119/// * `kafka_pool` -
120///   [`KafkaPublisher`](kafka_threadpool::kafka_publisher::KafkaPublisher)
121///   for asynchronously publishing messages to the connected kafka cluster
122/// * `bytes` - `&[u8]` - received bytes from the hyper
123///   [`Request`](hyper::Request)'s [`Body`](hyper::Body)
124///
125/// # Returns
126///
127/// ## create_user on Success Returns
128///
129/// The new user record from the db and a jwt for auto-auth.
130/// (token created by
131/// [`create_user_token`](crate::requests::auth::create_user_token::create_user_token)
132/// )
133///
134/// hyper [`Response`](hyper::Response)
135/// containing a json-serialized
136/// [`ApiResUserCreate`](crate::requests::user::create_user::ApiResUserCreate)
137/// dictionary within the
138/// [`Body`](hyper::Body) and a
139/// `201` HTTP status code
140///
141/// Ok([`Response`](hyper::Response))
142///
143/// # Errors
144///
145/// ## create_user on Failure Returns
146///
147/// All errors return as a
148/// hyper [`Response`](hyper::Response)
149/// containing a json-serialized
150/// [`ApiResUserCreate`](crate::requests::user::create_user::ApiResUserCreate)
151/// dictionary with a
152/// `non-201` HTTP status code
153///
154/// Err([`Response`](hyper::Response))
155///
156pub async fn create_user(
157    tracking_label: &str,
158    config: &CoreConfig,
159    db_pool: &Pool<PostgresConnectionManager<MakeTlsConnector>>,
160    kafka_pool: &KafkaPublisher,
161    bytes: &[u8],
162) -> std::result::Result<Response<Body>, Infallible> {
163    let user_object: ApiReqUserCreate = serde_json::from_slice(bytes).unwrap();
164
165    if user_object.password.len() < 4 {
166        let response = Response::builder()
167            .status(400)
168            .body(Body::from(
169                serde_json::to_string(&ApiResUserCreate {
170                    user_id: -1,
171                    email: "".to_string(),
172                    state: -1,
173                    role: "".to_string(),
174                    token: "".to_string(),
175                    msg: ("User password must be more than 4 characters")
176                        .to_string(),
177                })
178                .unwrap(),
179            ))
180            .unwrap();
181        return Ok(response);
182    }
183
184    let mut user_role = "user";
185    if user_object.email == "admin@email.com" {
186        user_role = "admin";
187    }
188
189    let user_verification_enabled = is_verification_enabled();
190    let user_start_state_value = 0;
191    let user_verified_value = match user_verification_enabled {
192        true => 0,
193        false => 1,
194    };
195
196    // salt the user's password
197    let argon_config = argon_config::default();
198    let hash = argon_hash_encoded(
199        user_object.password.as_bytes(),
200        &config.server_password_salt,
201        &argon_config,
202    )
203    .unwrap();
204
205    let insert_query = format!(
206        "INSERT INTO \
207            users (\
208                email, \
209                password, \
210                state, \
211                verified, \
212                role) \
213        VALUES (\
214            '{}', \
215            '{hash}', \
216            {user_start_state_value}, \
217            {user_verified_value}, \
218            '{user_role}') \
219        RETURNING \
220            users.id, \
221            users.email, \
222            users.password, \
223            users.state, \
224            users.verified, \
225            users.role;",
226        user_object.email
227    );
228    let conn = db_pool.get().await.unwrap();
229    let stmt = conn.prepare(&insert_query).await.unwrap();
230    let query_result = match conn.query(&stmt, &[]).await {
231        Ok(query_result) => query_result,
232        Err(e) => {
233            let err_msg = format!("{e}");
234            if err_msg.contains("duplicate key value violates") {
235                let response = Response::builder()
236                    .status(400)
237                    .body(Body::from(
238                        serde_json::to_string(&ApiResUserCreate {
239                            user_id: -1,
240                            email: "".to_string(),
241                            state: -1,
242                            role: "".to_string(),
243                            token: "".to_string(),
244                            msg: format!(
245                                "User email {} already registered",
246                                user_object.email
247                            ),
248                        })
249                        .unwrap(),
250                    ))
251                    .unwrap();
252                return Ok(response);
253            } else {
254                let response = Response::builder()
255                    .status(500)
256                    .body(Body::from(
257                        serde_json::to_string(
258                            &ApiResUserCreate {
259                                user_id: -1,
260                                email: "".to_string(),
261                                state: -1,
262                                role: "".to_string(),
263                                token: "".to_string(),
264                                msg: format!(
265                                    "User creation failed for email={} with err='{err_msg}'",
266                                        user_object.email)
267                        }).unwrap()))
268                    .unwrap();
269                return Ok(response);
270            }
271        }
272    };
273
274    let mut row_list: Vec<(i32, String, String, i32, i32, String)> =
275        Vec::with_capacity(1);
276    for row in query_result.iter() {
277        let id: i32 = row.try_get("id").unwrap();
278        let email: String = row.try_get("email").unwrap();
279        let password: String = row.try_get("password").unwrap();
280        if password != hash {
281            error!("BAD PASSWORD FOUND DURING USER CREATION:\npassword=\n{password}\n!=\nsalt=\n{hash}");
282            let response = Response::builder()
283                .status(400)
284                .body(Body::from(
285                    serde_json::to_string(&ApiResUserLogin {
286                        user_id: -1,
287                        email: "".to_string(),
288                        state: -1,
289                        verified: -1,
290                        role: "".to_string(),
291                        token: "".to_string(),
292                        msg: ("User login failed - invalid password")
293                            .to_string(),
294                    })
295                    .unwrap(),
296                ))
297                .unwrap();
298            return Ok(response);
299        }
300        let user_state: i32 = row.try_get("state").unwrap();
301        let user_verified_db: i32 = row.try_get("verified").unwrap();
302        let role: String = row.try_get("role").unwrap();
303        row_list.push((id, email, password, user_state, user_verified_db, role))
304    }
305    if row_list.is_empty() {
306        let response = Response::builder()
307            .status(400)
308            .body(Body::from(
309                serde_json::to_string(
310                    &ApiResUserLogin {
311                        user_id: -1,
312                        email: "".to_string(),
313                        state: -1,
314                        verified: -1,
315                        role: "".to_string(),
316                        token: "".to_string(),
317                        msg: format!(
318                            "User creation failed - user does not exist with email={}",
319                                user_object.email)
320                    }
321                ).unwrap()))
322            .unwrap();
323        Ok(response)
324    } else {
325        let user_id = row_list[0].0;
326        let user_email = row_list[0].1.clone();
327        let user_token = match create_user_token(
328            tracking_label,
329            config,
330            &conn,
331            &user_email,
332            user_id,
333        )
334        .await
335        {
336            Ok(user_token) => user_token,
337            Err(_) => {
338                let response = Response::builder()
339                    .status(500)
340                    .body(Body::from(
341                        serde_json::to_string(
342                            &ApiResUserLogin {
343                                user_id: -1,
344                                email: "".to_string(),
345                                state: -1,
346                                verified: -1,
347                                role: "".to_string(),
348                                token: "".to_string(),
349                                msg: format!("User token creation failed - {user_id} {user_email}"),
350                            }
351                        ).unwrap()))
352                    .unwrap();
353                return Ok(response);
354            }
355        };
356        if user_verification_enabled {
357            match upsert_user_verification(
358                tracking_label,
359                user_id,
360                &user_email,
361                true, // is new user flag
362                0,    // not verified
363                &conn,
364            )
365            .await
366            {
367                Ok(verification_token) => {
368                    info!(
369                        "{tracking_label} - verify token created user={user_id} \
370                        {user_email} - verify url:\
371                        curl -ks \
372                        \"https://{}/user/verify?u={user_id}&t={verification_token}\" \
373                        | jq",
374                            get_server_address("api"));
375                }
376                Err(e) => {
377                    error!(
378                        "{tracking_label} - \
379                        failed to generate verify token for user {user_id} \
380                        {user_email} with err='{e}'"
381                    );
382                }
383            };
384        }
385
386        // if enabled, publish to kafka
387        if config.kafka_publish_events {
388            publish_msg(
389                kafka_pool,
390                // topic
391                "user.events",
392                // partition key
393                &format!("user-{}", user_id),
394                // optional headers stored in: Option<HashMap<String, String>>
395                None,
396                // payload in the message
397                &format!("USER_CREATE user={user_id} email={user_email}"),
398            )
399            .await;
400        }
401
402        let response = Response::builder()
403            .status(201)
404            .body(Body::from(
405                serde_json::to_string(&ApiResUserLogin {
406                    user_id,
407                    email: user_email,
408                    state: row_list[0].3,
409                    verified: row_list[0].4,
410                    role: row_list[0].5.clone(),
411                    token: user_token,
412                    msg: "success".to_string(),
413                })
414                .unwrap(),
415            ))
416            .unwrap();
417        Ok(response)
418    }
419}