Fixed user service actix backend
This commit is contained in:
parent
3de2319fd7
commit
5b86b21734
@ -19,4 +19,4 @@ futures-util = "0.3"
|
|||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
dotenv = "0.15.0"
|
dotenv = "0.15.0"
|
||||||
sqlx = { version = "0.6.0", features = [ "runtime-tokio-rustls", "mysql", "chrono" ] }
|
sqlx = { version = "0.6.0", features = [ "runtime-tokio-rustls", "mysql", "chrono" ] }
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
1
migrations/20220726034843_aaa.sql
Normal file
1
migrations/20220726034843_aaa.sql
Normal file
@ -0,0 +1 @@
|
|||||||
|
-- Add migration script here
|
@ -3,23 +3,11 @@ use std::collections::HashMap;
|
|||||||
use sqlx::{MySqlPool};
|
use sqlx::{MySqlPool};
|
||||||
|
|
||||||
pub async fn start_database_connection(env_vars: &HashMap<String, String>) -> Result<MySqlPool, sqlx::Error>{
|
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,
|
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") {
|
let formatted_db_url = &db_url;
|
||||||
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}");
|
|
||||||
sqlx::MySqlPool::connect(&formatted_db_url).await
|
sqlx::MySqlPool::connect(&formatted_db_url).await
|
||||||
}
|
}
|
||||||
pub async fn run_all_migrations(conn: &MySqlPool){
|
pub async fn run_all_migrations(conn: &MySqlPool){
|
||||||
|
@ -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>{
|
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
|
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
|
sqlx::query_file_as!(User, "sql/schema/user/find_with_id.sql", id).fetch_one(conn).await
|
||||||
}
|
}
|
@ -6,9 +6,12 @@ pub const REFRESH_TOKEN_EXPIRATION_TIME_IN_DAYS: i32 = 20;
|
|||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct Token {
|
pub struct Token {
|
||||||
|
#[serde(skip_serializing)]
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub user_id: i32,
|
pub user_id: i32,
|
||||||
|
#[serde(skip_serializing)]
|
||||||
pub time_created: Option<NaiveDateTime>,
|
pub time_created: Option<NaiveDateTime>,
|
||||||
|
#[serde(skip_serializing)]
|
||||||
pub last_updated: Option<NaiveDateTime>,
|
pub last_updated: Option<NaiveDateTime>,
|
||||||
pub auth_token: String,
|
pub auth_token: String,
|
||||||
pub refresh_token: String
|
pub refresh_token: String
|
||||||
|
@ -6,12 +6,16 @@ use crate::dto::user_dtos::UserForCreationDto;
|
|||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
pub struct User{
|
pub struct User{
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub time_created: Option<NaiveDateTime>,
|
pub time_created: Option<NaiveDateTime>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub last_updated: Option<NaiveDateTime>,
|
pub last_updated: Option<NaiveDateTime>,
|
||||||
pub app: String,
|
pub app: String,
|
||||||
pub email: String,
|
pub email: String,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
#[serde(skip_serializing)]
|
||||||
pub password: String,
|
pub password: String,
|
||||||
|
#[serde(skip_serializing)]
|
||||||
pub salt: String
|
pub salt: String
|
||||||
}
|
}
|
||||||
impl User {
|
impl User {
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
#[forbid(unsafe_code)]
|
||||||
mod r#do; mod dao;
|
mod r#do; mod dao;
|
||||||
mod routes; mod service;
|
mod routes; mod service;
|
||||||
mod util; mod dto;
|
mod util; mod dto;
|
||||||
|
@ -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_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.");
|
@ -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.");},
|
None => {panic!("HOST_PORT env variable not found.");},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// Extract variables to be put into shared app state & clone them
|
// Extract variables to be put into shared app state & clone them
|
||||||
let db_conn_state = web::Data::new(Arc::new(state.db_conn));
|
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()));
|
let env_vars_state = web::Data::new(Mutex::new(state.env_vars.clone()));
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
use core::panic;
|
|
||||||
use std::{sync::{Arc}};
|
use std::{sync::{Arc}};
|
||||||
|
|
||||||
use actix_web::{web::{self, Data}, HttpResponse, post, patch, HttpRequest};
|
use actix_web::{web::{self, Data}, HttpResponse, post, patch, HttpRequest};
|
||||||
use chrono::{Utc};
|
use chrono::{Utc};
|
||||||
use sqlx::{MySqlPool};
|
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")]
|
#[post("/user")]
|
||||||
pub async fn create_user(incoming_user: web::Json<UserForCreationDto>, db_conn: Data<Arc<MySqlPool>>) -> HttpResponse {
|
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
|
// Insert token in DB
|
||||||
match insert_token(&db_conn, &token_to_insert).await{
|
match insert_token(&db_conn, &token_to_insert).await{
|
||||||
Ok(resultrs) => {token_to_insert.id = resultrs.last_insert_id() as i32},
|
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!
|
// All good? Send an OK!
|
||||||
HttpResponse::Ok().json(web::Json(token_to_insert))
|
HttpResponse::Ok().json(web::Json(token_to_insert))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[post("/user/auth/password")]
|
#[post("/user/auth/password")]
|
||||||
pub async fn authenticate_user_with_password(incoming_user: web::Json<UserForLoginDto>, db_conn: Data<Arc<MySqlPool>>) -> HttpResponse {
|
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();
|
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
|
// 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 {
|
let persisted_user = match find_user_by_email(&db_conn, &incoming_user_obj.email, &incoming_user_obj.app).await {
|
||||||
Ok(rs) => {rs},
|
Ok(rs) => {rs},
|
||||||
Err(_e) => {
|
Err(_) => {
|
||||||
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::NotFound().json(web::Json(message_resources));
|
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.
|
// 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,
|
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(0).expect("Error. Token doesn't exist in list.").to_string(),
|
||||||
tokens.get(1).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
|
// Insert token in DB
|
||||||
match insert_token(&db_conn, &token_to_insert).await{
|
match insert_token(&db_conn, &token_to_insert).await{
|
||||||
Ok(resultrs) => {token_to_insert.id = resultrs.last_insert_id() as i32},
|
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)
|
HttpResponse::Ok().json(token_to_insert)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[post("/user/auth/token/{user_id}")]
|
#[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{
|
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();
|
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();
|
let now = Utc::now().naive_utc();
|
||||||
match token.auth_token == auth_token{
|
match token.auth_token == auth_token{
|
||||||
true if token.last_updated.unwrap().signed_duration_since(now).num_days() <
|
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 => {
|
true => {
|
||||||
message_resources.push(MessageResourceDto::new_from_error_message(ERROR_EXPIRED_TOKEN));
|
message_resources.push(MessageResourceDto::new_from_error_message(ERROR_EXPIRED_TOKEN));
|
||||||
return HttpResponse::Unauthorized().json(web::Json(message_resources));
|
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}")]
|
#[patch("/user/refresh/{user_id}")]
|
||||||
pub async fn refresh_auth_token(request: HttpRequest, user_id: web::Path<i32>, db_conn: Data<Arc<MySqlPool>>) -> HttpResponse{
|
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();
|
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) => {
|
Some(token) => {
|
||||||
match token.to_str(){
|
match token.to_str(){
|
||||||
Ok(value) => {value},
|
Ok(value) => {value},
|
||||||
Err(e) => {
|
Err(_) => {
|
||||||
println!("{}", e);
|
|
||||||
message_resources.push(MessageResourceDto::new_from_error_message(ERROR_INVALID_TOKEN));
|
message_resources.push(MessageResourceDto::new_from_error_message(ERROR_INVALID_TOKEN));
|
||||||
return HttpResponse::BadRequest().json(web::Json(message_resources));
|
return HttpResponse::BadRequest().json(web::Json(message_resources));
|
||||||
}}},
|
}}},
|
||||||
|
@ -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(()) => {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.") }
|
Err(_e) => { panic!("Failed to generate random token for some reason.") }
|
||||||
}};
|
}};
|
||||||
tokens.push(tokio::spawn(future_token));
|
tokens.push(tokio::spawn(future_token));
|
||||||
@ -68,7 +68,7 @@ pub fn hash_password(password: &String) -> HashResult{
|
|||||||
// Fill array with random-generated salt
|
// Fill array with random-generated salt
|
||||||
match rng.fill(&mut salt){
|
match rng.fill(&mut salt){
|
||||||
Ok(()) => {},
|
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
|
// Create empty 64-bit byte array for the hash + salt
|
||||||
|
Loading…
Reference in New Issue
Block a user