From b832b02a2a9f49248a3eb8ea964ac321d9314d4e Mon Sep 17 00:00:00 2001 From: franklinblanco Date: Tue, 28 Jun 2022 00:46:00 -0400 Subject: [PATCH] Perfected the create user method, added todo's, started working on token stuff. --- migrations/20220627132425_userup.sql.sql | 4 +- migrations/20220627132445_tokenup.sql.sql | 4 +- sql/schema/user/find.sql | 0 sql/schema/user/find_with_email.sql | 3 ++ src/dao/token_dao.rs | 4 +- src/dao/user_dao.rs | 3 ++ src/resources/error_messages.rs | 6 ++- src/routes/user_routes.rs | 51 +++++++++++++++++++---- src/util/hasher.rs | 6 ++- src/validation/user_validator.rs | 10 ++++- 10 files changed, 74 insertions(+), 17 deletions(-) delete mode 100644 sql/schema/user/find.sql create mode 100644 sql/schema/user/find_with_email.sql diff --git a/migrations/20220627132425_userup.sql.sql b/migrations/20220627132425_userup.sql.sql index ca5748e..d1fd45c 100644 --- a/migrations/20220627132425_userup.sql.sql +++ b/migrations/20220627132425_userup.sql.sql @@ -1,7 +1,7 @@ CREATE TABLE IF NOT EXISTS user ( id INT AUTO_INCREMENT PRIMARY KEY, - time_created TIMESTAMP NOT NULL, - last_updated TIMESTAMP NOT NULL, + time_created DATETIME, + last_updated DATETIME, email VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, password TEXT NOT NULL, diff --git a/migrations/20220627132445_tokenup.sql.sql b/migrations/20220627132445_tokenup.sql.sql index 48578aa..31e5157 100644 --- a/migrations/20220627132445_tokenup.sql.sql +++ b/migrations/20220627132445_tokenup.sql.sql @@ -1,8 +1,8 @@ CREATE TABLE IF NOT EXISTS token ( id INT AUTO_INCREMENT PRIMARY KEY, user_id INT NOT NULL, - time_created TIMESTAMP NOT NULL, - last_updated TIMESTAMP NOT NULL, + time_created DATETIME, + last_updated DATETIME, auth_token TEXT NOT NULL, refresh_token TEXT NOT NULL ) \ No newline at end of file diff --git a/sql/schema/user/find.sql b/sql/schema/user/find.sql deleted file mode 100644 index e69de29..0000000 diff --git a/sql/schema/user/find_with_email.sql b/sql/schema/user/find_with_email.sql new file mode 100644 index 0000000..1d14774 --- /dev/null +++ b/sql/schema/user/find_with_email.sql @@ -0,0 +1,3 @@ +SELECT * +FROM user +WHERE user.email = ? \ No newline at end of file diff --git a/src/dao/token_dao.rs b/src/dao/token_dao.rs index 9e8f611..7c2ec51 100644 --- a/src/dao/token_dao.rs +++ b/src/dao/token_dao.rs @@ -4,4 +4,6 @@ use crate::r#do::token::Token; pub async fn insert_token(conn: &mut MySqlConnection, token: &Token) -> Result { sqlx::query_file!("sql/schema/token/insert.sql", token.user_id, token.auth_token, token.refresh_token).execute(conn).await -} \ No newline at end of file +} +// TODO: Get token with auth token +// TODO: Get token with user_id \ No newline at end of file diff --git a/src/dao/user_dao.rs b/src/dao/user_dao.rs index fbc6165..72c266f 100644 --- a/src/dao/user_dao.rs +++ b/src/dao/user_dao.rs @@ -7,6 +7,9 @@ pub async fn insert_user(conn: &mut MySqlConnection, user_to_insert: &User) -> R user_to_insert.email, user_to_insert.name, user_to_insert.password, user_to_insert.salt) .execute(conn).await } +pub async fn find_user_by_email(conn: &mut MySqlConnection, email: &String) -> Result{ + sqlx::query_file_as!(User, "sql/schema/user/find_with_email.sql", email).fetch_one(conn).await +} //pub async fn _update_user(conn: &mut MySqlConnection, user_to_modify: &User) -> Result<(), sqlx::Error>{ // Ok(()) //} diff --git a/src/resources/error_messages.rs b/src/resources/error_messages.rs index c5690b8..1b5bb7e 100644 --- a/src/resources/error_messages.rs +++ b/src/resources/error_messages.rs @@ -5,4 +5,8 @@ pub const ERROR_INVALID_EMAIL: (&str, &str) = ("ERROR.INVALID_EMAIL", "Invalid e pub const ERROR_INVALID_NAME: (&str, &str) = ("ERROR.INVALID_NAME", "Invalid name. Names should have at least 4 characters in length and at most 254."); -pub const ERROR_INVALID_PASSWORD: (&str, &str) = ("ERROR.INVALID_PASSWORD", "Invalid password. Password should have at least 8 characters and at most 128."); \ No newline at end of file +pub const ERROR_INVALID_PASSWORD: (&str, &str) = ("ERROR.INVALID_PASSWORD", "Invalid password. Password should have at least 8 characters and at most 128."); + +pub const ERROR_USER_ALREADY_EXISTS: (&str, &str) = ("ERROR.USER_ALREADY_EXISTS", "A user with that email already exists."); + +pub const ERROR_USER_DOES_NOT_EXIST: (&str, &str) = ("ERROR.USER_DOES_NOT_EXIST", "User with this email does not exist."); \ No newline at end of file diff --git a/src/routes/user_routes.rs b/src/routes/user_routes.rs index bda5d57..4388652 100644 --- a/src/routes/user_routes.rs +++ b/src/routes/user_routes.rs @@ -1,11 +1,12 @@ +use core::panic; use std::sync::Mutex; use actix_web::{web::{self, Data}, HttpResponse, post}; use sqlx::MySqlConnection; -use crate::{r#do::user::User, dao::{user_dao::{insert_user}, token_dao::insert_token}, dto::{user_dtos::UserForCreationDto, message_resources_dtos::MessageResourceDto}, validation::user_validator, util::hasher, r#do::token::Token}; +use crate::{r#do::user::User, dao::{user_dao::{insert_user, find_user_by_email}, token_dao::insert_token}, dto::{user_dtos::{UserForCreationDto, UserForLoginDto}, message_resources_dtos::MessageResourceDto}, validation::user_validator, util::hasher, r#do::token::Token, resources::error_messages::{ERROR_USER_ALREADY_EXISTS, ERROR_USER_DOES_NOT_EXIST}}; -/*#[get("/user/{id}")] +/*#[get("/u&ser/{id}")] pub async fn get_user_from_db(id: Path, db_conn: Data>) -> HttpResponse { match find_user_by_id(&mut db_conn.lock().unwrap(), *id).await{ Ok(MySqlQueryResult) @@ -28,6 +29,15 @@ pub async fn create_user(incoming_user: web::Json, db_conn: // Validate user user_validator::validate_user_for_creation(incoming_user_obj, &mut message_resources); + // Find if user exists + match find_user_by_email(&mut db_conn.lock().unwrap(), &user_to_insert.email).await{ + Ok(_usr) => { + message_resources.push(MessageResourceDto::new_from_error_message(ERROR_USER_ALREADY_EXISTS)); + return HttpResponse::BadRequest().json(web::Json(message_resources)); + }, + Err(_e) => {} + }; + // If validation gave any errors blow up and send them back to the client if message_resources.len() > 0 { return HttpResponse::BadRequest().json(web::Json(message_resources)); } @@ -36,24 +46,25 @@ pub async fn create_user(incoming_user: web::Json, db_conn: user_to_insert.password = hash_result.hash; user_to_insert.salt = hash_result.salt; - // Try to insert user in DB + // Insert user in DB match insert_user(&mut db_conn.lock().unwrap(), &user_to_insert).await{ Ok(resultrs) => { user_to_insert.id = resultrs.last_insert_id() as i32; }, Err(error) => { println!("Error while inserting user in database from create_user method. Log: {}", error); - return HttpResponse::InternalServerError().json(web::Json(())) + return HttpResponse::InternalServerError().finish(); }}; // Create token and send it back. - let tokens: Vec = hasher::generate_multiple_random_token_with_rng(2).await.expect("msg"); + let tokens: Vec = hasher::generate_multiple_random_token_with_rng(2).await.expect("Error creating multiple random tokens."); let mut token_to_insert = Token::new(user_to_insert.id, - tokens.get(0).expect("msg").to_string(), - tokens.get(1).expect("msg").to_string() + tokens.get(0).expect("Error. Token doesn't exist in list.").to_string(), + tokens.get(1).expect("Error. Token doesn't exist in list.").to_string() ); + // Insert token in DB match insert_token(&mut db_conn.lock().unwrap(), &token_to_insert).await{ Ok(resultrs) => {token_to_insert.id = resultrs.last_insert_id() as i32}, Err(e) => {panic!("{e}")} @@ -61,4 +72,28 @@ pub async fn create_user(incoming_user: web::Json, db_conn: // All good? Send an OK! HttpResponse::Ok().json(web::Json(token_to_insert)) -} \ No newline at end of file +} + +#[post("/user/auth/password")] +pub async fn authenticate_user_with_password(incoming_user: web::Json, db_conn: Data>) -> HttpResponse { + let mut message_resources: Vec = Vec::new(); + let incoming_user_obj = incoming_user.0; + + // Validate user email & password + user_validator::validate_user_for_password_authentication(&incoming_user_obj, &mut message_resources); + + // If validation gave any errors blow up and send them back to the client + if message_resources.len() > 0 { return HttpResponse::BadRequest().json(web::Json(message_resources)); } + + // If user exists get it, if it doesn't blow up to the client + let persisted_user = match find_user_by_email(&mut db_conn.lock().unwrap(), &incoming_user_obj.email).await { + Ok(rs) => {rs}, + Err(_e) => { + message_resources.push(MessageResourceDto::new_from_error_message(ERROR_USER_DOES_NOT_EXIST)); + return HttpResponse::BadRequest().json(web::Json(message_resources)); + }}; + + // TODO: create new token & check for all the others that may have expired.s + + HttpResponse::Ok().finish() +} diff --git a/src/util/hasher.rs b/src/util/hasher.rs index a1cfc13..9092852 100644 --- a/src/util/hasher.rs +++ b/src/util/hasher.rs @@ -5,6 +5,8 @@ use tokio::task::JoinError; use crate::dto::hash_dtos::HashResult; +const SALT_ROUNDS: u32 = 1000; + pub async fn generate_multiple_random_token_with_rng(amount: u8) -> Result, JoinError> { // Get a new instance of a Random Number Generator let rng = SystemRandom::new(); @@ -34,7 +36,7 @@ pub async fn generate_multiple_random_token_with_rng(amount: u8) -> Result HashResult{ // Get output length from a sha512 hash const CREDENTIAL_LEN: usize = digest::SHA512_OUTPUT_LEN; - let n_iter = NonZeroU32::new(100_000).unwrap(); + let n_iter = NonZeroU32::new(SALT_ROUNDS).unwrap(); let rng = SystemRandom::new(); // Create empty 64-bit byte array for the salt @@ -66,7 +68,7 @@ fn _verify_password_hash(input_password: &String, salt: &String, actual_hash: &S // Get output length from a sha512 hash const CREDENTIAL_LEN: usize = digest::SHA512_OUTPUT_LEN; - let n_iter = NonZeroU32::new(100_000).unwrap(); + let n_iter = NonZeroU32::new(SALT_ROUNDS).unwrap(); // Verify the user-inputted password hashed with the salt is the same. pbkdf2::verify( diff --git a/src/validation/user_validator.rs b/src/validation/user_validator.rs index e4dbe38..ed9ef04 100644 --- a/src/validation/user_validator.rs +++ b/src/validation/user_validator.rs @@ -1,5 +1,5 @@ use crate::{ - dto::{ user_dtos::UserForCreationDto, message_resources_dtos::MessageResourceDto }, + dto::{ user_dtos::{UserForCreationDto, UserForLoginDto}, message_resources_dtos::MessageResourceDto }, resources::{ variable_lengths::*, error_messages::* } }; @@ -28,4 +28,12 @@ pub fn validate_user_for_creation(user: UserForCreationDto, message_resources: & if !validate_user_password(&user.password) { message_resources.push(MessageResourceDto::new_from_error_message(ERROR_INVALID_PASSWORD)); } +} +pub fn validate_user_for_password_authentication(user: &UserForLoginDto, message_resources: &mut Vec){ + if !validate_user_email(&user.email) { + message_resources.push(MessageResourceDto::new_from_error_message(ERROR_INVALID_EMAIL)); + } + if !validate_user_password(&user.password) { + message_resources.push(MessageResourceDto::new_from_error_message(ERROR_INVALID_PASSWORD)); + } } \ No newline at end of file