mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 00:49:42 +08:00
feat(users): handle email url for users in different tenancies (#6809)
This commit is contained in:
@ -759,8 +759,15 @@ sdk_eligible_payment_methods = "card"
|
||||
enabled = false
|
||||
global_tenant = { schema = "public", redis_key_prefix = "", clickhouse_database = "default"}
|
||||
|
||||
[multitenancy.tenants]
|
||||
public = { base_url = "http://localhost:8080", schema = "public", redis_key_prefix = "", clickhouse_database = "default" } # schema -> Postgres db schema, redis_key_prefix -> redis key distinguisher, base_url -> url of the tenant
|
||||
[multitenancy.tenants.public]
|
||||
base_url = "http://localhost:8080" # URL of the tenant
|
||||
schema = "public" # Postgres db schema
|
||||
redis_key_prefix = "" # Redis key distinguisher
|
||||
clickhouse_database = "default" # Clickhouse database
|
||||
|
||||
[multitenancy.tenants.public.user]
|
||||
control_center_url = "http://localhost:9000" # Control center URL
|
||||
|
||||
|
||||
[user_auth_methods]
|
||||
encryption_key = "" # Encryption key used for encrypting data in user_authentication_methods table
|
||||
|
||||
@ -305,8 +305,14 @@ region = "kms_region" # The AWS region used by the KMS SDK for decrypting data.
|
||||
enabled = false
|
||||
global_tenant = { schema = "public", redis_key_prefix = "", clickhouse_database = "default"}
|
||||
|
||||
[multitenancy.tenants]
|
||||
public = { base_url = "http://localhost:8080", schema = "public", redis_key_prefix = "", clickhouse_database = "default" }
|
||||
[multitenancy.tenants.public]
|
||||
base_url = "http://localhost:8080"
|
||||
schema = "public"
|
||||
redis_key_prefix = ""
|
||||
clickhouse_database = "default"
|
||||
|
||||
[multitenancy.tenants.public.user]
|
||||
control_center_url = "http://localhost:9000"
|
||||
|
||||
[user_auth_methods]
|
||||
encryption_key = "user_auth_table_encryption_key" # Encryption key used for encrypting data in user_authentication_methods table
|
||||
|
||||
@ -775,8 +775,14 @@ sdk_eligible_payment_methods = "card"
|
||||
enabled = false
|
||||
global_tenant = { schema = "public", redis_key_prefix = "", clickhouse_database = "default"}
|
||||
|
||||
[multitenancy.tenants]
|
||||
public = { base_url = "http://localhost:8080", schema = "public", redis_key_prefix = "", clickhouse_database = "default"}
|
||||
[multitenancy.tenants.public]
|
||||
base_url = "http://localhost:8080"
|
||||
schema = "public"
|
||||
redis_key_prefix = ""
|
||||
clickhouse_database = "default"
|
||||
|
||||
[multitenancy.tenants.public.user]
|
||||
control_center_url = "http://localhost:9000"
|
||||
|
||||
[user_auth_methods]
|
||||
encryption_key = "A8EF32E029BC3342E54BF2E172A4D7AA43E8EF9D2C3A624A9F04E2EF79DC698F"
|
||||
|
||||
@ -633,8 +633,14 @@ sdk_eligible_payment_methods = "card"
|
||||
enabled = false
|
||||
global_tenant = { schema = "public", redis_key_prefix = "", clickhouse_database = "default" }
|
||||
|
||||
[multitenancy.tenants]
|
||||
public = { base_url = "http://localhost:8080", schema = "public", redis_key_prefix = "", clickhouse_database = "default" }
|
||||
[multitenancy.tenants.public]
|
||||
base_url = "http://localhost:8080"
|
||||
schema = "public"
|
||||
redis_key_prefix = ""
|
||||
clickhouse_database = "default"
|
||||
|
||||
[multitenancy.tenants.public.user]
|
||||
control_center_url = "http://localhost:9000"
|
||||
|
||||
[user_auth_methods]
|
||||
encryption_key = "A8EF32E029BC3342E54BF2E172A4D7AA43E8EF9D2C3A624A9F04E2EF79DC698F"
|
||||
|
||||
@ -47,6 +47,7 @@ pub trait EmailService: Sync + Send + dyn_clone::DynClone {
|
||||
/// Compose and send email using the email data
|
||||
async fn compose_and_send_email(
|
||||
&self,
|
||||
base_url: &str,
|
||||
email_data: Box<dyn EmailData + Send>,
|
||||
proxy_url: Option<&String>,
|
||||
) -> EmailResult<()>;
|
||||
@ -60,10 +61,11 @@ where
|
||||
{
|
||||
async fn compose_and_send_email(
|
||||
&self,
|
||||
base_url: &str,
|
||||
email_data: Box<dyn EmailData + Send>,
|
||||
proxy_url: Option<&String>,
|
||||
) -> EmailResult<()> {
|
||||
let email_data = email_data.get_email_data();
|
||||
let email_data = email_data.get_email_data(base_url);
|
||||
let email_data = email_data.await?;
|
||||
|
||||
let EmailContents {
|
||||
@ -113,7 +115,7 @@ pub struct EmailContents {
|
||||
#[async_trait::async_trait]
|
||||
pub trait EmailData {
|
||||
/// Get the email contents
|
||||
async fn get_email_data(&self) -> CustomResult<EmailContents, EmailError>;
|
||||
async fn get_email_data(&self, base_url: &str) -> CustomResult<EmailContents, EmailError>;
|
||||
}
|
||||
|
||||
dyn_clone::clone_trait_object!(EmailClient<RichText = Body>);
|
||||
|
||||
@ -169,6 +169,12 @@ pub struct Tenant {
|
||||
pub schema: String,
|
||||
pub redis_key_prefix: String,
|
||||
pub clickhouse_database: String,
|
||||
pub user: TenantUserConfig,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Clone)]
|
||||
pub struct TenantUserConfig {
|
||||
pub control_center_url: String,
|
||||
}
|
||||
|
||||
impl storage_impl::config::TenantConfig for Tenant {
|
||||
@ -1130,6 +1136,7 @@ impl<'de> Deserialize<'de> for TenantConfig {
|
||||
schema: String,
|
||||
redis_key_prefix: String,
|
||||
clickhouse_database: String,
|
||||
user: TenantUserConfig,
|
||||
}
|
||||
|
||||
let hashmap = <HashMap<id_type::TenantId, Inner>>::deserialize(deserializer)?;
|
||||
@ -1146,6 +1153,7 @@ impl<'de> Deserialize<'de> for TenantConfig {
|
||||
schema: value.schema,
|
||||
redis_key_prefix: value.redis_key_prefix,
|
||||
clickhouse_database: value.clickhouse_database,
|
||||
user: value.user,
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
@ -80,6 +80,7 @@ pub async fn send_recon_request(
|
||||
state
|
||||
.email_client
|
||||
.compose_and_send_email(
|
||||
email_types::get_base_url(&state),
|
||||
Box::new(email_contents),
|
||||
state.conf.proxy.https_url.as_ref(),
|
||||
)
|
||||
@ -179,7 +180,7 @@ pub async fn recon_merchant_account_update(
|
||||
let theme = theme_utils::get_most_specific_theme_using_lineage(
|
||||
&state.clone(),
|
||||
ThemeLineage::Merchant {
|
||||
tenant_id: state.tenant.tenant_id,
|
||||
tenant_id: state.tenant.tenant_id.clone(),
|
||||
org_id: auth.merchant_account.get_org_id().clone(),
|
||||
merchant_id: merchant_id.clone(),
|
||||
},
|
||||
@ -210,6 +211,7 @@ pub async fn recon_merchant_account_update(
|
||||
let _ = state
|
||||
.email_client
|
||||
.compose_and_send_email(
|
||||
email_types::get_base_url(&state),
|
||||
Box::new(email_contents),
|
||||
state.conf.proxy.https_url.as_ref(),
|
||||
)
|
||||
|
||||
@ -96,6 +96,7 @@ pub async fn signup_with_merchant_id(
|
||||
let send_email_result = state
|
||||
.email_client
|
||||
.compose_and_send_email(
|
||||
email_types::get_base_url(&state),
|
||||
Box::new(email_contents),
|
||||
state.conf.proxy.https_url.as_ref(),
|
||||
)
|
||||
@ -239,6 +240,7 @@ pub async fn connect_account(
|
||||
let send_email_result = state
|
||||
.email_client
|
||||
.compose_and_send_email(
|
||||
email_types::get_base_url(&state),
|
||||
Box::new(email_contents),
|
||||
state.conf.proxy.https_url.as_ref(),
|
||||
)
|
||||
@ -294,6 +296,7 @@ pub async fn connect_account(
|
||||
let magic_link_result = state
|
||||
.email_client
|
||||
.compose_and_send_email(
|
||||
email_types::get_base_url(&state),
|
||||
Box::new(magic_link_email),
|
||||
state.conf.proxy.https_url.as_ref(),
|
||||
)
|
||||
@ -310,6 +313,7 @@ pub async fn connect_account(
|
||||
let welcome_email_result = state
|
||||
.email_client
|
||||
.compose_and_send_email(
|
||||
email_types::get_base_url(&state),
|
||||
Box::new(welcome_to_community_email),
|
||||
state.conf.proxy.https_url.as_ref(),
|
||||
)
|
||||
@ -438,6 +442,7 @@ pub async fn forgot_password(
|
||||
state
|
||||
.email_client
|
||||
.compose_and_send_email(
|
||||
email_types::get_base_url(&state),
|
||||
Box::new(email_contents),
|
||||
state.conf.proxy.https_url.as_ref(),
|
||||
)
|
||||
@ -845,6 +850,7 @@ async fn handle_existing_user_invitation(
|
||||
is_email_sent = state
|
||||
.email_client
|
||||
.compose_and_send_email(
|
||||
email_types::get_base_url(state),
|
||||
Box::new(email_contents),
|
||||
state.conf.proxy.https_url.as_ref(),
|
||||
)
|
||||
@ -1000,6 +1006,7 @@ async fn handle_new_user_invitation(
|
||||
let send_email_result = state
|
||||
.email_client
|
||||
.compose_and_send_email(
|
||||
email_types::get_base_url(state),
|
||||
Box::new(email_contents),
|
||||
state.conf.proxy.https_url.as_ref(),
|
||||
)
|
||||
@ -1151,6 +1158,7 @@ pub async fn resend_invite(
|
||||
state
|
||||
.email_client
|
||||
.compose_and_send_email(
|
||||
email_types::get_base_url(&state),
|
||||
Box::new(email_contents),
|
||||
state.conf.proxy.https_url.as_ref(),
|
||||
)
|
||||
@ -1782,6 +1790,7 @@ pub async fn send_verification_mail(
|
||||
state
|
||||
.email_client
|
||||
.compose_and_send_email(
|
||||
email_types::get_base_url(&state),
|
||||
Box::new(email_contents),
|
||||
state.conf.proxy.https_url.as_ref(),
|
||||
)
|
||||
|
||||
@ -496,6 +496,7 @@ async fn insert_metadata(
|
||||
let send_email_result = state
|
||||
.email_client
|
||||
.compose_and_send_email(
|
||||
email_types::get_base_url(state),
|
||||
Box::new(email_contents),
|
||||
state.conf.proxy.https_url.as_ref(),
|
||||
)
|
||||
|
||||
@ -225,6 +225,14 @@ pub fn get_link_with_token(
|
||||
email_url
|
||||
}
|
||||
|
||||
pub fn get_base_url(state: &SessionState) -> &str {
|
||||
if !state.conf.multitenancy.enabled {
|
||||
&state.conf.user.base_url
|
||||
} else {
|
||||
&state.tenant.user.control_center_url
|
||||
}
|
||||
}
|
||||
|
||||
pub struct VerifyEmail {
|
||||
pub recipient_email: domain::UserEmail,
|
||||
pub settings: std::sync::Arc<configs::Settings>,
|
||||
@ -237,7 +245,7 @@ pub struct VerifyEmail {
|
||||
/// Currently only HTML is supported
|
||||
#[async_trait::async_trait]
|
||||
impl EmailData for VerifyEmail {
|
||||
async fn get_email_data(&self) -> CustomResult<EmailContents, EmailError> {
|
||||
async fn get_email_data(&self, base_url: &str) -> CustomResult<EmailContents, EmailError> {
|
||||
let token = EmailToken::new_token(
|
||||
self.recipient_email.clone(),
|
||||
None,
|
||||
@ -248,7 +256,7 @@ impl EmailData for VerifyEmail {
|
||||
.change_context(EmailError::TokenGenerationFailure)?;
|
||||
|
||||
let verify_email_link = get_link_with_token(
|
||||
&self.settings.user.base_url,
|
||||
base_url,
|
||||
token,
|
||||
"verify_email",
|
||||
&self.auth_id,
|
||||
@ -279,7 +287,7 @@ pub struct ResetPassword {
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl EmailData for ResetPassword {
|
||||
async fn get_email_data(&self) -> CustomResult<EmailContents, EmailError> {
|
||||
async fn get_email_data(&self, base_url: &str) -> CustomResult<EmailContents, EmailError> {
|
||||
let token = EmailToken::new_token(
|
||||
self.recipient_email.clone(),
|
||||
None,
|
||||
@ -290,7 +298,7 @@ impl EmailData for ResetPassword {
|
||||
.change_context(EmailError::TokenGenerationFailure)?;
|
||||
|
||||
let reset_password_link = get_link_with_token(
|
||||
&self.settings.user.base_url,
|
||||
base_url,
|
||||
token,
|
||||
"set_password",
|
||||
&self.auth_id,
|
||||
@ -322,7 +330,7 @@ pub struct MagicLink {
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl EmailData for MagicLink {
|
||||
async fn get_email_data(&self) -> CustomResult<EmailContents, EmailError> {
|
||||
async fn get_email_data(&self, base_url: &str) -> CustomResult<EmailContents, EmailError> {
|
||||
let token = EmailToken::new_token(
|
||||
self.recipient_email.clone(),
|
||||
None,
|
||||
@ -333,7 +341,7 @@ impl EmailData for MagicLink {
|
||||
.change_context(EmailError::TokenGenerationFailure)?;
|
||||
|
||||
let magic_link_login = get_link_with_token(
|
||||
&self.settings.user.base_url,
|
||||
base_url,
|
||||
token,
|
||||
"verify_email",
|
||||
&self.auth_id,
|
||||
@ -366,7 +374,7 @@ pub struct InviteUser {
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl EmailData for InviteUser {
|
||||
async fn get_email_data(&self) -> CustomResult<EmailContents, EmailError> {
|
||||
async fn get_email_data(&self, base_url: &str) -> CustomResult<EmailContents, EmailError> {
|
||||
let token = EmailToken::new_token(
|
||||
self.recipient_email.clone(),
|
||||
Some(self.entity.clone()),
|
||||
@ -377,7 +385,7 @@ impl EmailData for InviteUser {
|
||||
.change_context(EmailError::TokenGenerationFailure)?;
|
||||
|
||||
let invite_user_link = get_link_with_token(
|
||||
&self.settings.user.base_url,
|
||||
base_url,
|
||||
token,
|
||||
"accept_invite_from_email",
|
||||
&self.auth_id,
|
||||
@ -406,7 +414,7 @@ pub struct ReconActivation {
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl EmailData for ReconActivation {
|
||||
async fn get_email_data(&self) -> CustomResult<EmailContents, EmailError> {
|
||||
async fn get_email_data(&self, _base_url: &str) -> CustomResult<EmailContents, EmailError> {
|
||||
let body = html::get_html_body(EmailBody::ReconActivation {
|
||||
user_name: self.user_name.clone().get_secret().expose(),
|
||||
});
|
||||
@ -461,7 +469,7 @@ impl BizEmailProd {
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl EmailData for BizEmailProd {
|
||||
async fn get_email_data(&self) -> CustomResult<EmailContents, EmailError> {
|
||||
async fn get_email_data(&self, _base_url: &str) -> CustomResult<EmailContents, EmailError> {
|
||||
let body = html::get_html_body(EmailBody::BizEmailProd {
|
||||
user_name: self.user_name.clone().expose(),
|
||||
poc_email: self.poc_email.clone().expose(),
|
||||
@ -491,7 +499,7 @@ pub struct ProFeatureRequest {
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl EmailData for ProFeatureRequest {
|
||||
async fn get_email_data(&self) -> CustomResult<EmailContents, EmailError> {
|
||||
async fn get_email_data(&self, _base_url: &str) -> CustomResult<EmailContents, EmailError> {
|
||||
let recipient = self.recipient_email.clone().into_inner();
|
||||
|
||||
let body = html::get_html_body(EmailBody::ProFeatureRequest {
|
||||
@ -521,7 +529,7 @@ pub struct ApiKeyExpiryReminder {
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl EmailData for ApiKeyExpiryReminder {
|
||||
async fn get_email_data(&self) -> CustomResult<EmailContents, EmailError> {
|
||||
async fn get_email_data(&self, _base_url: &str) -> CustomResult<EmailContents, EmailError> {
|
||||
let recipient = self.recipient_email.clone().into_inner();
|
||||
|
||||
let body = html::get_html_body(EmailBody::ApiKeyExpiryReminder {
|
||||
@ -545,7 +553,7 @@ pub struct WelcomeToCommunity {
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl EmailData for WelcomeToCommunity {
|
||||
async fn get_email_data(&self) -> CustomResult<EmailContents, EmailError> {
|
||||
async fn get_email_data(&self, _base_url: &str) -> CustomResult<EmailContents, EmailError> {
|
||||
let body = html::get_html_body(EmailBody::WelcomeToCommunity);
|
||||
|
||||
Ok(EmailContents {
|
||||
|
||||
@ -9,7 +9,7 @@ use crate::{
|
||||
consts, errors,
|
||||
logger::error,
|
||||
routes::{metrics, SessionState},
|
||||
services::email::types::ApiKeyExpiryReminder,
|
||||
services::email::types::{self as email_types, ApiKeyExpiryReminder},
|
||||
types::{api, domain::UserEmail, storage},
|
||||
utils::{user::theme as theme_utils, OptionExt},
|
||||
};
|
||||
@ -110,6 +110,7 @@ impl ProcessTrackerWorkflow<SessionState> for ApiKeyExpiryWorkflow {
|
||||
.email_client
|
||||
.clone()
|
||||
.compose_and_send_email(
|
||||
email_types::get_base_url(state),
|
||||
Box::new(email_contents),
|
||||
state.conf.proxy.https_url.as_ref(),
|
||||
)
|
||||
|
||||
@ -385,8 +385,14 @@ keys = "accept-language,user-agent"
|
||||
enabled = false
|
||||
global_tenant = { schema = "public", redis_key_prefix = "" }
|
||||
|
||||
[multitenancy.tenants]
|
||||
public = { base_url = "http://localhost:8080", schema = "public", redis_key_prefix = "", clickhouse_database = "default"}
|
||||
[multitenancy.tenants.public]
|
||||
base_url = "http://localhost:8080"
|
||||
schema = "public"
|
||||
redis_key_prefix = ""
|
||||
clickhouse_database = "default"
|
||||
|
||||
[multitenancy.tenants.public.user]
|
||||
control_center_url = "http://localhost:9000"
|
||||
|
||||
[email]
|
||||
sender_email = "example@example.com"
|
||||
|
||||
Reference in New Issue
Block a user