mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-10-31 10:06:32 +08:00 
			
		
		
		
	feat(users): Add merchant_id in EmailToken and change user status in reset password (#3473)
				
					
				
			This commit is contained in:
		| @ -3,7 +3,7 @@ use diesel::{ | ||||
|     associations::HasTable, debug_query, result::Error as DieselError, ExpressionMethods, | ||||
|     JoinOnDsl, QueryDsl, | ||||
| }; | ||||
| use error_stack::{report, IntoReport}; | ||||
| use error_stack::IntoReport; | ||||
| use router_env::{ | ||||
|     logger, | ||||
|     tracing::{self, instrument}, | ||||
| @ -49,19 +49,37 @@ impl User { | ||||
|     pub async fn update_by_user_id( | ||||
|         conn: &PgPooledConn, | ||||
|         user_id: &str, | ||||
|         user: UserUpdate, | ||||
|         user_update: UserUpdate, | ||||
|     ) -> StorageResult<Self> { | ||||
|         generics::generic_update_with_results::<<Self as HasTable>::Table, _, _, _>( | ||||
|         generics::generic_update_with_unique_predicate_get_result::< | ||||
|             <Self as HasTable>::Table, | ||||
|             _, | ||||
|             _, | ||||
|             _, | ||||
|         >( | ||||
|             conn, | ||||
|             users_dsl::user_id.eq(user_id.to_owned()), | ||||
|             UserUpdateInternal::from(user), | ||||
|             UserUpdateInternal::from(user_update), | ||||
|         ) | ||||
|         .await? | ||||
|         .first() | ||||
|         .cloned() | ||||
|         .ok_or_else(|| { | ||||
|             report!(errors::DatabaseError::NotFound).attach_printable("Error while updating user") | ||||
|         }) | ||||
|         .await | ||||
|     } | ||||
|  | ||||
|     pub async fn update_by_user_email( | ||||
|         conn: &PgPooledConn, | ||||
|         user_email: &str, | ||||
|         user_update: UserUpdate, | ||||
|     ) -> StorageResult<Self> { | ||||
|         generics::generic_update_with_unique_predicate_get_result::< | ||||
|             <Self as HasTable>::Table, | ||||
|             _, | ||||
|             _, | ||||
|             _, | ||||
|         >( | ||||
|             conn, | ||||
|             users_dsl::email.eq(user_email.to_owned()), | ||||
|             UserUpdateInternal::from(user_update), | ||||
|         ) | ||||
|         .await | ||||
|     } | ||||
|  | ||||
|     pub async fn delete_by_user_id(conn: &PgPooledConn, user_id: &str) -> StorageResult<bool> { | ||||
|  | ||||
| @ -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,12 +223,17 @@ 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) | ||||
|         let token = EmailToken::new_token( | ||||
|             self.recipient_email.clone(), | ||||
|             Some(self.merchant_id.clone()), | ||||
|             &self.settings, | ||||
|         ) | ||||
|         .await | ||||
|         .change_context(EmailError::TokenGenerationFailure)?; | ||||
|  | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Mani Chandra
					Mani Chandra