diff --git a/.DS_Store b/.DS_Store index 0febaa6..281020a 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/Cargo.toml b/Cargo.toml index 05f9abf..ce797f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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"] } \ No newline at end of file +dev-dtos = { git = "https://backend:Eo1n1TPsyWV7wwo9uFwgUJGKKheMM6paM2mDkVPA4zqkh5dt6Q6XPkbtojzYQudQsM84vSwKmhHHTPjyn535d6NLBmA3meeGj0Gb8if4sceAwvySdmzedg5mN2P5zzQt@gitea.blancoinfante.com/blancoinfante_backend/dev-dtos-rust.git" } \ No newline at end of file diff --git a/build.rs b/build.rs deleted file mode 100644 index c7c204c..0000000 --- a/build.rs +++ /dev/null @@ -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 = 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}")} - }; -} \ No newline at end of file diff --git a/migrations/1_user.sql b/migrations/1_user.sql index 6e29235..74e33af 100644 --- a/migrations/1_user.sql +++ b/migrations/1_user.sql @@ -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 diff --git a/migrations/20220726034843_aaa.sql b/migrations/20220726034843_aaa.sql deleted file mode 100644 index 8ddc1d3..0000000 --- a/migrations/20220726034843_aaa.sql +++ /dev/null @@ -1 +0,0 @@ --- Add migration script here diff --git a/sql/schema/migrations/tokenup.sql b/sql/schema/migrations/tokenup.sql deleted file mode 100644 index 48578aa..0000000 --- a/sql/schema/migrations/tokenup.sql +++ /dev/null @@ -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 -) \ No newline at end of file diff --git a/sql/schema/migrations/userup.sql b/sql/schema/migrations/userup.sql deleted file mode 100644 index a6e16f2..0000000 --- a/sql/schema/migrations/userup.sql +++ /dev/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 -) \ No newline at end of file diff --git a/sql/schema/user/find_with_credential.sql b/sql/schema/user/find_with_credential.sql new file mode 100644 index 0000000..4ce52d4 --- /dev/null +++ b/sql/schema/user/find_with_credential.sql @@ -0,0 +1,5 @@ +SELECT * +FROM user +WHERE user.credential = ? AND +user.credential_type = ? AND +user.app = ? \ No newline at end of file diff --git a/sql/schema/user/find_with_email.sql b/sql/schema/user/find_with_email.sql deleted file mode 100644 index a9b92cc..0000000 --- a/sql/schema/user/find_with_email.sql +++ /dev/null @@ -1,4 +0,0 @@ -SELECT * -FROM user -WHERE user.email = ? AND -user.app = ? \ No newline at end of file diff --git a/sql/schema/user/insert.sql b/sql/schema/user/insert.sql index cd2bb7f..6eb9b5d 100644 --- a/sql/schema/user/insert.sql +++ b/sql/schema/user/insert.sql @@ -1,3 +1,3 @@ INSERT INTO user -(id, time_created, last_updated, app, email, name, password, salt) values -(NULL, NOW(), NOW(), ?, ?, ?, ?, ?) \ No newline at end of file +(id, time_created, last_updated, app, credential, credential_type, name, password, salt) values +(NULL, NOW(), NOW(), ?, ?, ?, ?, ?, ?) \ No newline at end of file diff --git a/src/dao/token_dao.rs b/src/dao/token_dao.rs index 5cbb23c..8062ebf 100644 --- a/src/dao/token_dao.rs +++ b/src/dao/token_dao.rs @@ -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 { sqlx::query_file!("sql/schema/token/insert.sql", token.user_id, token.auth_token, token.refresh_token).execute(conn).await } diff --git a/src/dao/user_dao.rs b/src/dao/user_dao.rs index 282f81f..0486341 100644 --- a/src/dao/user_dao.rs +++ b/src/dao/user_dao.rs @@ -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{ 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{ - 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{ + 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 { sqlx::query_file_as!(User, "sql/schema/user/find_with_id.sql", id).fetch_one(conn).await diff --git a/src/do/mod.rs b/src/do/mod.rs index b6de466..60d821c 100644 --- a/src/do/mod.rs +++ b/src/do/mod.rs @@ -1,3 +1 @@ -pub mod shared_state; -pub mod user; -pub mod token; \ No newline at end of file +pub mod shared_state; \ No newline at end of file diff --git a/src/do/token.rs b/src/do/token.rs deleted file mode 100644 index 041a667..0000000 --- a/src/do/token.rs +++ /dev/null @@ -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, - #[serde(skip_serializing)] - pub last_updated: Option, - 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 - } - } -} \ No newline at end of file diff --git a/src/do/user.rs b/src/do/user.rs deleted file mode 100644 index 3b0b8a0..0000000 --- a/src/do/user.rs +++ /dev/null @@ -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, - #[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 { - 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() } - } -} diff --git a/src/dto/mod.rs b/src/dto/mod.rs index 8058c37..46d6c91 100644 --- a/src/dto/mod.rs +++ b/src/dto/mod.rs @@ -1,3 +1,2 @@ -pub mod user_dtos; pub mod message_resources_dtos; pub mod hash_dtos; \ No newline at end of file diff --git a/src/dto/user_dtos.rs b/src/dto/user_dtos.rs deleted file mode 100644 index b061890..0000000 --- a/src/dto/user_dtos.rs +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/src/resources/error_messages.rs b/src/resources/error_messages.rs index 04bb887..acbe10a 100644 --- a/src/resources/error_messages.rs +++ b/src/resources/error_messages.rs @@ -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."); diff --git a/src/routes/user_routes.rs b/src/routes/user_routes.rs index 44efe42..08307f0 100644 --- a/src/routes/user_routes.rs +++ b/src/routes/user_routes.rs @@ -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, db_conn: Data>) -> HttpResponse { @@ -24,7 +23,7 @@ pub async fn create_user(incoming_user: web::Json, 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 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)); diff --git a/src/validation/user_validator.rs b/src/validation/user_validator.rs index ed9ef04..886c5d6 100644 --- a/src/validation/user_validator.rs +++ b/src/validation/user_validator.rs @@ -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){ - 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){ - 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));