From db2d92b3641010658d79569c518e9894a34566e5 Mon Sep 17 00:00:00 2001 From: Franklin Date: Sat, 23 Sep 2023 18:39:41 -0400 Subject: [PATCH] User-lib v1 --- .idea/inspectionProfiles/Project_Default.xml | 5 + src/dao/credential.rs | 2 +- src/dao/token.rs | 7 +- src/dao/user.rs | 1 + src/domain/credential.rs | 2 +- src/dto/users.rs | 8 + src/service/user.rs | 172 +++++++++++++++---- src/validation/user_validator.rs | 56 +++--- 8 files changed, 182 insertions(+), 71 deletions(-) diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index be4b286..b909970 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -1,6 +1,11 @@ diff --git a/src/dao/credential.rs b/src/dao/credential.rs index 84a0fb7..f9b53dc 100644 --- a/src/dao/credential.rs +++ b/src/dao/credential.rs @@ -1,7 +1,7 @@ use crate::domain::credential::Credential; use crate::dto::credential::CredentialDto; use chrono::Utc; -use sqlx::{Error, PgConnection, PgPool}; +use sqlx::{Error, PgConnection}; pub(crate) async fn insert_credential( conn: &mut PgConnection, diff --git a/src/dao/token.rs b/src/dao/token.rs index 4f0c71f..cf3af55 100644 --- a/src/dao/token.rs +++ b/src/dao/token.rs @@ -1,6 +1,6 @@ use crate::domain::token::Token; use chrono::Utc; -use sqlx::{Error, PgConnection, PgPool}; +use sqlx::{Error, PgConnection}; pub(crate) async fn insert_token(conn: &mut PgConnection, token: Token) -> Result { sqlx::query_as(r#"INSERT INTO token ( @@ -11,16 +11,14 @@ pub(crate) async fn insert_token(conn: &mut PgConnection, token: Token) -> Resul pub(crate) async fn update_token( conn: &mut PgConnection, - token_id: &i32, refresh_token: String, new_auth_token: String, ) -> Result { sqlx::query_as( r#"UPDATE token set auth_token = $3, last_updated = $4 - WHERE id = $1 AND refresh_token = $2 RETURNING *;"#, + WHERE refresh_token = $1 RETURNING *;"#, ) - .bind(token_id) .bind(refresh_token) .bind(new_auth_token) .bind(Utc::now()) @@ -28,6 +26,7 @@ pub(crate) async fn update_token( .await } +#[allow(unused)] pub(crate) async fn remove_token(conn: &mut PgConnection, token_id: &i32) -> Result, Error> { sqlx::query_as(r#"DELETE FROM token WHERE id = $1 RETURNING *;"#) .bind(token_id) diff --git a/src/dao/user.rs b/src/dao/user.rs index 4889719..193a35f 100644 --- a/src/dao/user.rs +++ b/src/dao/user.rs @@ -44,6 +44,7 @@ pub(crate) async fn update_user(conn: &mut PgConnection, user: User) -> Result Result, sqlx::Error> { sqlx::query_as( r#" diff --git a/src/domain/credential.rs b/src/domain/credential.rs index 69495e1..ab5a003 100644 --- a/src/domain/credential.rs +++ b/src/domain/credential.rs @@ -1,5 +1,5 @@ use crate::resources::variable_lengths::{ - MAX_EMAIL_LENGTH, MAX_NAME_LENGTH, MAX_PHONE_NUMBER_LENGTH, MAX_USERNAME_LENGTH, + MAX_EMAIL_LENGTH, MAX_PHONE_NUMBER_LENGTH, MAX_USERNAME_LENGTH, MIN_EMAIL_LENGTH, MIN_PHONE_NUMBER_LENGTH, MIN_USERNAME_LENGTH, }; use chrono::{DateTime, Utc}; diff --git a/src/dto/users.rs b/src/dto/users.rs index b2e94ce..0762501 100644 --- a/src/dto/users.rs +++ b/src/dto/users.rs @@ -18,3 +18,11 @@ pub struct UserRegisterPayload { pub password: String, pub name: String, } + +#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[serde(rename_all = "camelCase")] +pub struct UserResetPasswordPayload { + pub id: i32, + pub password: String, + pub new_password: String, +} \ No newline at end of file diff --git a/src/service/user.rs b/src/service/user.rs index 13d2592..130aa50 100644 --- a/src/service/user.rs +++ b/src/service/user.rs @@ -1,20 +1,21 @@ +use std::future::Future; use chrono::Utc; use log::{debug, error}; -use sqlx::{PgConnection, Postgres, Transaction}; +use sqlx::{Error, PgConnection, Postgres, Transaction}; use crate::dao::credential::{fetch_user_credentials, get_credential, insert_credential}; -use crate::dao::token::{insert_token, validate_user_token}; -use crate::dao::user::{get_user_with_id, insert_user}; +use crate::dao::token::{insert_token, update_token, validate_user_token}; +use crate::dao::user::{get_user_with_id, insert_user, update_user}; use crate::domain::credential::Credential; use crate::domain::token::Token; use crate::domain::user::User; -use crate::dto::token::AuthenticateUserDto; -use crate::dto::users::{UserLoginPayload, UserRegisterPayload}; -use crate::resources::error_messages::{ERROR_CREDENTIAL_DOES_NOT_EXIST, ERROR_EXPIRED_TOKEN, ERROR_INCORRECT_TOKEN, ERROR_TOKEN_NOT_CREATED, ERROR_TOO_MANY_CREDENTIALS, ERROR_USER_ALREADY_EXISTS, ERROR_USER_DOES_NOT_EXIST, ErrorResource}; +use crate::dto::token::{AuthenticateUserDto, RefreshAuthTokenForUserDto}; +use crate::dto::users::{UserLoginPayload, UserRegisterPayload, UserResetPasswordPayload}; +use crate::resources::error_messages::{ERROR_CREDENTIAL_DOES_NOT_EXIST, ERROR_EXPIRED_TOKEN, ERROR_INCORRECT_TOKEN, ERROR_PASSWORD_INCORRECT, ERROR_TOKEN_NOT_CREATED, ERROR_TOO_MANY_CREDENTIALS, ERROR_USER_ALREADY_EXISTS, ERROR_USER_DOES_NOT_EXIST, ErrorResource}; use crate::resources::expirations::AUTH_TOKEN_EXPIRATION_TIME_MILLIS; use crate::utils::hasher::{generate_multiple_random_token_with_rng, hash_password, hash_password_with_existing_salt}; use crate::validation::user_validator::validate_user_for_creation; -pub async fn register_user<'a>(transaction: &mut Transaction<'a, Postgres>, user: UserRegisterPayload) -> Result>> { +pub async fn register_user<'a>(transaction: &mut PgConnection, user: UserRegisterPayload) -> Result>> { let mut error_resources: Vec = Vec::new(); // Validate user validate_user_for_creation(&user, &mut error_resources); @@ -135,19 +136,100 @@ pub async fn authenticate_user<'a>(conn: &mut PgConnection, user: AuthenticateUs } /// -pub async fn refresh_auth_token() {} +pub async fn refresh_auth_token<'a>(conn: &mut PgConnection, user: RefreshAuthTokenForUserDto) -> Result> { + let mut error_resources = Vec::new(); + let persisted_user = match get_user_with_id(conn, &user.id).await { + Ok(persisted_user_opt) => match persisted_user_opt { + None => { + error_resources.push(ERROR_USER_DOES_NOT_EXIST); + return Err(error_resources); + }, + Some(persisted_user) => persisted_user + }, + Err(error) => { + error!("{:?}", error); + error_resources.push(("ERROR.DATABASE_ERROR", "")); + return Err(error_resources); + } + }; -/// -pub async fn reset_password() {} + let mut tokens: Vec = match generate_multiple_random_token_with_rng(2).await { + Ok(tokens) => tokens, + Err(e) => { + error!("{}", e); + error_resources.push(("ERROR.JOIN_ERROR", "")); + return Err(error_resources); + } + }; + + if tokens.len() > 0 { + let new_auth_token = tokens.remove(0); + return match update_token(conn, user.refresh_token, new_auth_token).await { + Ok(persisted_token) => Ok(persisted_token), + Err(e) => { + error!("{:?}", e); + error_resources.push(("ERROR.DATABASE_ERROR", "")); + Err(error_resources) + } + } + } + + Err(error_resources) +} + +/// reset a user's password by validating the user's own password. +pub async fn reset_password(conn: &mut PgConnection, user: UserResetPasswordPayload) -> Result>{ + let mut error_resources: Vec = Vec::new(); + + let password_matches = match validate_user_password(conn, &user.id, user.password).await { + Ok(matches) => matches, + Err(e) => { + error!("{:?}", e); + error_resources.push(e); + return Err(error_resources); + } + }; + + if let Some(persisted_user) = password_matches { // Change pass + match change_password(conn, persisted_user, &user.new_password).await { + Ok(user_changed) => Ok(user_changed), + Err(e) => { + error!("{:?}", e); + error_resources.push(e); + Err(error_resources) + } + } + } else { + error_resources.push(ERROR_PASSWORD_INCORRECT); + Err(error_resources) + } +} /// ## This resets a user's password without any validations! /// Don't expose this to any public endpoint!! -pub async fn force_reset_password() {} +pub async fn force_reset_password<'a>(conn: &mut PgConnection, user_id: &i32, new_password: String) -> Result> { + let persisted_user = match get_user_with_id(conn, user_id).await { + Ok(persisted_user_opt) => match persisted_user_opt { + None => { + error!("Serious error. User doesn't exist but credentials pointing to the user do."); + return Err(("ERROR.DATABASE_ERROR", "Critical. User doesn't exist but credentials pointing to the user do.")); + }, + Some(persisted_user) => { + persisted_user + } + }, + Err(e) => { + error!("{}", e); + return Err(("ERROR.DATABASE_ERROR", "")); + } + }; + Ok(change_password(conn, persisted_user, &new_password).await?) +} /// -pub async fn password_login<'a>(transaction: &mut Transaction<'a, Postgres>, user: UserLoginPayload) -> Result>>{ +pub async fn password_login<'a>(conn: &mut Transaction<'a, Postgres>, user: UserLoginPayload) -> Result>>{ let mut error_resources = Vec::new(); - let persisted_user_credential = match get_credential(transaction, user.credential).await { + let persisted_user_credential = match get_credential(conn, user.credential).await { Ok(credential_opt) => match credential_opt { None => { error!("Credential not found for password login."); @@ -162,25 +244,16 @@ pub async fn password_login<'a>(transaction: &mut Transaction<'a, Postgres>, use return Err(error_resources); } }; - let persisted_user = match get_user_with_id(transaction, &persisted_user_credential.user_id).await { - Ok(persisted_user_opt) => match persisted_user_opt { - None => { - error!("Serious error. User doesn't exist but credentials pointing to the user do."); - error_resources.push(("ERROR.DATABASE_ERROR", "Critical. User doesn't exist but credentials pointing to the user do.")); - return Err(error_resources); - }, - Some(persisted_user) => { - persisted_user - } - }, + let persisted_user_opt = match validate_user_password(conn, &persisted_user_credential.user_id, user.password).await { + Ok(matches) => matches, Err(e) => { - error!("{}", e); - error_resources.push(("ERROR.DATABASE_ERROR", "")); - return Err(error_resources);} + error!("{:?}", e); + error_resources.push(e); + return Err(error_resources); + } }; - let hashed_password = hash_password_with_existing_salt(&user.password, &persisted_user.salt); - if hashed_password.hash == persisted_user.password { - return if let Some(persisted_token) = create_token_for_user(transaction, persisted_user.id, &mut error_resources).await { + if let Some(_) = persisted_user_opt { + return if let Some(persisted_token) = create_token_for_user(conn, persisted_user_credential.user_id, &mut error_resources).await { Ok(persisted_token) } else { Err(error_resources) @@ -204,7 +277,7 @@ pub async fn get_user_credentials<'a>(transaction: &mut Transaction<'a, Postgres } } -async fn create_token_for_user<'a>(transaction: &mut Transaction<'a, Postgres>, user_id: i32, error_resources: &mut Vec>) -> Option { +async fn create_token_for_user<'a>(transaction: &mut PgConnection, user_id: i32, error_resources: &mut Vec>) -> Option { // Create token and send it back. let tokens: Vec = match generate_multiple_random_token_with_rng(2).await { Ok(tokens) => tokens, @@ -249,4 +322,41 @@ async fn create_token_for_user<'a>(transaction: &mut Transaction<'a, Postgres>, None } } +} + +async fn validate_user_password<'a>(conn: &mut PgConnection, user_id: &i32, password: String) -> Result, ErrorResource<'a>> { + let persisted_user = match get_user_with_id(conn, user_id).await { + Ok(persisted_user_opt) => match persisted_user_opt { + None => { + error!("Serious error. User doesn't exist but credentials pointing to the user do."); + return Err(("ERROR.DATABASE_ERROR", "Critical. User doesn't exist but credentials pointing to the user do.")); + }, + Some(persisted_user) => { + persisted_user + } + }, + Err(e) => { + error!("{}", e); + return Err(("ERROR.DATABASE_ERROR", "")); + } + }; + let hashed_password = hash_password_with_existing_salt(&password, &persisted_user.salt); + if hashed_password.hash == persisted_user.password { + Ok(Some(persisted_user)) + } else { + Ok(None) + } +} + +async fn change_password<'a>(conn: &mut PgConnection, mut persisted_user: User, new_password: &String) -> Result> { + let hash_result = hash_password(&new_password); + persisted_user.password = hash_result.hash; + persisted_user.salt = hash_result.salt; + match update_user(conn, persisted_user).await { + Ok(user) => Ok(user), + Err(error) => { + error!("{}", error); + return Err(("ERROR.DATABASE_ERROR", "")); + } + } } \ No newline at end of file diff --git a/src/validation/user_validator.rs b/src/validation/user_validator.rs index da8225d..73e14b1 100644 --- a/src/validation/user_validator.rs +++ b/src/validation/user_validator.rs @@ -38,23 +38,7 @@ pub(crate) fn validate_user_for_creation( error_resources: &mut Vec, ) { for credential_dto in user.credentials.iter() { - match credential_dto.credential_type { - CredentialType::Email => { - if !validate_user_email(&credential_dto.credential) { - error_resources.push(ERROR_INVALID_EMAIL); - } - } - CredentialType::PhoneNumber => { - if !validate_user_phone_number(&credential_dto.credential) { - error_resources.push(ERROR_INVALID_PHONE_NUMBER); - } - } - CredentialType::Username => { - if !validate_user_username(&credential_dto.credential) { - error_resources.push(ERROR_INVALID_USERNAME); - } - } - }; + validate_credential(error_resources, &credential_dto.credential, &credential_dto.credential_type); } if !validate_user_name(&user.name) { @@ -68,24 +52,28 @@ pub(crate) fn validate_user_for_password_authentication( user: &UserLoginPayload, error_resources: &mut Vec, ) { - match user.credential_type { - CredentialType::Email => { - if !validate_user_email(&user.credential) { - error_resources.push(ERROR_INVALID_EMAIL); - } - } - CredentialType::PhoneNumber => { - if !validate_user_phone_number(&user.credential) { - error_resources.push(ERROR_INVALID_PHONE_NUMBER); - } - } - CredentialType::Username => { - if !validate_user_username(&user.credential) { - error_resources.push(ERROR_INVALID_USERNAME); - } - } - } + validate_credential(error_resources, &user.credential, &user.credential_type); if !validate_user_password(&user.password) { error_resources.push(ERROR_INVALID_PASSWORD); } } + +fn validate_credential(error_resources: &mut Vec, credential: &String, credential_type: &CredentialType) { + match credential_type { + CredentialType::Email => { + if !validate_user_email(credential) { + error_resources.push(ERROR_INVALID_EMAIL); + } + } + CredentialType::PhoneNumber => { + if !validate_user_phone_number(credential) { + error_resources.push(ERROR_INVALID_PHONE_NUMBER); + } + } + CredentialType::Username => { + if !validate_user_username(credential) { + error_resources.push(ERROR_INVALID_USERNAME); + } + } + } +} \ No newline at end of file