fixed user service flaws
This commit is contained in:
parent
0574c59006
commit
2b9b0fe83e
@ -2,7 +2,6 @@
|
||||
name = "user-svc-actix"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
build = "build.rs"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
@ -16,7 +15,4 @@ ring = "0.16.20"
|
||||
data-encoding = "2.3.2"
|
||||
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"] }
|
||||
dev-dtos = { git = "https://backend:Eo1n1TPsyWV7wwo9uFwgUJGKKheMM6paM2mDkVPA4zqkh5dt6Q6XPkbtojzYQudQsM84vSwKmhHHTPjyn535d6NLBmA3meeGj0Gb8if4sceAwvySdmzedg5mN2P5zzQt@gitea.blancoinfante.com/blancoinfante_backend/dev-dtos-rust.git" }
|
28
build.rs
28
build.rs
@ -1,28 +0,0 @@
|
||||
extern crate dotenv;
|
||||
|
||||
use std::{env, collections::HashMap};
|
||||
|
||||
use sqlx::Connection;
|
||||
use dotenv::dotenv;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main(){
|
||||
println!("cargo:rerun-if-changed=migrations");
|
||||
dotenv().ok();
|
||||
let mut dotenv_vars: HashMap<String, String> = HashMap::new();
|
||||
for (key, val) in env::vars() {
|
||||
dotenv_vars.insert(key, val);
|
||||
}
|
||||
let db_url = match dotenv_vars.get("DATABASE_URL") {
|
||||
Some(var) => {var},
|
||||
None => {panic!("DATABASE_URL env var not found, set it!")}
|
||||
};
|
||||
let mut conn = match sqlx::MySqlConnection::connect(&db_url).await {
|
||||
Ok(res) => {res},
|
||||
Err(e) => {panic!("{}", e)}
|
||||
};
|
||||
match sqlx::migrate!("./migrations").run(&mut conn).await {
|
||||
Ok(()) => {println!("{}", "Successfully ran migrations.")},
|
||||
Err(error) => {panic!("{error}")}
|
||||
};
|
||||
}
|
@ -3,7 +3,8 @@ CREATE TABLE IF NOT EXISTS user (
|
||||
time_created DATETIME,
|
||||
last_updated DATETIME,
|
||||
app VARCHAR(255) NOT NULL,
|
||||
email VARCHAR(255) NOT NULL,
|
||||
credential VARCHAR(255) NOT NULL,
|
||||
credential_type VARCHAR(10) NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
password TEXT NOT NULL,
|
||||
salt TEXT NOT NULL
|
||||
|
@ -1 +0,0 @@
|
||||
-- Add migration script here
|
@ -1,8 +0,0 @@
|
||||
CREATE TABLE IF NOT EXISTS token (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id INT NOT NULL,
|
||||
time_created TIMESTAMP NOT NULL,
|
||||
last_updated TIMESTAMP NOT NULL,
|
||||
auth_token TEXT NOT NULL,
|
||||
refresh_token TEXT NOT NULL
|
||||
)
|
@ -1,9 +0,0 @@
|
||||
CREATE TABLE IF NOT EXISTS user (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
time_created TIMESTAMP NOT NULL,
|
||||
last_updated TIMESTAMP NOT NULL,
|
||||
email VARCHAR(255) NOT NULL,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
password TEXT NOT NULL,
|
||||
salt VARCHAR(255) NOT NULL
|
||||
)
|
5
sql/schema/user/find_with_credential.sql
Normal file
5
sql/schema/user/find_with_credential.sql
Normal file
@ -0,0 +1,5 @@
|
||||
SELECT *
|
||||
FROM user
|
||||
WHERE user.credential = ? AND
|
||||
user.credential_type = ? AND
|
||||
user.app = ?
|
@ -1,4 +0,0 @@
|
||||
SELECT *
|
||||
FROM user
|
||||
WHERE user.email = ? AND
|
||||
user.app = ?
|
@ -1,3 +1,3 @@
|
||||
INSERT INTO user
|
||||
(id, time_created, last_updated, app, email, name, password, salt) values
|
||||
(NULL, NOW(), NOW(), ?, ?, ?, ?, ?)
|
||||
(id, time_created, last_updated, app, credential, credential_type, name, password, salt) values
|
||||
(NULL, NOW(), NOW(), ?, ?, ?, ?, ?, ?)
|
@ -1,9 +1,7 @@
|
||||
use dev_dtos::domain::user::token::{AUTH_TOKEN_EXPIRATION_TIME_IN_DAYS, Token, REFRESH_TOKEN_EXPIRATION_TIME_IN_DAYS};
|
||||
use sqlx::MySqlPool;
|
||||
use sqlx::{mysql::MySqlQueryResult};
|
||||
|
||||
use crate::r#do::token::Token;
|
||||
use crate::r#do::token::{AUTH_TOKEN_EXPIRATION_TIME_IN_DAYS, REFRESH_TOKEN_EXPIRATION_TIME_IN_DAYS};
|
||||
|
||||
pub async fn insert_token(conn: &MySqlPool, 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
|
||||
}
|
||||
|
@ -1,14 +1,13 @@
|
||||
use dev_dtos::domain::user::user::User;
|
||||
use sqlx::{mysql::MySqlQueryResult, MySqlPool};
|
||||
|
||||
use crate::r#do::user::User;
|
||||
|
||||
pub async fn insert_user(conn: &MySqlPool, user_to_insert: &User) -> Result<MySqlQueryResult, sqlx::Error>{
|
||||
sqlx::query_file!("sql/schema/user/insert.sql",
|
||||
user_to_insert.app, user_to_insert.email, user_to_insert.name, user_to_insert.password, user_to_insert.salt)
|
||||
user_to_insert.app, user_to_insert.credential, user_to_insert.credential_type, user_to_insert.name, user_to_insert.password, user_to_insert.salt)
|
||||
.execute(conn).await
|
||||
}
|
||||
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_credential(conn: &MySqlPool, credential: &String, credential_type: &String, app: &String) -> Result<User, sqlx::Error>{
|
||||
sqlx::query_file_as!(User, "sql/schema/user/find_with_credential.sql", credential, credential_type, app).fetch_one(conn).await
|
||||
}
|
||||
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
|
||||
|
@ -1,3 +1 @@
|
||||
pub mod shared_state;
|
||||
pub mod user;
|
||||
pub mod token;
|
||||
pub mod shared_state;
|
@ -1,31 +0,0 @@
|
||||
use chrono::NaiveDateTime;
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
pub const AUTH_TOKEN_EXPIRATION_TIME_IN_DAYS:i32 = 1;
|
||||
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
|
||||
}
|
||||
|
||||
impl Token{
|
||||
pub fn new(user_id: i32, auth_token: String, refresh_token: String) -> Token{
|
||||
Token {
|
||||
id: 0,
|
||||
user_id,
|
||||
time_created: None,
|
||||
last_updated: None,
|
||||
auth_token,
|
||||
refresh_token
|
||||
}
|
||||
}
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
use chrono::{NaiveDateTime};
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
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 {
|
||||
pub fn _new() -> User {
|
||||
User { id: 0,
|
||||
time_created: None, // This will be automatically generated from the database
|
||||
last_updated: None, // This will be automatically generated from the database
|
||||
app: "".to_string(),
|
||||
email: "".to_string(),
|
||||
name:"".to_string(),
|
||||
password:"".to_string(),
|
||||
salt: "".to_string() }
|
||||
}
|
||||
pub fn new_for_creation(incoming_user: &UserForCreationDto) -> User{
|
||||
User { id: 0,
|
||||
time_created: None, // This will be automatically generated from the database
|
||||
last_updated: None, // This will be automatically generated from the database
|
||||
app: incoming_user.app.to_string(),
|
||||
email: incoming_user.email.to_string(),
|
||||
name: incoming_user.name.to_string(),
|
||||
password: incoming_user.password.to_string(),
|
||||
salt: "".to_string() }
|
||||
}
|
||||
}
|
@ -1,3 +1,2 @@
|
||||
pub mod user_dtos;
|
||||
pub mod message_resources_dtos;
|
||||
pub mod hash_dtos;
|
@ -1,21 +0,0 @@
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct UserForCreationDto{
|
||||
pub app: String,
|
||||
pub email: String,
|
||||
pub password: String,
|
||||
pub name: String
|
||||
}
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct UserForLoginDto{
|
||||
pub app: String,
|
||||
pub email: String,
|
||||
pub password: String
|
||||
}
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct UserForAuthenticationDto{
|
||||
pub app: String,
|
||||
pub email: String,
|
||||
pub token: String
|
||||
}
|
@ -3,6 +3,8 @@
|
||||
|
||||
pub const ERROR_INVALID_EMAIL: (&str, &str) = ("ERROR.INVALID_EMAIL", "Invalid email. Needs to be at least 4 characters, at most 254 and correctly formatted.");
|
||||
|
||||
pub const ERROR_INVALID_PHONE_NUMBER: (&str, &str) = ("ERROR.INVALID_PHONE_NUMBER", "Invalid Phone number. Needs to be 10 characters.");
|
||||
|
||||
pub const ERROR_INVALID_NAME: (&str, &str) = ("ERROR.INVALID_NAME", "Invalid name. Names should have at least 4 characters in length and at most 254.");
|
||||
|
||||
pub const ERROR_INVALID_PASSWORD: (&str, &str) = ("ERROR.INVALID_PASSWORD", "Invalid password. Password should have at least 8 characters and at most 128.");
|
||||
|
@ -2,11 +2,10 @@ use std::{sync::{Arc}};
|
||||
|
||||
use actix_web::{web::{self, Data}, HttpResponse, post, patch, HttpRequest};
|
||||
use chrono::{Utc};
|
||||
use dev_dtos::{dtos::user::user_dtos::{UserForLoginDto, UserForCreationDto}, domain::user::{user::User, token::{Token, AUTH_TOKEN_EXPIRATION_TIME_IN_DAYS, REFRESH_TOKEN_EXPIRATION_TIME_IN_DAYS}}};
|
||||
use sqlx::{MySqlPool};
|
||||
|
||||
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};
|
||||
|
||||
|
||||
use crate::{dto::message_resources_dtos::MessageResourceDto, resources::error_messages::{ERROR_USER_ALREADY_EXISTS, ERROR_USER_DOES_NOT_EXIST, ERROR_INCORRECT_TOKEN, ERROR_INVALID_TOKEN, ERROR_MISSING_TOKEN, ERROR_EXPIRED_TOKEN, ERROR_CREATING_TOKEN, ERROR_PASSWORD_INCORRECT}, dao::{user_dao::{find_user_by_credential, insert_user, find_user_by_id}, token_dao::{insert_token, update_token_with_id, self}}, validation::user_validator, util::hasher::{self, generate_multiple_random_token_with_rng}};
|
||||
|
||||
#[post("/user")]
|
||||
pub async fn create_user(incoming_user: web::Json<UserForCreationDto>, db_conn: Data<Arc<MySqlPool>>) -> HttpResponse {
|
||||
@ -24,7 +23,7 @@ pub async fn create_user(incoming_user: web::Json<UserForCreationDto>, db_conn:
|
||||
user_validator::validate_user_for_creation(incoming_user_obj, &mut message_resources);
|
||||
|
||||
// Find if user exists
|
||||
match find_user_by_email(&db_conn, &user_to_insert.email, &user_to_insert.app).await{
|
||||
match find_user_by_credential(&db_conn, &user_to_insert.credential, &user_to_insert.credential_type, &user_to_insert.app).await{
|
||||
Ok(_usr) => {
|
||||
message_resources.push(MessageResourceDto::new_from_error_message(ERROR_USER_ALREADY_EXISTS));
|
||||
return HttpResponse::BadRequest().json(web::Json(message_resources));
|
||||
@ -82,7 +81,7 @@ pub async fn authenticate_user_with_password(incoming_user: web::Json<UserForLog
|
||||
if message_resources.len() > 0 { return HttpResponse::BadRequest().json(web::Json(message_resources)); }
|
||||
|
||||
// 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_credential(&db_conn, &incoming_user_obj.credential, &incoming_user_obj.credential_type.to_string(), &incoming_user_obj.app).await {
|
||||
Ok(rs) => {rs},
|
||||
Err(_) => {
|
||||
message_resources.push(MessageResourceDto::new_from_error_message(ERROR_USER_DOES_NOT_EXIST));
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
dto::{ user_dtos::{UserForCreationDto, UserForLoginDto}, message_resources_dtos::MessageResourceDto },
|
||||
resources::{ variable_lengths::*, error_messages::* }
|
||||
};
|
||||
use dev_dtos::{dtos::user::user_dtos::{UserForCreationDto, UserForLoginDto}, domain::user::credential_type::CredentialType};
|
||||
|
||||
use crate::{resources::{variable_lengths::{MAX_EMAIL_LENGTH, MIN_EMAIL_LENGTH, MIN_NAME_LENGTH, MAX_NAME_LENGTH, MIN_PASSWORD_LENGTH, MAX_PASSWORD_LENGTH}, error_messages::{ERROR_INVALID_NAME, ERROR_INVALID_EMAIL, ERROR_INVALID_PASSWORD, ERROR_INVALID_PHONE_NUMBER}}, dto::message_resources_dtos::MessageResourceDto};
|
||||
|
||||
|
||||
fn validate_user_email(email: &String) -> bool {
|
||||
email.len() >= MIN_EMAIL_LENGTH.into() &&
|
||||
@ -9,6 +9,10 @@ fn validate_user_email(email: &String) -> bool {
|
||||
email.contains('@') &&
|
||||
email.contains('.')
|
||||
}
|
||||
fn validate_user_phone_number(email: &String) -> bool {
|
||||
email.len() >= CredentialType::get_max_length(&CredentialType::PhoneNumber) &&
|
||||
email.len() <= MAX_EMAIL_LENGTH.into()
|
||||
}
|
||||
fn validate_user_name(name: &String) -> bool {
|
||||
name.len() >= MIN_NAME_LENGTH.into() &&
|
||||
name.len() <= MAX_NAME_LENGTH.into()
|
||||
@ -19,9 +23,17 @@ fn validate_user_password(password: &String) -> bool {
|
||||
}
|
||||
// User dto SHOULD die here.
|
||||
pub fn validate_user_for_creation(user: UserForCreationDto, message_resources: &mut Vec<MessageResourceDto>){
|
||||
if !validate_user_email(&user.email) {
|
||||
message_resources.push(MessageResourceDto::new_from_error_message(ERROR_INVALID_EMAIL));
|
||||
match user.credential_type {
|
||||
CredentialType::Email =>
|
||||
if !validate_user_email(&user.credential) {
|
||||
message_resources.push(MessageResourceDto::new_from_error_message(ERROR_INVALID_EMAIL));
|
||||
},
|
||||
CredentialType::PhoneNumber =>
|
||||
if !validate_user_phone_number(&user.credential) {
|
||||
message_resources.push(MessageResourceDto::new_from_error_message(ERROR_INVALID_PHONE_NUMBER));
|
||||
},
|
||||
}
|
||||
|
||||
if !validate_user_name(&user.name) {
|
||||
message_resources.push(MessageResourceDto::new_from_error_message(ERROR_INVALID_NAME));
|
||||
}
|
||||
@ -30,8 +42,15 @@ pub fn validate_user_for_creation(user: UserForCreationDto, message_resources: &
|
||||
}
|
||||
}
|
||||
pub fn validate_user_for_password_authentication(user: &UserForLoginDto, message_resources: &mut Vec<MessageResourceDto>){
|
||||
if !validate_user_email(&user.email) {
|
||||
message_resources.push(MessageResourceDto::new_from_error_message(ERROR_INVALID_EMAIL));
|
||||
match user.credential_type {
|
||||
CredentialType::Email =>
|
||||
if !validate_user_email(&user.credential) {
|
||||
message_resources.push(MessageResourceDto::new_from_error_message(ERROR_INVALID_EMAIL));
|
||||
},
|
||||
CredentialType::PhoneNumber =>
|
||||
if !validate_user_phone_number(&user.credential) {
|
||||
message_resources.push(MessageResourceDto::new_from_error_message(ERROR_INVALID_PHONE_NUMBER));
|
||||
},
|
||||
}
|
||||
if !validate_user_password(&user.password) {
|
||||
message_resources.push(MessageResourceDto::new_from_error_message(ERROR_INVALID_PASSWORD));
|
||||
|
Loading…
Reference in New Issue
Block a user