From 5b86b2173480297a3504d75b4d48369b434b5974 Mon Sep 17 00:00:00 2001 From: franklinblanco Date: Fri, 12 Aug 2022 19:56:59 -0400 Subject: [PATCH] Fixed user service actix backend --- .DS_Store | Bin 6148 -> 6148 bytes Cargo.toml | 2 +- migrations/20220726034843_aaa.sql | 1 + src/dao/main_dao.rs | 18 +++----------- src/dao/user_dao.rs | 2 +- src/do/token.rs | 3 +++ src/do/user.rs | 4 +++ src/main.rs | 1 + src/resources/error_messages.rs | 4 ++- src/routes/main_router.rs | 1 - src/routes/user_routes.rs | 40 ++++++++++++++++++++++-------- src/util/hasher.rs | 4 +-- 12 files changed, 49 insertions(+), 31 deletions(-) create mode 100644 migrations/20220726034843_aaa.sql diff --git a/.DS_Store b/.DS_Store index 0f320e976a39557fe6a315df64650b3b7ab4518c..0febaa621e02705f181609b1508dfe9ae6c192a2 100644 GIT binary patch delta 49 zcmZoMXfc@J&&a(oU^g=(_hufJSVm!HhGK>yhUBu~qP(2^ymSTz2FA^4tldnT**X65 F0{~A14RHVf delta 36 scmZoMXfc@J&&ahgU^g=(*Jd7;SjNe#S$Q}2uvRloY!KVb&heKY0L}pmL;wH) diff --git a/Cargo.toml b/Cargo.toml index 4ee5ce2..05f9abf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,4 +19,4 @@ futures-util = "0.3" [build-dependencies] dotenv = "0.15.0" sqlx = { version = "0.6.0", features = [ "runtime-tokio-rustls", "mysql", "chrono" ] } -tokio = { version = "1", features = ["full"] } +tokio = { version = "1", features = ["full"] } \ No newline at end of file diff --git a/migrations/20220726034843_aaa.sql b/migrations/20220726034843_aaa.sql new file mode 100644 index 0000000..8ddc1d3 --- /dev/null +++ b/migrations/20220726034843_aaa.sql @@ -0,0 +1 @@ +-- Add migration script here diff --git a/src/dao/main_dao.rs b/src/dao/main_dao.rs index 7b27013..e4c8296 100644 --- a/src/dao/main_dao.rs +++ b/src/dao/main_dao.rs @@ -3,23 +3,11 @@ use std::collections::HashMap; use sqlx::{MySqlPool}; pub async fn start_database_connection(env_vars: &HashMap) -> Result{ - let db_user = match env_vars.get("DB_USER") { + let db_url = match env_vars.get("DATABASE_URL") { Some(str) => str, - None => panic!("DB_USER env var not found") + None => panic!("DATABASE_URL env var not found") }; - let db_pass = match env_vars.get("DB_PASS") { - Some(str) => str, - None => panic!("DB_PASS env var not found") - }; - let db_host = match env_vars.get("DB_HOST") { - Some(str) => str, - None => panic!("DB_HOST env var not found") - }; - let db_database_name = match env_vars.get("DB_DATABASE_NAME") { - Some(str) => str, - 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 = &db_url; sqlx::MySqlPool::connect(&formatted_db_url).await } pub async fn run_all_migrations(conn: &MySqlPool){ diff --git a/src/dao/user_dao.rs b/src/dao/user_dao.rs index 2d208cc..282f81f 100644 --- a/src/dao/user_dao.rs +++ b/src/dao/user_dao.rs @@ -10,6 +10,6 @@ pub async fn insert_user(conn: &MySqlPool, user_to_insert: &User) -> Result Result{ sqlx::query_file_as!(User, "sql/schema/user/find_with_email.sql", email, app).fetch_one(conn).await } -pub async fn _find_user_by_id(conn: &MySqlPool, id: &i32) -> Result { +pub async fn find_user_by_id(conn: &MySqlPool, id: &i32) -> Result { sqlx::query_file_as!(User, "sql/schema/user/find_with_id.sql", id).fetch_one(conn).await } \ No newline at end of file diff --git a/src/do/token.rs b/src/do/token.rs index 6d65dc6..041a667 100644 --- a/src/do/token.rs +++ b/src/do/token.rs @@ -6,9 +6,12 @@ pub const REFRESH_TOKEN_EXPIRATION_TIME_IN_DAYS: i32 = 20; #[derive(Serialize, Deserialize)] pub struct Token { + #[serde(skip_serializing)] pub id: i32, pub user_id: i32, + #[serde(skip_serializing)] pub time_created: Option, + #[serde(skip_serializing)] pub last_updated: Option, pub auth_token: String, pub refresh_token: String diff --git a/src/do/user.rs b/src/do/user.rs index 082fcea..3b0b8a0 100644 --- a/src/do/user.rs +++ b/src/do/user.rs @@ -6,12 +6,16 @@ use crate::dto::user_dtos::UserForCreationDto; #[derive(Serialize, Deserialize, Debug)] pub struct User{ pub id: i32, + #[serde(skip_serializing_if = "Option::is_none")] pub time_created: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub last_updated: Option, pub app: String, pub email: String, pub name: String, + #[serde(skip_serializing)] pub password: String, + #[serde(skip_serializing)] pub salt: String } impl User { diff --git a/src/main.rs b/src/main.rs index 52453a9..1fc3c60 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +#[forbid(unsafe_code)] mod r#do; mod dao; mod routes; mod service; mod util; mod dto; diff --git a/src/resources/error_messages.rs b/src/resources/error_messages.rs index 47e7f4a..04bb887 100644 --- a/src/resources/error_messages.rs +++ b/src/resources/error_messages.rs @@ -19,4 +19,6 @@ pub const ERROR_INCORRECT_TOKEN: (&str, &str) = ("ERROR.INCORRECT_TOKEN", "The t pub const ERROR_MISSING_TOKEN: (&str, &str) = ("ERROR.MISSING_TOKEN", "No token supplied."); -pub const ERROR_EXPIRED_TOKEN: (&str, &str) = ("ERROR.EXPIRED_TOKEN", "The token you have supplied is expired."); \ No newline at end of file +pub const ERROR_EXPIRED_TOKEN: (&str, &str) = ("ERROR.EXPIRED_TOKEN", "The token you have supplied is expired."); + +pub const ERROR_CREATING_TOKEN: (&str, &str) = ("ERROR.CREATING_TOKEN", "The server had an error creating the auth tokens."); \ No newline at end of file diff --git a/src/routes/main_router.rs b/src/routes/main_router.rs index 29141b2..055695f 100644 --- a/src/routes/main_router.rs +++ b/src/routes/main_router.rs @@ -27,7 +27,6 @@ pub async fn start_all_routes(after_startup_fn_call: &dyn Fn(), state: SharedSta None => {panic!("HOST_PORT env variable not found.");}, }; - // Extract variables to be put into shared app state & clone them let db_conn_state = web::Data::new(Arc::new(state.db_conn)); let env_vars_state = web::Data::new(Mutex::new(state.env_vars.clone())); diff --git a/src/routes/user_routes.rs b/src/routes/user_routes.rs index 1ab0346..44efe42 100644 --- a/src/routes/user_routes.rs +++ b/src/routes/user_routes.rs @@ -1,11 +1,12 @@ -use core::panic; use std::{sync::{Arc}}; use actix_web::{web::{self, Data}, HttpResponse, post, patch, HttpRequest}; use chrono::{Utc}; use sqlx::{MySqlPool}; -use crate::{r#do::user::User, dao::{user_dao::{insert_user, find_user_by_email}, token_dao::{insert_token, self, update_token_with_id}}, 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, ERROR_INVALID_TOKEN, ERROR_MISSING_TOKEN, ERROR_INCORRECT_TOKEN, ERROR_EXPIRED_TOKEN}, r#do::token::AUTH_TOKEN_EXPIRATION_TIME_IN_DAYS, r#do::token::REFRESH_TOKEN_EXPIRATION_TIME_IN_DAYS}; +use crate::{r#do::user::User, dao::{user_dao::{insert_user, find_user_by_email, find_user_by_id}, token_dao::{insert_token, self, update_token_with_id}}, 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, ERROR_INVALID_TOKEN, ERROR_MISSING_TOKEN, ERROR_INCORRECT_TOKEN, ERROR_EXPIRED_TOKEN, ERROR_CREATING_TOKEN}, r#do::token::AUTH_TOKEN_EXPIRATION_TIME_IN_DAYS, r#do::token::REFRESH_TOKEN_EXPIRATION_TIME_IN_DAYS}; + + #[post("/user")] pub async fn create_user(incoming_user: web::Json, db_conn: Data>) -> HttpResponse { @@ -60,13 +61,15 @@ pub async fn create_user(incoming_user: web::Json, db_conn: // Insert token in DB match insert_token(&db_conn, &token_to_insert).await{ Ok(resultrs) => {token_to_insert.id = resultrs.last_insert_id() as i32}, - Err(e) => {panic!("{e}")} + Err(_e) => {return HttpResponse::InternalServerError().finish()} } // All good? Send an OK! HttpResponse::Ok().json(web::Json(token_to_insert)) } + + #[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(); @@ -81,7 +84,7 @@ pub async fn authenticate_user_with_password(incoming_user: web::Json {rs}, - Err(_e) => { + Err(_) => { message_resources.push(MessageResourceDto::new_from_error_message(ERROR_USER_DOES_NOT_EXIST)); return HttpResponse::NotFound().json(web::Json(message_resources)); }}; @@ -97,21 +100,32 @@ pub async fn authenticate_user_with_password(incoming_user: web::Json generated_tokens, + Err(_) => { + message_resources.push(MessageResourceDto::new_from_error_message(ERROR_CREATING_TOKEN)); + return HttpResponse::InternalServerError().json(web::Json(message_resources)) + } + }; 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() - ); + ); //TODO: I don't like these panics // Insert token in DB match insert_token(&db_conn, &token_to_insert).await{ Ok(resultrs) => {token_to_insert.id = resultrs.last_insert_id() as i32}, - Err(e) => {panic!("{e}")} + Err(_) => { + message_resources.push(MessageResourceDto::new_from_error_message(ERROR_CREATING_TOKEN)); + return HttpResponse::InternalServerError().json(message_resources) + } }; HttpResponse::Ok().json(token_to_insert) } + + #[post("/user/auth/token/{user_id}")] pub async fn authenticate_user_with_auth_token(request: HttpRequest, user_id: web::Path, db_conn: Data>) -> HttpResponse{ let mut message_resources: Vec = Vec::new(); @@ -140,7 +154,12 @@ pub async fn authenticate_user_with_auth_token(request: HttpRequest, user_id: we let now = Utc::now().naive_utc(); match token.auth_token == auth_token{ true if token.last_updated.unwrap().signed_duration_since(now).num_days() < - AUTH_TOKEN_EXPIRATION_TIME_IN_DAYS.into() => { return HttpResponse::Ok().finish(); }, + AUTH_TOKEN_EXPIRATION_TIME_IN_DAYS.into() => { + match find_user_by_id(&db_conn, &user_id).await { + Ok(persisted_user) => return HttpResponse::Ok().json(web::Json(persisted_user)), + Err(_) => {return HttpResponse::NotFound().json(MessageResourceDto::new_from_error_message(ERROR_USER_DOES_NOT_EXIST))} + }; + }, true => { message_resources.push(MessageResourceDto::new_from_error_message(ERROR_EXPIRED_TOKEN)); return HttpResponse::Unauthorized().json(web::Json(message_resources)); @@ -160,6 +179,8 @@ pub async fn authenticate_user_with_auth_token(request: HttpRequest, user_id: we }}; } + + #[patch("/user/refresh/{user_id}")] pub async fn refresh_auth_token(request: HttpRequest, user_id: web::Path, db_conn: Data>) -> HttpResponse{ let mut message_resources: Vec = Vec::new(); @@ -168,8 +189,7 @@ pub async fn refresh_auth_token(request: HttpRequest, user_id: web::Path, d Some(token) => { match token.to_str(){ Ok(value) => {value}, - Err(e) => { - println!("{}", e); + Err(_) => { message_resources.push(MessageResourceDto::new_from_error_message(ERROR_INVALID_TOKEN)); return HttpResponse::BadRequest().json(web::Json(message_resources)); }}}, diff --git a/src/util/hasher.rs b/src/util/hasher.rs index 24b119e..d0827f5 100644 --- a/src/util/hasher.rs +++ b/src/util/hasher.rs @@ -18,7 +18,7 @@ pub async fn generate_multiple_random_token_with_rng(amount: u8) -> Result {BASE64.encode(&token_arr)}, + Ok(()) => {BASE64.encode(&token_arr)}, //TODO: Remove this panic, make your own error and fix this Err(_e) => { panic!("Failed to generate random token for some reason.") } }}; tokens.push(tokio::spawn(future_token)); @@ -68,7 +68,7 @@ pub fn hash_password(password: &String) -> HashResult{ // Fill array with random-generated salt match rng.fill(&mut salt){ Ok(()) => {}, - Err(_e) => {panic!("Failed to generate random salt for some reason.")} + Err(_e) => {panic!("Failed to generate random salt for some reason.")} //TODO: Remove this panic, make your own error and fix this } // Create empty 64-bit byte array for the hash + salt