diff --git a/sql/schema/token/find.sql b/sql/schema/token/find.sql deleted file mode 100644 index e69de29..0000000 diff --git a/sql/schema/token/find_with_user_id.sql b/sql/schema/token/find_with_user_id.sql new file mode 100644 index 0000000..d301095 --- /dev/null +++ b/sql/schema/token/find_with_user_id.sql @@ -0,0 +1,3 @@ +SELECT * +FROM token +WHERE user_id = ? \ No newline at end of file diff --git a/src/dao/token_dao.rs b/src/dao/token_dao.rs index 7c2ec51..dc0d8f1 100644 --- a/src/dao/token_dao.rs +++ b/src/dao/token_dao.rs @@ -5,5 +5,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 } -// TODO: Get token with auth token -// TODO: Get token with user_id \ No newline at end of file +pub async fn get_token_with_user_id(conn: &mut MySqlConnection, user_id: &i32) -> Result { + sqlx::query_file_as!(Token, "sql/schema/token/find_with_user_id.sql", user_id).fetch_one(conn).await +} \ No newline at end of file diff --git a/src/resources/error_messages.rs b/src/resources/error_messages.rs index 1b5bb7e..7a721a5 100644 --- a/src/resources/error_messages.rs +++ b/src/resources/error_messages.rs @@ -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_DOES_NOT_EXIST: (&str, &str) = ("ERROR.USER_DOES_NOT_EXIST", "User with this email does not exist."); \ No newline at end of file +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."); \ No newline at end of file diff --git a/src/routes/main_router.rs b/src/routes/main_router.rs index 9a3be21..638e5c0 100644 --- a/src/routes/main_router.rs +++ b/src/routes/main_router.rs @@ -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(env_vars_state.clone()) .service(user_routes::create_user) + .service(user_routes::authenticate_user_with_password) //.service(user_routes::get_user_from_db) }) .bind((host_addr, host_port))? diff --git a/src/routes/user_routes.rs b/src/routes/user_routes.rs index 4388652..2043933 100644 --- a/src/routes/user_routes.rs +++ b/src/routes/user_routes.rs @@ -4,7 +4,7 @@ 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, 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}")] pub async fn get_user_from_db(id: Path, db_conn: Data>) -> HttpResponse { @@ -90,10 +90,31 @@ pub async fn authenticate_user_with_password(incoming_user: web::Json {rs}, Err(_e) => { 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 - - HttpResponse::Ok().finish() + let input_hash = hasher::hash_password_with_existing_salt(&incoming_user_obj.password, &persisted_user.salt); + + 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) } diff --git a/src/util/hasher.rs b/src/util/hasher.rs index 9092852..24b119e 100644 --- a/src/util/hasher.rs +++ b/src/util/hasher.rs @@ -1,6 +1,6 @@ use std::num::NonZeroU32; -use data_encoding::HEXUPPER; -use ring::{digest, rand::{SecureRandom, SystemRandom}, pbkdf2, error::Unspecified}; +use data_encoding::BASE64; +use ring::{digest, rand::{SecureRandom, SystemRandom}, pbkdf2}; use tokio::task::JoinError; use crate::dto::hash_dtos::HashResult; @@ -18,7 +18,7 @@ pub async fn generate_multiple_random_token_with_rng(amount: u8) -> Result {HEXUPPER.encode(&token_arr)}, + Ok(()) => {BASE64.encode(&token_arr)}, Err(_e) => { panic!("Failed to generate random token for some reason.") } }}; tokens.push(tokio::spawn(future_token)); @@ -33,13 +33,36 @@ 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(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{ // Get output length from a sha512 hash const CREDENTIAL_LEN: usize = digest::SHA512_OUTPUT_LEN; let n_iter = NonZeroU32::new(SALT_ROUNDS).unwrap(); 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]; // Fill array with random-generated salt @@ -60,21 +83,6 @@ pub fn hash_password(password: &String) -> HashResult{ &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(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()) + // Return an object containing the salt and the hash + HashResult::new(BASE64.encode(&salt), BASE64.encode(&pbkdf2_hash)) } \ No newline at end of file