mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-31 01:57:45 +08:00
feat(users): Add merchant_id in EmailToken and change user status in reset password (#3473)
This commit is contained in:
@ -1,4 +1,6 @@
|
||||
use api_models::user::{self as user_api, InviteMultipleUserResponse};
|
||||
#[cfg(feature = "email")]
|
||||
use diesel_models::user_role::UserRoleUpdate;
|
||||
use diesel_models::{enums::UserStatus, user as storage_user, user_role::UserRoleNew};
|
||||
#[cfg(feature = "email")]
|
||||
use error_stack::IntoReport;
|
||||
@ -362,18 +364,10 @@ pub async fn reset_password(
|
||||
|
||||
let hash_password = utils::user::password::generate_password_hash(password.get_secret())?;
|
||||
|
||||
//TODO: Create Update by email query
|
||||
let user_id = state
|
||||
let user = state
|
||||
.store
|
||||
.find_user_by_email(token.get_email())
|
||||
.await
|
||||
.change_context(UserErrors::InternalServerError)?
|
||||
.user_id;
|
||||
|
||||
state
|
||||
.store
|
||||
.update_user_by_user_id(
|
||||
user_id.as_str(),
|
||||
.update_user_by_email(
|
||||
token.get_email(),
|
||||
storage_user::UserUpdate::AccountUpdate {
|
||||
name: None,
|
||||
password: Some(hash_password),
|
||||
@ -384,7 +378,20 @@ pub async fn reset_password(
|
||||
.await
|
||||
.change_context(UserErrors::InternalServerError)?;
|
||||
|
||||
//TODO: Update User role status for invited user
|
||||
if let Some(inviter_merchant_id) = token.get_merchant_id() {
|
||||
let update_status_result = state
|
||||
.store
|
||||
.update_user_role_by_user_id_merchant_id(
|
||||
user.user_id.clone().as_str(),
|
||||
inviter_merchant_id,
|
||||
UserRoleUpdate::UpdateStatus {
|
||||
status: UserStatus::Active,
|
||||
modified_by: user.user_id,
|
||||
},
|
||||
)
|
||||
.await;
|
||||
logger::info!(?update_status_result);
|
||||
}
|
||||
|
||||
Ok(ApplicationResponse::StatusOk)
|
||||
}
|
||||
@ -467,7 +474,7 @@ pub async fn invite_user(
|
||||
.store
|
||||
.insert_user_role(UserRoleNew {
|
||||
user_id: new_user.get_user_id().to_owned(),
|
||||
merchant_id: user_from_token.merchant_id,
|
||||
merchant_id: user_from_token.merchant_id.clone(),
|
||||
role_id: request.role_id,
|
||||
org_id: user_from_token.org_id,
|
||||
status: invitation_status,
|
||||
@ -493,6 +500,7 @@ pub async fn invite_user(
|
||||
user_name: domain::UserName::new(new_user.get_name())?,
|
||||
settings: state.conf.clone(),
|
||||
subject: "You have been invited to join Hyperswitch Community!",
|
||||
merchant_id: user_from_token.merchant_id,
|
||||
};
|
||||
let send_email_result = state
|
||||
.email_client
|
||||
@ -669,6 +677,7 @@ async fn handle_new_user_invitation(
|
||||
user_name: domain::UserName::new(new_user.get_name())?,
|
||||
settings: state.conf.clone(),
|
||||
subject: "You have been invited to join Hyperswitch Community!",
|
||||
merchant_id: user_from_token.merchant_id.clone(),
|
||||
};
|
||||
let send_email_result = state
|
||||
.email_client
|
||||
|
||||
@ -1895,6 +1895,16 @@ impl UserInterface for KafkaStore {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn update_user_by_email(
|
||||
&self,
|
||||
user_email: &str,
|
||||
user: storage::UserUpdate,
|
||||
) -> CustomResult<storage::User, errors::StorageError> {
|
||||
self.diesel_store
|
||||
.update_user_by_email(user_email, user)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn delete_user_by_user_id(
|
||||
&self,
|
||||
user_id: &str,
|
||||
|
||||
@ -33,6 +33,12 @@ pub trait UserInterface {
|
||||
user: storage::UserUpdate,
|
||||
) -> CustomResult<storage::User, errors::StorageError>;
|
||||
|
||||
async fn update_user_by_email(
|
||||
&self,
|
||||
user_email: &str,
|
||||
user: storage::UserUpdate,
|
||||
) -> CustomResult<storage::User, errors::StorageError>;
|
||||
|
||||
async fn delete_user_by_user_id(
|
||||
&self,
|
||||
user_id: &str,
|
||||
@ -92,6 +98,18 @@ impl UserInterface for Store {
|
||||
.into_report()
|
||||
}
|
||||
|
||||
async fn update_user_by_email(
|
||||
&self,
|
||||
user_email: &str,
|
||||
user: storage::UserUpdate,
|
||||
) -> CustomResult<storage::User, errors::StorageError> {
|
||||
let conn = connection::pg_connection_write(self).await?;
|
||||
storage::User::update_by_user_email(&conn, user_email, user)
|
||||
.await
|
||||
.map_err(Into::into)
|
||||
.into_report()
|
||||
}
|
||||
|
||||
async fn delete_user_by_user_id(
|
||||
&self,
|
||||
user_id: &str,
|
||||
@ -229,6 +247,50 @@ impl UserInterface for MockDb {
|
||||
)
|
||||
}
|
||||
|
||||
async fn update_user_by_email(
|
||||
&self,
|
||||
user_email: &str,
|
||||
update_user: storage::UserUpdate,
|
||||
) -> CustomResult<storage::User, errors::StorageError> {
|
||||
let mut users = self.users.lock().await;
|
||||
let user_email_pii: common_utils::pii::Email = user_email
|
||||
.to_string()
|
||||
.try_into()
|
||||
.map_err(|_| errors::StorageError::MockDbError)?;
|
||||
users
|
||||
.iter_mut()
|
||||
.find(|user| user.email == user_email_pii)
|
||||
.map(|user| {
|
||||
*user = match &update_user {
|
||||
storage::UserUpdate::VerifyUser => storage::User {
|
||||
is_verified: true,
|
||||
..user.to_owned()
|
||||
},
|
||||
storage::UserUpdate::AccountUpdate {
|
||||
name,
|
||||
password,
|
||||
is_verified,
|
||||
preferred_merchant_id,
|
||||
} => storage::User {
|
||||
name: name.clone().map(Secret::new).unwrap_or(user.name.clone()),
|
||||
password: password.clone().unwrap_or(user.password.clone()),
|
||||
is_verified: is_verified.unwrap_or(user.is_verified),
|
||||
preferred_merchant_id: preferred_merchant_id
|
||||
.clone()
|
||||
.or(user.preferred_merchant_id.clone()),
|
||||
..user.to_owned()
|
||||
},
|
||||
};
|
||||
user.to_owned()
|
||||
})
|
||||
.ok_or(
|
||||
errors::StorageError::ValueNotFound(format!(
|
||||
"No user available for user_email = {user_email}"
|
||||
))
|
||||
.into(),
|
||||
)
|
||||
}
|
||||
|
||||
async fn delete_user_by_user_id(
|
||||
&self,
|
||||
user_id: &str,
|
||||
|
||||
@ -92,18 +92,21 @@ Email : {user_email}
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
pub struct EmailToken {
|
||||
email: String,
|
||||
merchant_id: Option<String>,
|
||||
exp: u64,
|
||||
}
|
||||
|
||||
impl EmailToken {
|
||||
pub async fn new_token(
|
||||
email: domain::UserEmail,
|
||||
merchant_id: Option<String>,
|
||||
settings: &configs::settings::Settings,
|
||||
) -> CustomResult<String, UserErrors> {
|
||||
let expiration_duration = std::time::Duration::from_secs(consts::EMAIL_TOKEN_TIME_IN_SECS);
|
||||
let exp = jwt::generate_exp(expiration_duration)?.as_secs();
|
||||
let token_payload = Self {
|
||||
email: email.get_secret().expose(),
|
||||
merchant_id,
|
||||
exp,
|
||||
};
|
||||
jwt::generate_jwt(&token_payload, settings).await
|
||||
@ -112,6 +115,10 @@ impl EmailToken {
|
||||
pub fn get_email(&self) -> &str {
|
||||
self.email.as_str()
|
||||
}
|
||||
|
||||
pub fn get_merchant_id(&self) -> Option<&str> {
|
||||
self.merchant_id.as_deref()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_link_with_token(
|
||||
@ -132,7 +139,7 @@ pub struct VerifyEmail {
|
||||
#[async_trait::async_trait]
|
||||
impl EmailData for VerifyEmail {
|
||||
async fn get_email_data(&self) -> CustomResult<EmailContents, EmailError> {
|
||||
let token = EmailToken::new_token(self.recipient_email.clone(), &self.settings)
|
||||
let token = EmailToken::new_token(self.recipient_email.clone(), None, &self.settings)
|
||||
.await
|
||||
.change_context(EmailError::TokenGenerationFailure)?;
|
||||
|
||||
@ -161,7 +168,7 @@ pub struct ResetPassword {
|
||||
#[async_trait::async_trait]
|
||||
impl EmailData for ResetPassword {
|
||||
async fn get_email_data(&self) -> CustomResult<EmailContents, EmailError> {
|
||||
let token = EmailToken::new_token(self.recipient_email.clone(), &self.settings)
|
||||
let token = EmailToken::new_token(self.recipient_email.clone(), None, &self.settings)
|
||||
.await
|
||||
.change_context(EmailError::TokenGenerationFailure)?;
|
||||
|
||||
@ -191,7 +198,7 @@ pub struct MagicLink {
|
||||
#[async_trait::async_trait]
|
||||
impl EmailData for MagicLink {
|
||||
async fn get_email_data(&self) -> CustomResult<EmailContents, EmailError> {
|
||||
let token = EmailToken::new_token(self.recipient_email.clone(), &self.settings)
|
||||
let token = EmailToken::new_token(self.recipient_email.clone(), None, &self.settings)
|
||||
.await
|
||||
.change_context(EmailError::TokenGenerationFailure)?;
|
||||
|
||||
@ -216,14 +223,19 @@ pub struct InviteUser {
|
||||
pub user_name: domain::UserName,
|
||||
pub settings: std::sync::Arc<configs::settings::Settings>,
|
||||
pub subject: &'static str,
|
||||
pub merchant_id: String,
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl EmailData for InviteUser {
|
||||
async fn get_email_data(&self) -> CustomResult<EmailContents, EmailError> {
|
||||
let token = EmailToken::new_token(self.recipient_email.clone(), &self.settings)
|
||||
.await
|
||||
.change_context(EmailError::TokenGenerationFailure)?;
|
||||
let token = EmailToken::new_token(
|
||||
self.recipient_email.clone(),
|
||||
Some(self.merchant_id.clone()),
|
||||
&self.settings,
|
||||
)
|
||||
.await
|
||||
.change_context(EmailError::TokenGenerationFailure)?;
|
||||
|
||||
let invite_user_link =
|
||||
get_link_with_token(&self.settings.email.base_url, token, "set_password");
|
||||
|
||||
Reference in New Issue
Block a user