From a71402aad0ef519a59ab3bc0e840aba05b1ec1fe Mon Sep 17 00:00:00 2001 From: franklinblanco Date: Mon, 27 Jun 2022 15:09:14 -0400 Subject: [PATCH] Added error constants, variable length constants, finished user creation & added password hashing with salts and verification --- Cargo.toml | 8 +-- diesel.toml | 5 -- migrations/20220627132425_userup.sql.sql | 9 ++++ migrations/20220627132445_tokenup.sql.sql | 8 +++ migrations/mysql/down.sql | 2 - migrations/mysql/up.sql | 5 -- sql/schema/migrations/tokenup.sql | 8 +++ sql/schema/migrations/userup.sql | 9 ++++ .../.gitkeep => sql/schema/token/find.sql | 0 sql/schema/token/insert.sql | 3 ++ sql/schema/token/update.sql | 0 sql/schema/user/find.sql | 0 sql/schema/user/insert.sql | 3 ++ sql/schema/user/update.sql | 0 src/dao/main_dao.rs | 17 +++--- src/dao/user_dao.rs | 18 ++++--- src/do/shared_state.rs | 7 +-- src/do/user.rs | 38 ++++++++++--- src/dto/hash_dtos.rs | 11 ++++ src/dto/message_resources_dtos.rs | 23 ++++++++ src/dto/mod.rs | 3 ++ src/dto/user_dtos.rs | 18 +++++++ src/main.rs | 25 ++++----- src/resources/error_messages.rs | 8 +++ src/resources/mod.rs | 2 + src/resources/variable_lengths.rs | 9 ++++ src/routes/main_router.rs | 6 ++- src/routes/user_routes.rs | 53 +++++++++++++++---- src/schema.rs | 6 --- src/util/hasher.rs | 52 ++++++++++++++++++ src/util/mod.rs | 3 +- src/validation/mod.rs | 1 + src/validation/user_validator.rs | 31 +++++++++++ 33 files changed, 316 insertions(+), 75 deletions(-) delete mode 100644 diesel.toml create mode 100644 migrations/20220627132425_userup.sql.sql create mode 100644 migrations/20220627132445_tokenup.sql.sql delete mode 100644 migrations/mysql/down.sql delete mode 100644 migrations/mysql/up.sql create mode 100644 sql/schema/migrations/tokenup.sql create mode 100644 sql/schema/migrations/userup.sql rename migrations/.gitkeep => sql/schema/token/find.sql (100%) create mode 100644 sql/schema/token/insert.sql create mode 100644 sql/schema/token/update.sql create mode 100644 sql/schema/user/find.sql create mode 100644 sql/schema/user/insert.sql create mode 100644 sql/schema/user/update.sql create mode 100644 src/dto/hash_dtos.rs create mode 100644 src/dto/message_resources_dtos.rs create mode 100644 src/dto/mod.rs create mode 100644 src/dto/user_dtos.rs create mode 100644 src/resources/error_messages.rs create mode 100644 src/resources/mod.rs create mode 100644 src/resources/variable_lengths.rs delete mode 100644 src/schema.rs create mode 100644 src/util/hasher.rs create mode 100644 src/validation/mod.rs create mode 100644 src/validation/user_validator.rs diff --git a/Cargo.toml b/Cargo.toml index 2922a48..4dd5098 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,8 +9,8 @@ edition = "2021" dotenv = "0.15.0" serde = { version = "1.0", features = ["derive"] } tokio = { version = "1", features = ["full"] } -#sqlx = { version = "0.5", features = [ "runtime-tokio-rustls", "mysql" ] } -diesel = { version = "1.4.4", features = ["mysql", "chrono"] } -diesel_migrations = { version = "1.4.0"} +sqlx = { version = "0.5", features = [ "runtime-tokio-rustls", "mysql", "chrono" ] } actix-web = "4" -chrono = "0.4" \ No newline at end of file +chrono = { version = "0.4", features = [ "serde" ] } +ring = "0.16.20" +data-encoding = "2.3.2" \ No newline at end of file diff --git a/diesel.toml b/diesel.toml deleted file mode 100644 index 92267c8..0000000 --- a/diesel.toml +++ /dev/null @@ -1,5 +0,0 @@ -# For documentation on how to configure this file, -# see diesel.rs/guides/configuring-diesel-cli - -[print_schema] -file = "src/schema.rs" diff --git a/migrations/20220627132425_userup.sql.sql b/migrations/20220627132425_userup.sql.sql new file mode 100644 index 0000000..a826771 --- /dev/null +++ b/migrations/20220627132425_userup.sql.sql @@ -0,0 +1,9 @@ +CREATE TABLE IF NOT EXISTS user ( + id INT AUTO_INCREMENT PRIMARY KEY, + time_created TIMESTAMP NOT NULL, + last_updated TIMESTAMP NOT NULL, + email VARCHAR(255) NOT NULL, + name VARCHAR(255) NOT NULL, + password VARCHAR(255) NOT NULL, + salt VARCHAR(255) NOT NULL +) \ No newline at end of file diff --git a/migrations/20220627132445_tokenup.sql.sql b/migrations/20220627132445_tokenup.sql.sql new file mode 100644 index 0000000..0f3c492 --- /dev/null +++ b/migrations/20220627132445_tokenup.sql.sql @@ -0,0 +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, + auth_token VARCHAR(255) NOT NULL, + refresh_token VARCHAR(255) NOT NULL +) \ No newline at end of file diff --git a/migrations/mysql/down.sql b/migrations/mysql/down.sql deleted file mode 100644 index ef17d92..0000000 --- a/migrations/mysql/down.sql +++ /dev/null @@ -1,2 +0,0 @@ --- This file should undo anything in `up.sql` -DROP TABLE user \ No newline at end of file diff --git a/migrations/mysql/up.sql b/migrations/mysql/up.sql deleted file mode 100644 index 88525bb..0000000 --- a/migrations/mysql/up.sql +++ /dev/null @@ -1,5 +0,0 @@ --- Your SQL goes here -CREATE TABLE IF NOT EXISTS user ( - id INT AUTO_INCREMENT PRIMARY KEY, - name VARCHAR(255) NOT NULL -) \ No newline at end of file diff --git a/sql/schema/migrations/tokenup.sql b/sql/schema/migrations/tokenup.sql new file mode 100644 index 0000000..0f3c492 --- /dev/null +++ b/sql/schema/migrations/tokenup.sql @@ -0,0 +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, + auth_token VARCHAR(255) NOT NULL, + refresh_token VARCHAR(255) NOT NULL +) \ No newline at end of file diff --git a/sql/schema/migrations/userup.sql b/sql/schema/migrations/userup.sql new file mode 100644 index 0000000..a826771 --- /dev/null +++ b/sql/schema/migrations/userup.sql @@ -0,0 +1,9 @@ +CREATE TABLE IF NOT EXISTS user ( + id INT AUTO_INCREMENT PRIMARY KEY, + time_created TIMESTAMP NOT NULL, + last_updated TIMESTAMP NOT NULL, + email VARCHAR(255) NOT NULL, + name VARCHAR(255) NOT NULL, + password VARCHAR(255) NOT NULL, + salt VARCHAR(255) NOT NULL +) \ No newline at end of file diff --git a/migrations/.gitkeep b/sql/schema/token/find.sql similarity index 100% rename from migrations/.gitkeep rename to sql/schema/token/find.sql diff --git a/sql/schema/token/insert.sql b/sql/schema/token/insert.sql new file mode 100644 index 0000000..3c324f6 --- /dev/null +++ b/sql/schema/token/insert.sql @@ -0,0 +1,3 @@ +INSERT INTO token +(id, user_id, time_created, last_updated, auth_token, refresh_token) +values (NULL, ?, NOW(), NOW(), ?, ?) \ No newline at end of file diff --git a/sql/schema/token/update.sql b/sql/schema/token/update.sql new file mode 100644 index 0000000..e69de29 diff --git a/sql/schema/user/find.sql b/sql/schema/user/find.sql new file mode 100644 index 0000000..e69de29 diff --git a/sql/schema/user/insert.sql b/sql/schema/user/insert.sql new file mode 100644 index 0000000..4fd03c1 --- /dev/null +++ b/sql/schema/user/insert.sql @@ -0,0 +1,3 @@ +INSERT INTO user +(id, time_created, last_updated, email, name, password, salt) values +(NULL, NOW(), NOW(), ?, ?, ?, ?) \ No newline at end of file diff --git a/sql/schema/user/update.sql b/sql/schema/user/update.sql new file mode 100644 index 0000000..e69de29 diff --git a/src/dao/main_dao.rs b/src/dao/main_dao.rs index ff8bfd4..af26feb 100644 --- a/src/dao/main_dao.rs +++ b/src/dao/main_dao.rs @@ -1,10 +1,8 @@ use std::collections::HashMap; -use diesel::{MysqlConnection, Connection, ConnectionError}; +use sqlx::{MySqlConnection, Connection}; -use crate::embedded_migrations; - -pub fn start_database_connection(env_vars: &HashMap) -> Result{ +pub async fn start_database_connection(env_vars: &HashMap) -> Result{ let db_user = match env_vars.get("DB_USER") { Some(str) => str, None => panic!("DB_USER env var not found") @@ -22,12 +20,11 @@ pub fn start_database_connection(env_vars: &HashMap) -> Result panic!("DB_DATABASE_NAME env var not found") }; let formatted_db_url = &format!("mysql://{db_user}:{db_pass}@{db_host}/{db_database_name}"); - MysqlConnection::establish(formatted_db_url) + sqlx::MySqlConnection::connect(&formatted_db_url).await } -pub fn run_all_migrations(conn: &mut MysqlConnection){ - match embedded_migrations::run(conn){ - Ok(()) => {println!("{}", "Successfully ran migrations.")} - Err(e) => {panic!("Error happened while trying to run migrations Error: {}", e)} +pub async fn run_all_migrations(conn: &mut MySqlConnection){ + match sqlx::migrate!("./migrations").run(conn).await { + Ok(()) => {println!("{}", "Successfully ran migrations.")}, + Err(error) => {panic!("{error}")} } - //TODO: log output like this: embedded_migrations::run_with_output(&connection, &mut std::io::stdout()); } \ No newline at end of file diff --git a/src/dao/user_dao.rs b/src/dao/user_dao.rs index f09aa98..fbc6165 100644 --- a/src/dao/user_dao.rs +++ b/src/dao/user_dao.rs @@ -1,10 +1,16 @@ -use diesel::prelude::*; +use sqlx::{MySqlConnection, mysql::MySqlQueryResult}; -use crate::{schema::user, r#do::user::User}; +use crate::r#do::user::User; -pub fn _insert_user(conn: &mut MysqlConnection, user: User) -> Result{ - diesel::insert_into(user::table).values(&user).execute(conn) +pub async fn insert_user(conn: &mut MySqlConnection, user_to_insert: &User) -> Result{ + sqlx::query_file!("sql/schema/user/insert.sql", + user_to_insert.email, user_to_insert.name, user_to_insert.password, user_to_insert.salt) + .execute(conn).await } -pub fn _find_user_by_id(_conn: &mut MysqlConnection, _id: i32) -> Result<(), ()>{ +//pub async fn _update_user(conn: &mut MySqlConnection, user_to_modify: &User) -> Result<(), sqlx::Error>{ +// Ok(()) +//} +/*pub async fn find_user_by_id(_conn: &mut MySqlConnection, _id: i32) -> Result<(), ()>{ + //println!("{:?}", user::select(user, (id, name, time_created)).load::(_conn)); Ok(()) -} \ No newline at end of file +}*/ \ No newline at end of file diff --git a/src/do/shared_state.rs b/src/do/shared_state.rs index 287a565..a125f7b 100644 --- a/src/do/shared_state.rs +++ b/src/do/shared_state.rs @@ -1,10 +1,7 @@ use std::collections::HashMap; - -use diesel::MysqlConnection; - - +use sqlx::MySqlConnection; pub struct SharedStateObj{ - pub db_conn: MysqlConnection, + pub db_conn: MySqlConnection, pub env_vars: HashMap, } \ No newline at end of file diff --git a/src/do/user.rs b/src/do/user.rs index f9aaf5e..c43f87c 100644 --- a/src/do/user.rs +++ b/src/do/user.rs @@ -1,11 +1,35 @@ -use diesel::{Queryable, Insertable}; -use serde::Serialize; +use chrono::{NaiveDateTime}; +use serde::{Serialize, Deserialize}; -use crate::schema::*; +use crate::dto::user_dtos::UserForCreationDto; -#[derive(Serialize, Queryable, Insertable)] -#[table_name = "user"] +#[derive(Serialize, Deserialize, Debug)] pub struct User{ pub id: i32, - pub name: String -} \ No newline at end of file + pub time_created: Option, + pub last_updated: Option, + pub email: String, + pub name: String, + pub password: String, + pub salt: String +} +impl User { + pub fn _new() -> User { + User { id: 0, + time_created: None, // This will be automatically generated from the database + last_updated: None, // This will be automatically generated from the database + email: "".to_string(), + name:"".to_string(), + password:"".to_string(), + salt: "".to_string() } + } + pub fn new_for_creation(incoming_user: &UserForCreationDto) -> User{ + User { id: 0, + time_created: None, // This will be automatically generated from the database + last_updated: None, // This will be automatically generated from the database + email: incoming_user.email.to_string(), + name: incoming_user.name.to_string(), + password: incoming_user.password.to_string(), + salt: "".to_string() } + } +} diff --git a/src/dto/hash_dtos.rs b/src/dto/hash_dtos.rs new file mode 100644 index 0000000..5cdbd81 --- /dev/null +++ b/src/dto/hash_dtos.rs @@ -0,0 +1,11 @@ + +pub struct HashResult{ + pub salt: String, + pub hash: String +} + +impl HashResult{ + pub fn new(salt: String, hash: String) -> HashResult{ + HashResult { salt, hash } + } +} \ No newline at end of file diff --git a/src/dto/message_resources_dtos.rs b/src/dto/message_resources_dtos.rs new file mode 100644 index 0000000..39c1507 --- /dev/null +++ b/src/dto/message_resources_dtos.rs @@ -0,0 +1,23 @@ +use serde::Serialize; + + +#[derive(Serialize)] +pub struct MessageResourceDto{ + key: String, + message: String +} + +impl MessageResourceDto{ + pub fn _new(key: String, message: String) -> MessageResourceDto{ + MessageResourceDto{ + key, + message + } + } + pub fn new_from_error_message(error: (&str, &str)) -> MessageResourceDto{ + MessageResourceDto { + key: error.0.to_string(), + message: error.1.to_string() + } + } +} \ No newline at end of file diff --git a/src/dto/mod.rs b/src/dto/mod.rs new file mode 100644 index 0000000..8058c37 --- /dev/null +++ b/src/dto/mod.rs @@ -0,0 +1,3 @@ +pub mod user_dtos; +pub mod message_resources_dtos; +pub mod hash_dtos; \ No newline at end of file diff --git a/src/dto/user_dtos.rs b/src/dto/user_dtos.rs new file mode 100644 index 0000000..3344d83 --- /dev/null +++ b/src/dto/user_dtos.rs @@ -0,0 +1,18 @@ +use serde::{Serialize, Deserialize}; + +#[derive(Serialize, Deserialize, Debug)] +pub struct UserForCreationDto{ + pub email: String, + pub password: String, + pub name: String +} +#[derive(Serialize, Deserialize, Debug)] +pub struct UserForLoginDto{ + pub email: String, + pub password: String +} +#[derive(Serialize, Deserialize, Debug)] +pub struct UserForAuthenticationDto{ + pub email: String, + pub token: String +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 8a7b571..1df88d3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,19 +1,12 @@ mod r#do; mod dao; mod routes; mod service; -mod util; pub mod schema; +mod util; mod dto; +mod validation; mod resources; use r#do::shared_state::SharedStateObj; use util::env_util; use routes::main_router::{start_all_routes, after_startup_fn}; -use dao::main_dao::{self, run_all_migrations}; - -// Include diesel_migrations so embed_migrations! macro can be called. -#[macro_use] -extern crate diesel_migrations; -#[macro_use] -extern crate diesel; -// Run all migrations inside the path -embed_migrations!(); +use dao::{main_dao::{self, run_all_migrations}}; #[tokio::main] @@ -23,17 +16,25 @@ async fn main() -> Result<(), std::io::Error> { let env_vars = env_util::get_dot_env_map(); // Start database - let mut db_conn = match main_dao::start_database_connection(&env_vars) { + let mut db_conn = match main_dao::start_database_connection(&env_vars).await { Ok(conn) => conn, Err(e) => panic!("Failure starting the database. Reason: {}", e) }; + /*match insert_user(&mut db_conn, + &User{ id: 1, name: "s".to_string(), + time_created: Some(chrono::Utc::now().naive_utc()) }).await { + Ok(()) => {}, + Err(e) => {panic!("ERROR MYSQL {}", e)} + } +*/ // Run all migrations - run_all_migrations(&mut db_conn); + run_all_migrations(&mut db_conn).await; // Put db connection and env variables in shared state let shared_state_obj = SharedStateObj {db_conn, env_vars }; // Pass shared state to server and start it start_all_routes(&after_startup_fn, shared_state_obj).await + } diff --git a/src/resources/error_messages.rs b/src/resources/error_messages.rs new file mode 100644 index 0000000..c5690b8 --- /dev/null +++ b/src/resources/error_messages.rs @@ -0,0 +1,8 @@ +// This file stores all the error messages +// Template: pub const ERROR_KEY_OR_NAME: (&str, &str) = ("ERROR.KEY", "ERROR VALUE"); + +pub const ERROR_INVALID_EMAIL: (&str, &str) = ("ERROR.INVALID_EMAIL", "Invalid email. Needs to be at least 4 characters, at most 254 and correctly formatted."); + +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 diff --git a/src/resources/mod.rs b/src/resources/mod.rs new file mode 100644 index 0000000..76bc0b4 --- /dev/null +++ b/src/resources/mod.rs @@ -0,0 +1,2 @@ +pub mod error_messages; +pub mod variable_lengths; \ No newline at end of file diff --git a/src/resources/variable_lengths.rs b/src/resources/variable_lengths.rs new file mode 100644 index 0000000..242214a --- /dev/null +++ b/src/resources/variable_lengths.rs @@ -0,0 +1,9 @@ + +pub const MIN_EMAIL_LENGTH: u16 = 4; +pub const MAX_EMAIL_LENGTH: u16 = 254; + +pub const MIN_NAME_LENGTH: u16 = 4; +pub const MAX_NAME_LENGTH: u16 = 254; + +pub const MIN_PASSWORD_LENGTH: u16 = 8; +pub const MAX_PASSWORD_LENGTH: u16 = 128; \ No newline at end of file diff --git a/src/routes/main_router.rs b/src/routes/main_router.rs index 7ffc32b..cbd1776 100644 --- a/src/routes/main_router.rs +++ b/src/routes/main_router.rs @@ -33,17 +33,19 @@ pub async fn start_all_routes(after_startup_fn_call: &dyn Fn(), state: SharedSta let db_conn_state = web::Data::new(Mutex::new(state.db_conn)); let env_vars_state = web::Data::new(Mutex::new(state.env_vars.clone())); - // Start server + // Start server code that turns into a future to be executed below let server_future = HttpServer::new( move || { App::new() // Define routes & pass in shared state .app_data(db_conn_state.clone()) .app_data(env_vars_state.clone()) - .service(user_routes::get_user_from_db) + .service(user_routes::create_user) + //.service(user_routes::get_user_from_db) }) .bind((host_addr, host_port))? .run(); + // Actual server start and after startup call let (server_start_result, _after_startup_value) = tokio::join!(server_future, async {after_startup_fn_call();}); return server_start_result; // Return server diff --git a/src/routes/user_routes.rs b/src/routes/user_routes.rs index 160d302..49c97fb 100644 --- a/src/routes/user_routes.rs +++ b/src/routes/user_routes.rs @@ -1,17 +1,50 @@ use std::sync::Mutex; -use actix_web::{get, web::{self, Path, Data}, HttpResponse, post}; -use diesel::MysqlConnection; +use actix_web::{web::{self, Data}, HttpResponse, post}; +use sqlx::{MySqlConnection}; -use crate::{r#do::user::User, dao::user_dao::_insert_user}; +use crate::{r#do::user::User, dao::user_dao::{insert_user}, dto::{user_dtos::UserForCreationDto, message_resources_dtos::MessageResourceDto}, validation::user_validator, util::hasher}; -#[get("/user/{id}")] -pub async fn get_user_from_db(id: Path, _data: Data>) -> HttpResponse { - _insert_user(&mut _data.lock().unwrap(), User { id: *id, name: "nigga".to_string() }); - HttpResponse::Ok().json(web::Json(User {id: *id, name: "nigga".to_string()})) -} +/*#[get("/user/{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) + } + HttpResponse::Ok().json(web::Json("ss")) +}*/ #[post("/user")] -pub async fn create_user() -> HttpResponse { - HttpResponse::Ok().json(web::Json("")) +pub async fn create_user(incoming_user: web::Json, db_conn: Data>) -> HttpResponse { + let mut message_resources: Vec = Vec::new(); + + // Get user object from json + let incoming_user_obj = incoming_user.0; + + // Transform userdto to user domain obj + let mut user_to_insert = User::new_for_creation( + &incoming_user_obj + ); + + // Validate user + user_validator::validate_user_for_creation(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)); } + + // Get salt and hashed password from hashing function then give the results to the user + let hash_result = hasher::hash_password(&user_to_insert.password); + user_to_insert.password = hash_result.hash; + user_to_insert.salt = hash_result.salt; + + // Try to insert user in DB + match insert_user(&mut db_conn.lock().unwrap(), &user_to_insert).await{ + Ok(_resultrs) => {}, + Err(error) => { + println!("Error while inserting user in database from create_user method. Log: {}", error); + return HttpResponse::InternalServerError().json(web::Json(())) + }}; + // TODO: Create token and send it back. + + // All good? Send an OK! + HttpResponse::Ok().body(()) } \ No newline at end of file diff --git a/src/schema.rs b/src/schema.rs deleted file mode 100644 index f2e0999..0000000 --- a/src/schema.rs +++ /dev/null @@ -1,6 +0,0 @@ -table! { - user (id) { - id -> Integer, - name -> Varchar, - } -} diff --git a/src/util/hasher.rs b/src/util/hasher.rs new file mode 100644 index 0000000..9158f54 --- /dev/null +++ b/src/util/hasher.rs @@ -0,0 +1,52 @@ +use std::{num::NonZeroU32}; +use data_encoding::HEXUPPER; +use ring::{digest, rand::{SecureRandom, SystemRandom}, pbkdf2, error::Unspecified}; + +use crate::dto::hash_dtos::HashResult; + + +pub fn hash_password(password: &String) -> 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 rng = SystemRandom::new(); + + // Create empty 64-bit byte array for the salt + let mut salt = [0u8; CREDENTIAL_LEN]; + + // Fill array with random-generated salt + match rng.fill(&mut salt){ + Ok(()) => {}, + Err(_e) => {panic!("Failed to generate random salt for some reason.")} + } + + // Create empty 64-bit byte array for the hash + salt + let mut pbkdf2_hash = [0u8; CREDENTIAL_LEN]; + + // Fills byte array with hashed values + pbkdf2::derive( + pbkdf2::PBKDF2_HMAC_SHA512, + n_iter, + &salt, + password.as_bytes(), + &mut pbkdf2_hash, + ); + + // Return a tuple containing the salt and the hash + HashResult::new(HEXUPPER.encode(&salt), HEXUPPER.encode(&pbkdf2_hash)) +} + +fn _verify_password_hash(input_password: &String, salt: &String, actual_hash: &String) -> Result<(), Unspecified>{ + + // Get output length from a sha512 hash + const CREDENTIAL_LEN: usize = digest::SHA512_OUTPUT_LEN; + let n_iter = NonZeroU32::new(100_000).unwrap(); + + // Verify the user-inputted password hashed with the salt is the same. + pbkdf2::verify( + pbkdf2::PBKDF2_HMAC_SHA512, + n_iter, + salt.as_bytes(), + input_password.as_bytes(), + actual_hash.as_bytes()) +} \ No newline at end of file diff --git a/src/util/mod.rs b/src/util/mod.rs index d50ede2..71f894b 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -1 +1,2 @@ -pub mod env_util; \ No newline at end of file +pub mod env_util; +pub mod hasher; \ No newline at end of file diff --git a/src/validation/mod.rs b/src/validation/mod.rs new file mode 100644 index 0000000..4365395 --- /dev/null +++ b/src/validation/mod.rs @@ -0,0 +1 @@ +pub mod user_validator; \ No newline at end of file diff --git a/src/validation/user_validator.rs b/src/validation/user_validator.rs new file mode 100644 index 0000000..15bbd98 --- /dev/null +++ b/src/validation/user_validator.rs @@ -0,0 +1,31 @@ +use crate::{ + dto::{ user_dtos::UserForCreationDto, message_resources_dtos::MessageResourceDto }, + resources::{ variable_lengths::*, error_messages::* } +}; + +fn validate_user_email(email: &String) -> bool { + email.len() >= MIN_EMAIL_LENGTH.into() && + email.len() <= MAX_EMAIL_LENGTH.into() && + email.contains('@') && + email.contains('.') +} +fn validate_user_name(name: &String) -> bool { + name.len() >= MIN_NAME_LENGTH.into() && + name.len() <= MAX_NAME_LENGTH.into() +} +fn validate_user_password(password: &String) -> bool { + password.len() >= MIN_PASSWORD_LENGTH.into() && + password.len() <= MAX_PASSWORD_LENGTH.into() +} +// User dto SHOULD die here. +pub fn validate_user_for_creation(user: UserForCreationDto, message_resources: &mut Vec){ + if validate_user_email(&user.email) { + message_resources.push(MessageResourceDto::new_from_error_message(ERROR_INVALID_EMAIL)); + } + if validate_user_name(&user.name) { + message_resources.push(MessageResourceDto::new_from_error_message(ERROR_INVALID_NAME)); + } + if validate_user_password(&user.password) { + message_resources.push(MessageResourceDto::new_from_error_message(ERROR_INVALID_PASSWORD)); + } +} \ No newline at end of file