From d2b098ed92359d258d640da0d4b77e443479994a Mon Sep 17 00:00:00 2001 From: Bakunya Date: Sun, 6 Apr 2025 19:15:01 +0700 Subject: [PATCH] fix otp and password validation --- imphnen-apis/src/apps/v1/auth/auth_dto.rs | 21 +++++++++++++++---- .../src/apps/v1/auth/auth_repository.rs | 2 +- imphnen-apis/src/apps/v1/auth/auth_service.rs | 12 +++++++++-- 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/imphnen-apis/src/apps/v1/auth/auth_dto.rs b/imphnen-apis/src/apps/v1/auth/auth_dto.rs index 6fb20bf..90374c4 100644 --- a/imphnen-apis/src/apps/v1/auth/auth_dto.rs +++ b/imphnen-apis/src/apps/v1/auth/auth_dto.rs @@ -3,10 +3,23 @@ use lazy_static::lazy_static; use regex::Regex; use serde::{Deserialize, Serialize}; use utoipa::ToSchema; -use validator::Validate; +use validator::{Validate, ValidationError}; lazy_static! { - static ref PASSWORD_REGEX: Regex = Regex::new(r"^[A-Za-z\d@$!%*?&]{8,}$").unwrap(); + static ref PASSWORD_REGEX: Regex = Regex::new(r"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$").unwrap(); +} + +fn validate_password_complexity(password: &str) -> Result<(), ValidationError> { + let has_uppercase = password.chars().any(|c| c.is_ascii_uppercase()); + let has_lowercase = password.chars().any(|c| c.is_ascii_lowercase()); + let has_digit = password.chars().any(|c| c.is_ascii_digit()); + let has_special = password.chars().any(|c| "@$!%*?&".contains(c)); + + if has_uppercase && has_lowercase && has_digit && has_special { + Ok(()) + } else { + Err(ValidationError::new("complexity")) + } } #[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)] @@ -56,8 +69,8 @@ pub struct AuthRegisterRequestDto { min = 8, message = "Password must have at least 8 characters" ))] - #[validate(regex( - path = "PASSWORD_REGEX", + #[validate(custom( + function = "validate_password_complexity", message = "Password must include uppercase, lowercase, number, and special character" ))] pub password: String, diff --git a/imphnen-apis/src/apps/v1/auth/auth_repository.rs b/imphnen-apis/src/apps/v1/auth/auth_repository.rs index 3bd3deb..7fa4076 100644 --- a/imphnen-apis/src/apps/v1/auth/auth_repository.rs +++ b/imphnen-apis/src/apps/v1/auth/auth_repository.rs @@ -88,7 +88,7 @@ impl<'a> AuthRepository<'a> { pub async fn query_store_otp(&self, email: String, otp: u32) -> Result { let expires_at = Utc::now() + Duration::seconds(300); - let table = ResourceEnum::OtpCache.to_string(); + let table: String = ResourceEnum::OtpCache.to_string(); let record: Option = self .state .surrealdb_mem diff --git a/imphnen-apis/src/apps/v1/auth/auth_service.rs b/imphnen-apis/src/apps/v1/auth/auth_service.rs index 7c09629..76df782 100644 --- a/imphnen-apis/src/apps/v1/auth/auth_service.rs +++ b/imphnen-apis/src/apps/v1/auth/auth_service.rs @@ -202,10 +202,18 @@ impl AuthService { if let Err((status, message)) = validate_request(&payload) { return common_response(status, &message); } - let repository = AuthRepository::new(state); + + let user_repo = UsersRepository::new(state); + if user_repo.query_user_by_email(payload.email.clone()).await.is_err() { + return common_response(StatusCode::BAD_REQUEST, "User not found"); + } + + let auth_repo = AuthRepository::new(state); + let _ = auth_repo.query_get_stored_otp(payload.email.clone()).await; + let otp = generate_otp::OtpManager::generate_otp(); let message = format!("Your OTP code is {}", otp); - match repository.query_store_otp(payload.email.clone(), otp).await { + match auth_repo.query_store_otp(payload.email.clone(), otp).await { Ok(_) => match send_email(&payload.email, "OTP Verification", &message) { Ok(_) => common_response(StatusCode::OK, "OTP resent successfully"), Err(err) => common_response(StatusCode::BAD_REQUEST, &err.to_string()),