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}