Added error constants, variable length constants, finished user creation & added password hashing with salts and verification
This commit is contained in:
parent
7a8847ed5b
commit
a71402aad0
|
@ -9,8 +9,8 @@ edition = "2021"
|
||||||
dotenv = "0.15.0"
|
dotenv = "0.15.0"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
#sqlx = { version = "0.5", features = [ "runtime-tokio-rustls", "mysql" ] }
|
sqlx = { version = "0.5", features = [ "runtime-tokio-rustls", "mysql", "chrono" ] }
|
||||||
diesel = { version = "1.4.4", features = ["mysql", "chrono"] }
|
|
||||||
diesel_migrations = { version = "1.4.0"}
|
|
||||||
actix-web = "4"
|
actix-web = "4"
|
||||||
chrono = "0.4"
|
chrono = { version = "0.4", features = [ "serde" ] }
|
||||||
|
ring = "0.16.20"
|
||||||
|
data-encoding = "2.3.2"
|
|
@ -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"
|
|
|
@ -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
|
||||||
|
)
|
|
@ -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
|
||||||
|
)
|
|
@ -1,2 +0,0 @@
|
||||||
-- This file should undo anything in `up.sql`
|
|
||||||
DROP TABLE user
|
|
|
@ -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
|
|
||||||
)
|
|
|
@ -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
|
||||||
|
)
|
|
@ -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
|
||||||
|
)
|
|
@ -0,0 +1,3 @@
|
||||||
|
INSERT INTO token
|
||||||
|
(id, user_id, time_created, last_updated, auth_token, refresh_token)
|
||||||
|
values (NULL, ?, NOW(), NOW(), ?, ?)
|
|
@ -0,0 +1,3 @@
|
||||||
|
INSERT INTO user
|
||||||
|
(id, time_created, last_updated, email, name, password, salt) values
|
||||||
|
(NULL, NOW(), NOW(), ?, ?, ?, ?)
|
|
@ -1,10 +1,8 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use diesel::{MysqlConnection, Connection, ConnectionError};
|
use sqlx::{MySqlConnection, Connection};
|
||||||
|
|
||||||
use crate::embedded_migrations;
|
pub async fn start_database_connection(env_vars: &HashMap<String, String>) -> Result<MySqlConnection, sqlx::Error>{
|
||||||
|
|
||||||
pub fn start_database_connection(env_vars: &HashMap<String, String>) -> Result<MysqlConnection, ConnectionError>{
|
|
||||||
let db_user = match env_vars.get("DB_USER") {
|
let db_user = match env_vars.get("DB_USER") {
|
||||||
Some(str) => str,
|
Some(str) => str,
|
||||||
None => panic!("DB_USER env var not found")
|
None => panic!("DB_USER env var not found")
|
||||||
|
@ -22,12 +20,11 @@ pub fn start_database_connection(env_vars: &HashMap<String, String>) -> Result<M
|
||||||
None => panic!("DB_DATABASE_NAME env var not found")
|
None => panic!("DB_DATABASE_NAME env var not found")
|
||||||
};
|
};
|
||||||
let formatted_db_url = &format!("mysql://{db_user}:{db_pass}@{db_host}/{db_database_name}");
|
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){
|
pub async fn run_all_migrations(conn: &mut MySqlConnection){
|
||||||
match embedded_migrations::run(conn){
|
match sqlx::migrate!("./migrations").run(conn).await {
|
||||||
Ok(()) => {println!("{}", "Successfully ran migrations.")}
|
Ok(()) => {println!("{}", "Successfully ran migrations.")},
|
||||||
Err(e) => {panic!("Error happened while trying to run migrations Error: {}", e)}
|
Err(error) => {panic!("{error}")}
|
||||||
}
|
}
|
||||||
//TODO: log output like this: embedded_migrations::run_with_output(&connection, &mut std::io::stdout());
|
|
||||||
}
|
}
|
|
@ -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<usize, diesel::result::Error>{
|
pub async fn insert_user(conn: &mut MySqlConnection, user_to_insert: &User) -> Result<MySqlQueryResult, sqlx::Error>{
|
||||||
diesel::insert_into(user::table).values(&user).execute(conn)
|
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::<User>(_conn));
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}*/
|
|
@ -1,10 +1,7 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use sqlx::MySqlConnection;
|
||||||
use diesel::MysqlConnection;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
pub struct SharedStateObj{
|
pub struct SharedStateObj{
|
||||||
pub db_conn: MysqlConnection,
|
pub db_conn: MySqlConnection,
|
||||||
pub env_vars: HashMap<String, String>,
|
pub env_vars: HashMap<String, String>,
|
||||||
}
|
}
|
|
@ -1,11 +1,35 @@
|
||||||
use diesel::{Queryable, Insertable};
|
use chrono::{NaiveDateTime};
|
||||||
use serde::Serialize;
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
use crate::schema::*;
|
use crate::dto::user_dtos::UserForCreationDto;
|
||||||
|
|
||||||
#[derive(Serialize, Queryable, Insertable)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
#[table_name = "user"]
|
|
||||||
pub struct User{
|
pub struct User{
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub name: String
|
pub time_created: Option<NaiveDateTime>,
|
||||||
|
pub last_updated: Option<NaiveDateTime>,
|
||||||
|
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() }
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -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 }
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
pub mod user_dtos;
|
||||||
|
pub mod message_resources_dtos;
|
||||||
|
pub mod hash_dtos;
|
|
@ -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
|
||||||
|
}
|
25
src/main.rs
25
src/main.rs
|
@ -1,19 +1,12 @@
|
||||||
mod r#do; mod dao;
|
mod r#do; mod dao;
|
||||||
mod routes; mod service;
|
mod routes; mod service;
|
||||||
mod util; pub mod schema;
|
mod util; mod dto;
|
||||||
|
mod validation; mod resources;
|
||||||
|
|
||||||
use r#do::shared_state::SharedStateObj;
|
use r#do::shared_state::SharedStateObj;
|
||||||
use util::env_util;
|
use util::env_util;
|
||||||
use routes::main_router::{start_all_routes, after_startup_fn};
|
use routes::main_router::{start_all_routes, after_startup_fn};
|
||||||
use dao::main_dao::{self, run_all_migrations};
|
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!();
|
|
||||||
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
|
@ -23,17 +16,25 @@ async fn main() -> Result<(), std::io::Error> {
|
||||||
let env_vars = env_util::get_dot_env_map();
|
let env_vars = env_util::get_dot_env_map();
|
||||||
|
|
||||||
// Start database
|
// 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,
|
Ok(conn) => conn,
|
||||||
Err(e) => panic!("Failure starting the database. Reason: {}", e)
|
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
|
||||||
run_all_migrations(&mut db_conn);
|
run_all_migrations(&mut db_conn).await;
|
||||||
|
|
||||||
// Put db connection and env variables in shared state
|
// Put db connection and env variables in shared state
|
||||||
let shared_state_obj = SharedStateObj {db_conn, env_vars };
|
let shared_state_obj = SharedStateObj {db_conn, env_vars };
|
||||||
|
|
||||||
// Pass shared state to server and start it
|
// Pass shared state to server and start it
|
||||||
start_all_routes(&after_startup_fn, shared_state_obj).await
|
start_all_routes(&after_startup_fn, shared_state_obj).await
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.");
|
|
@ -0,0 +1,2 @@
|
||||||
|
pub mod error_messages;
|
||||||
|
pub mod variable_lengths;
|
|
@ -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;
|
|
@ -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 db_conn_state = web::Data::new(Mutex::new(state.db_conn));
|
||||||
let env_vars_state = web::Data::new(Mutex::new(state.env_vars.clone()));
|
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 || {
|
let server_future = HttpServer::new( move || {
|
||||||
App::new()
|
App::new()
|
||||||
// Define routes & pass in shared state
|
// Define routes & pass in shared state
|
||||||
.app_data(db_conn_state.clone())
|
.app_data(db_conn_state.clone())
|
||||||
.app_data(env_vars_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))?
|
.bind((host_addr, host_port))?
|
||||||
.run();
|
.run();
|
||||||
|
|
||||||
|
// Actual server start and after startup call
|
||||||
let (server_start_result, _after_startup_value) =
|
let (server_start_result, _after_startup_value) =
|
||||||
tokio::join!(server_future, async {after_startup_fn_call();});
|
tokio::join!(server_future, async {after_startup_fn_call();});
|
||||||
return server_start_result; // Return server
|
return server_start_result; // Return server
|
||||||
|
|
|
@ -1,17 +1,50 @@
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
|
|
||||||
use actix_web::{get, web::{self, Path, Data}, HttpResponse, post};
|
use actix_web::{web::{self, Data}, HttpResponse, post};
|
||||||
use diesel::MysqlConnection;
|
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}")]
|
/*#[get("/user/{id}")]
|
||||||
pub async fn get_user_from_db(id: Path<i32>, _data: Data<Mutex<MysqlConnection>>) -> HttpResponse {
|
pub async fn get_user_from_db(id: Path<i32>, db_conn: Data<Mutex<MySqlConnection>>) -> HttpResponse {
|
||||||
_insert_user(&mut _data.lock().unwrap(), User { id: *id, name: "nigga".to_string() });
|
match find_user_by_id(&mut db_conn.lock().unwrap(), *id).await{
|
||||||
HttpResponse::Ok().json(web::Json(User {id: *id, name: "nigga".to_string()}))
|
Ok(MySqlQueryResult)
|
||||||
}
|
}
|
||||||
|
HttpResponse::Ok().json(web::Json("ss"))
|
||||||
|
}*/
|
||||||
|
|
||||||
#[post("/user")]
|
#[post("/user")]
|
||||||
pub async fn create_user() -> HttpResponse {
|
pub async fn create_user(incoming_user: web::Json<UserForCreationDto>, db_conn: Data<Mutex<MySqlConnection>>) -> HttpResponse {
|
||||||
HttpResponse::Ok().json(web::Json(""))
|
let mut message_resources: Vec<MessageResourceDto> = 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(())
|
||||||
}
|
}
|
|
@ -1,6 +0,0 @@
|
||||||
table! {
|
|
||||||
user (id) {
|
|
||||||
id -> Integer,
|
|
||||||
name -> Varchar,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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())
|
||||||
|
}
|
|
@ -1 +1,2 @@
|
||||||
pub mod env_util;
|
pub mod env_util;
|
||||||
|
pub mod hasher;
|
|
@ -0,0 +1 @@
|
||||||
|
pub mod user_validator;
|
|
@ -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<MessageResourceDto>){
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue