Authentication with password done.
This commit is contained in:
parent
b832b02a2a
commit
2112836fa6
3
sql/schema/token/find_with_user_id.sql
Normal file
3
sql/schema/token/find_with_user_id.sql
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
SELECT *
|
||||||
|
FROM token
|
||||||
|
WHERE user_id = ?
|
@ -5,5 +5,6 @@ use crate::r#do::token::Token;
|
|||||||
pub async fn insert_token(conn: &mut MySqlConnection, token: &Token) -> Result<MySqlQueryResult, sqlx::Error> {
|
pub async fn insert_token(conn: &mut MySqlConnection, token: &Token) -> Result<MySqlQueryResult, sqlx::Error> {
|
||||||
sqlx::query_file!("sql/schema/token/insert.sql", token.user_id, token.auth_token, token.refresh_token).execute(conn).await
|
sqlx::query_file!("sql/schema/token/insert.sql", token.user_id, token.auth_token, token.refresh_token).execute(conn).await
|
||||||
}
|
}
|
||||||
// TODO: Get token with auth token
|
pub async fn get_token_with_user_id(conn: &mut MySqlConnection, user_id: &i32) -> Result<Token, sqlx::Error> {
|
||||||
// TODO: Get token with user_id
|
sqlx::query_file_as!(Token, "sql/schema/token/find_with_user_id.sql", user_id).fetch_one(conn).await
|
||||||
|
}
|
@ -9,4 +9,6 @@ pub const ERROR_INVALID_PASSWORD: (&str, &str) = ("ERROR.INVALID_PASSWORD", "Inv
|
|||||||
|
|
||||||
pub const ERROR_USER_ALREADY_EXISTS: (&str, &str) = ("ERROR.USER_ALREADY_EXISTS", "A user with that email already exists.");
|
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.");
|
pub const ERROR_USER_DOES_NOT_EXIST: (&str, &str) = ("ERROR.USER_DOES_NOT_EXIST", "User with this email does not exist.");
|
||||||
|
|
||||||
|
pub const ERROR_PASSWORD_INCORRECT: (&str, &str) = ("ERROR.PASSWORD_INCORRECT", "THe password you have entered is incorrect.");
|
@ -39,6 +39,7 @@ pub async fn start_all_routes(after_startup_fn_call: &dyn Fn(), state: SharedSta
|
|||||||
.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::create_user)
|
.service(user_routes::create_user)
|
||||||
|
.service(user_routes::authenticate_user_with_password)
|
||||||
//.service(user_routes::get_user_from_db)
|
//.service(user_routes::get_user_from_db)
|
||||||
})
|
})
|
||||||
.bind((host_addr, host_port))?
|
.bind((host_addr, host_port))?
|
||||||
|
@ -4,7 +4,7 @@ use std::sync::Mutex;
|
|||||||
use actix_web::{web::{self, Data}, HttpResponse, post};
|
use actix_web::{web::{self, Data}, HttpResponse, post};
|
||||||
use sqlx::MySqlConnection;
|
use sqlx::MySqlConnection;
|
||||||
|
|
||||||
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}};
|
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::{self, generate_multiple_random_token_with_rng}, r#do::token::Token, resources::error_messages::{ERROR_USER_ALREADY_EXISTS, ERROR_USER_DOES_NOT_EXIST, ERROR_PASSWORD_INCORRECT}};
|
||||||
|
|
||||||
/*#[get("/u&ser/{id}")]
|
/*#[get("/u&ser/{id}")]
|
||||||
pub async fn get_user_from_db(id: Path<i32>, db_conn: Data<Mutex<MySqlConnection>>) -> HttpResponse {
|
pub async fn get_user_from_db(id: Path<i32>, db_conn: Data<Mutex<MySqlConnection>>) -> HttpResponse {
|
||||||
@ -90,10 +90,31 @@ pub async fn authenticate_user_with_password(incoming_user: web::Json<UserForLog
|
|||||||
Ok(rs) => {rs},
|
Ok(rs) => {rs},
|
||||||
Err(_e) => {
|
Err(_e) => {
|
||||||
message_resources.push(MessageResourceDto::new_from_error_message(ERROR_USER_DOES_NOT_EXIST));
|
message_resources.push(MessageResourceDto::new_from_error_message(ERROR_USER_DOES_NOT_EXIST));
|
||||||
return HttpResponse::BadRequest().json(web::Json(message_resources));
|
return HttpResponse::NotFound().json(web::Json(message_resources));
|
||||||
}};
|
}};
|
||||||
|
|
||||||
// TODO: create new token & check for all the others that may have expired.s
|
let input_hash = hasher::hash_password_with_existing_salt(&incoming_user_obj.password, &persisted_user.salt);
|
||||||
|
|
||||||
HttpResponse::Ok().finish()
|
match input_hash.hash == persisted_user.password {
|
||||||
|
true => {},
|
||||||
|
false => {
|
||||||
|
message_resources.push(MessageResourceDto::new_from_error_message(ERROR_PASSWORD_INCORRECT));
|
||||||
|
return HttpResponse::Unauthorized().json(web::Json(message_resources));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: create new token & check for all the others that may have expired.
|
||||||
|
let tokens = generate_multiple_random_token_with_rng(2).await.expect("Error creating multiple tokens.");
|
||||||
|
let mut token_to_insert = Token::new(persisted_user.id,
|
||||||
|
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}")}
|
||||||
|
};
|
||||||
|
|
||||||
|
HttpResponse::Ok().json(token_to_insert)
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use std::num::NonZeroU32;
|
use std::num::NonZeroU32;
|
||||||
use data_encoding::HEXUPPER;
|
use data_encoding::BASE64;
|
||||||
use ring::{digest, rand::{SecureRandom, SystemRandom}, pbkdf2, error::Unspecified};
|
use ring::{digest, rand::{SecureRandom, SystemRandom}, pbkdf2};
|
||||||
use tokio::task::JoinError;
|
use tokio::task::JoinError;
|
||||||
|
|
||||||
use crate::dto::hash_dtos::HashResult;
|
use crate::dto::hash_dtos::HashResult;
|
||||||
@ -18,7 +18,7 @@ pub async fn generate_multiple_random_token_with_rng(amount: u8) -> Result<Vec<S
|
|||||||
let future_token = async move {
|
let future_token = async move {
|
||||||
let mut token_arr = [0u8; digest::SHA512_OUTPUT_LEN];
|
let mut token_arr = [0u8; digest::SHA512_OUTPUT_LEN];
|
||||||
match cloned_rng.fill(&mut token_arr){
|
match cloned_rng.fill(&mut token_arr){
|
||||||
Ok(()) => {HEXUPPER.encode(&token_arr)},
|
Ok(()) => {BASE64.encode(&token_arr)},
|
||||||
Err(_e) => { panic!("Failed to generate random token for some reason.") }
|
Err(_e) => { panic!("Failed to generate random token for some reason.") }
|
||||||
}};
|
}};
|
||||||
tokens.push(tokio::spawn(future_token));
|
tokens.push(tokio::spawn(future_token));
|
||||||
@ -33,13 +33,36 @@ pub async fn generate_multiple_random_token_with_rng(amount: u8) -> Result<Vec<S
|
|||||||
Ok(all_tokens_solved)
|
Ok(all_tokens_solved)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn hash_password_with_existing_salt(password: &String, input_salt: &String) -> HashResult{
|
||||||
|
// Get output length from a sha512 hash
|
||||||
|
const CREDENTIAL_LEN: usize = digest::SHA512_OUTPUT_LEN;
|
||||||
|
let n_iter = NonZeroU32::new(SALT_ROUNDS).unwrap();
|
||||||
|
|
||||||
|
let salt = BASE64.decode(input_salt.as_bytes()).unwrap();
|
||||||
|
|
||||||
|
// 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 an object containing the salt and the hash
|
||||||
|
HashResult::new(BASE64.encode(&salt), BASE64.encode(&pbkdf2_hash))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn hash_password(password: &String) -> HashResult{
|
pub fn hash_password(password: &String) -> HashResult{
|
||||||
// Get output length from a sha512 hash
|
// Get output length from a sha512 hash
|
||||||
const CREDENTIAL_LEN: usize = digest::SHA512_OUTPUT_LEN;
|
const CREDENTIAL_LEN: usize = digest::SHA512_OUTPUT_LEN;
|
||||||
let n_iter = NonZeroU32::new(SALT_ROUNDS).unwrap();
|
let n_iter = NonZeroU32::new(SALT_ROUNDS).unwrap();
|
||||||
let rng = SystemRandom::new();
|
let rng = SystemRandom::new();
|
||||||
|
|
||||||
// Create empty 64-bit byte array for the salt
|
// Create empty 64-byte array for the salt
|
||||||
let mut salt = [0u8; CREDENTIAL_LEN];
|
let mut salt = [0u8; CREDENTIAL_LEN];
|
||||||
|
|
||||||
// Fill array with random-generated salt
|
// Fill array with random-generated salt
|
||||||
@ -60,21 +83,6 @@ pub fn hash_password(password: &String) -> HashResult{
|
|||||||
&mut pbkdf2_hash,
|
&mut pbkdf2_hash,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Return a tuple containing the salt and the hash
|
// Return an object containing the salt and the hash
|
||||||
HashResult::new(HEXUPPER.encode(&salt), HEXUPPER.encode(&pbkdf2_hash))
|
HashResult::new(BASE64.encode(&salt), BASE64.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(SALT_ROUNDS).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())
|
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user