mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-11-03 13:30:39 +08:00
fix(users): add password validations (#4555)
Co-authored-by: Rachit Naithani <rachit.naithani@juspay.in>
This commit is contained in:
@ -1,3 +1,5 @@
|
||||
pub const MAX_NAME_LENGTH: usize = 70;
|
||||
pub const MAX_COMPANY_NAME_LENGTH: usize = 70;
|
||||
pub const BUSINESS_EMAIL: &str = "biz@hyperswitch.io";
|
||||
pub const MAX_PASSWORD_LENGTH: usize = 70;
|
||||
pub const MIN_PASSWORD_LENGTH: usize = 8;
|
||||
|
||||
@ -158,11 +158,43 @@ pub struct UserPassword(Secret<String>);
|
||||
impl UserPassword {
|
||||
pub fn new(password: Secret<String>) -> UserResult<Self> {
|
||||
let password = password.expose();
|
||||
if password.is_empty() {
|
||||
Err(UserErrors::PasswordParsingError.into())
|
||||
} else {
|
||||
Ok(Self(password.into()))
|
||||
|
||||
let mut has_upper_case = false;
|
||||
let mut has_lower_case = false;
|
||||
let mut has_numeric_value = false;
|
||||
let mut has_special_character = false;
|
||||
let mut has_whitespace = false;
|
||||
|
||||
for c in password.chars() {
|
||||
has_upper_case = has_upper_case || c.is_uppercase();
|
||||
has_lower_case = has_lower_case || c.is_lowercase();
|
||||
has_numeric_value = has_numeric_value || c.is_numeric();
|
||||
has_special_character =
|
||||
has_special_character || !(c.is_alphanumeric() && c.is_whitespace());
|
||||
has_whitespace = has_whitespace || c.is_whitespace();
|
||||
}
|
||||
|
||||
let is_password_format_valid = has_upper_case
|
||||
&& has_lower_case
|
||||
&& has_numeric_value
|
||||
&& has_special_character
|
||||
&& !has_whitespace;
|
||||
|
||||
let is_too_long = password.graphemes(true).count() > consts::user::MAX_PASSWORD_LENGTH;
|
||||
let is_too_short = password.graphemes(true).count() < consts::user::MIN_PASSWORD_LENGTH;
|
||||
|
||||
if is_too_short || is_too_long || !is_password_format_valid {
|
||||
return Err(UserErrors::PasswordParsingError.into());
|
||||
}
|
||||
Ok(Self(password.into()))
|
||||
}
|
||||
|
||||
pub fn new_password_without_validation(password: Secret<String>) -> UserResult<Self> {
|
||||
let password = password.expose();
|
||||
if password.is_empty() {
|
||||
return Err(UserErrors::PasswordParsingError.into());
|
||||
}
|
||||
Ok(Self(password.into()))
|
||||
}
|
||||
|
||||
pub fn get_secret(&self) -> Secret<String> {
|
||||
@ -636,7 +668,7 @@ impl TryFrom<user_api::ConnectAccountRequest> for NewUser {
|
||||
let user_id = uuid::Uuid::new_v4().to_string();
|
||||
let email = value.email.clone().try_into()?;
|
||||
let name = UserName::try_from(value.email.clone())?;
|
||||
let password = UserPassword::new(uuid::Uuid::new_v4().to_string().into())?;
|
||||
let password = UserPassword::new(password::get_temp_password())?;
|
||||
let new_merchant = NewUserMerchant::try_from(value)?;
|
||||
|
||||
Ok(Self {
|
||||
@ -680,7 +712,7 @@ impl TryFrom<UserMerchantCreateRequestWithToken> for NewUser {
|
||||
user_id: user.0.user_id,
|
||||
name: UserName::new(user.0.name)?,
|
||||
email: user.0.email.clone().try_into()?,
|
||||
password: UserPassword::new(user.0.password)?,
|
||||
password: UserPassword::new_password_without_validation(user.0.password)?,
|
||||
new_merchant,
|
||||
})
|
||||
}
|
||||
@ -692,7 +724,7 @@ impl TryFrom<InviteeUserRequestWithInvitedUserToken> for NewUser {
|
||||
let user_id = uuid::Uuid::new_v4().to_string();
|
||||
let email = value.0.email.clone().try_into()?;
|
||||
let name = UserName::new(value.0.name.clone())?;
|
||||
let password = UserPassword::new(uuid::Uuid::new_v4().to_string().into())?;
|
||||
let password = UserPassword::new(password::get_temp_password())?;
|
||||
let new_merchant = NewUserMerchant::try_from(value)?;
|
||||
|
||||
Ok(Self {
|
||||
|
||||
@ -8,6 +8,7 @@ use argon2::{
|
||||
use common_utils::errors::CustomResult;
|
||||
use error_stack::ResultExt;
|
||||
use masking::{ExposeInterface, Secret};
|
||||
use rand::{seq::SliceRandom, Rng};
|
||||
|
||||
use crate::core::errors::UserErrors;
|
||||
|
||||
@ -38,3 +39,20 @@ pub fn is_correct_password(
|
||||
}
|
||||
.change_context(UserErrors::InternalServerError)
|
||||
}
|
||||
|
||||
pub fn get_temp_password() -> Secret<String> {
|
||||
let uuid_pass = uuid::Uuid::new_v4().to_string();
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
let special_chars: Vec<char> = "!@#$%^&*()-_=+[]{}|;:,.<>?".chars().collect();
|
||||
let special_char = special_chars.choose(&mut rng).unwrap_or(&'@');
|
||||
|
||||
Secret::new(format!(
|
||||
"{}{}{}{}{}",
|
||||
uuid_pass,
|
||||
rng.gen_range('A'..='Z'),
|
||||
special_char,
|
||||
rng.gen_range('a'..='z'),
|
||||
rng.gen_range('0'..='9'),
|
||||
))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user