feat(users): Add merchant_id in EmailToken and change user status in reset password (#3473)

This commit is contained in:
Mani Chandra
2024-01-31 13:58:37 +05:30
committed by GitHub
parent 7597f3b692
commit db3d53ff1d
5 changed files with 140 additions and 29 deletions

View File

@ -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

View File

@ -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,

View File

@ -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,

View File

@ -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");