Fixed user service actix backend

This commit is contained in:
franklinblanco 2022-08-12 19:56:59 -04:00
parent 3de2319fd7
commit 5b86b21734
12 changed files with 49 additions and 31 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@ -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"] }

View File

@ -0,0 +1 @@
-- Add migration script here

View File

@ -3,23 +3,11 @@ use std::collections::HashMap;
use sqlx::{MySqlPool};
pub async fn start_database_connection(env_vars: &HashMap<String, String>) -> Result<MySqlPool, sqlx::Error>{
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){

View File

@ -10,6 +10,6 @@ pub async fn insert_user(conn: &MySqlPool, user_to_insert: &User) -> Result<MySq
pub async fn find_user_by_email(conn: &MySqlPool, email: &String, app: &String) -> Result<User, sqlx::Error>{
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<User, sqlx::Error> {
pub async fn find_user_by_id(conn: &MySqlPool, id: &i32) -> Result<User, sqlx::Error> {
sqlx::query_file_as!(User, "sql/schema/user/find_with_id.sql", id).fetch_one(conn).await
}

View File

@ -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<NaiveDateTime>,
#[serde(skip_serializing)]
pub last_updated: Option<NaiveDateTime>,
pub auth_token: String,
pub refresh_token: String

View File

@ -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<NaiveDateTime>,
#[serde(skip_serializing_if = "Option::is_none")]
pub last_updated: Option<NaiveDateTime>,
pub app: String,
pub email: String,
pub name: String,
#[serde(skip_serializing)]
pub password: String,
#[serde(skip_serializing)]
pub salt: String
}
impl User {

View File

@ -1,3 +1,4 @@
#[forbid(unsafe_code)]
mod r#do; mod dao;
mod routes; mod service;
mod util; mod dto;

View File

@ -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.");
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.");

View File

@ -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()));

View File

@ -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<UserForCreationDto>, db_conn: Data<Arc<MySqlPool>>) -> HttpResponse {
@ -60,13 +61,15 @@ pub async fn create_user(incoming_user: web::Json<UserForCreationDto>, 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<UserForLoginDto>, db_conn: Data<Arc<MySqlPool>>) -> HttpResponse {
let mut message_resources: Vec<MessageResourceDto> = Vec::new();
@ -81,7 +84,7 @@ pub async fn authenticate_user_with_password(incoming_user: web::Json<UserForLog
// If user exists get it, if it doesn't blow up to the client
let persisted_user = match find_user_by_email(&db_conn, &incoming_user_obj.email, &incoming_user_obj.app).await {
Ok(rs) => {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<UserForLog
}
// 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 tokens = match generate_multiple_random_token_with_rng(2).await {
Ok(generated_tokens) => 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<i32>, db_conn: Data<Arc<MySqlPool>>) -> HttpResponse{
let mut message_resources: Vec<MessageResourceDto> = 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<i32>, db_conn: Data<Arc<MySqlPool>>) -> HttpResponse{
let mut message_resources: Vec<MessageResourceDto> = Vec::new();
@ -168,8 +189,7 @@ pub async fn refresh_auth_token(request: HttpRequest, user_id: web::Path<i32>, 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));
}}},

View File

@ -18,7 +18,7 @@ pub async fn generate_multiple_random_token_with_rng(amount: u8) -> Result<Vec<S
let future_token = async move {
let mut token_arr = [0u8; digest::SHA512_OUTPUT_LEN];
match cloned_rng.fill(&mut token_arr){
Ok(()) => {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