mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-11-02 12:06:56 +08:00
feat(users): add support for tenant level users (#6708)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
@ -25,9 +25,13 @@ use router_env::logger;
|
||||
#[cfg(not(feature = "email"))]
|
||||
use user_api::dashboard_metadata::SetMetaDataRequest;
|
||||
|
||||
#[cfg(feature = "v1")]
|
||||
use super::admin;
|
||||
use super::errors::{StorageErrorExt, UserErrors, UserResponse, UserResult};
|
||||
#[cfg(feature = "email")]
|
||||
use crate::services::email::types as email_types;
|
||||
#[cfg(feature = "v1")]
|
||||
use crate::types::transformers::ForeignFrom;
|
||||
use crate::{
|
||||
consts,
|
||||
core::encryption::send_request_to_key_service_for_user,
|
||||
@ -648,6 +652,12 @@ async fn handle_existing_user_invitation(
|
||||
}
|
||||
|
||||
let (org_id, merchant_id, profile_id) = match role_info.get_entity_type() {
|
||||
EntityType::Tenant => {
|
||||
return Err(UserErrors::InvalidRoleOperationWithMessage(
|
||||
"Tenant roles are not allowed for this operation".to_string(),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
EntityType::Organization => (Some(&user_from_token.org_id), None, None),
|
||||
EntityType::Merchant => (
|
||||
Some(&user_from_token.org_id),
|
||||
@ -701,6 +711,12 @@ async fn handle_existing_user_invitation(
|
||||
};
|
||||
|
||||
let _user_role = match role_info.get_entity_type() {
|
||||
EntityType::Tenant => {
|
||||
return Err(UserErrors::InvalidRoleOperationWithMessage(
|
||||
"Tenant roles are not allowed for this operation".to_string(),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
EntityType::Organization => {
|
||||
user_role
|
||||
.add_entity(domain::OrganizationLevel {
|
||||
@ -747,6 +763,12 @@ async fn handle_existing_user_invitation(
|
||||
{
|
||||
let invitee_email = domain::UserEmail::from_pii_email(request.email.clone())?;
|
||||
let entity = match role_info.get_entity_type() {
|
||||
EntityType::Tenant => {
|
||||
return Err(UserErrors::InvalidRoleOperationWithMessage(
|
||||
"Tenant roles are not allowed for this operation".to_string(),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
EntityType::Organization => email_types::Entity {
|
||||
entity_id: user_from_token.org_id.get_string_repr().to_owned(),
|
||||
entity_type: EntityType::Organization,
|
||||
@ -830,6 +852,12 @@ async fn handle_new_user_invitation(
|
||||
};
|
||||
|
||||
let _user_role = match role_info.get_entity_type() {
|
||||
EntityType::Tenant => {
|
||||
return Err(UserErrors::InvalidRoleOperationWithMessage(
|
||||
"Tenant roles are not allowed for this operation".to_string(),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
EntityType::Organization => {
|
||||
user_role
|
||||
.add_entity(domain::OrganizationLevel {
|
||||
@ -880,6 +908,12 @@ async fn handle_new_user_invitation(
|
||||
let _ = req_state.clone();
|
||||
let invitee_email = domain::UserEmail::from_pii_email(request.email.clone())?;
|
||||
let entity = match role_info.get_entity_type() {
|
||||
EntityType::Tenant => {
|
||||
return Err(UserErrors::InvalidRoleOperationWithMessage(
|
||||
"Tenant roles are not allowed for this operation".to_string(),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
EntityType::Organization => email_types::Entity {
|
||||
entity_id: user_from_token.org_id.get_string_repr().to_owned(),
|
||||
entity_type: EntityType::Organization,
|
||||
@ -1235,6 +1269,83 @@ pub async fn create_internal_user(
|
||||
Ok(ApplicationResponse::StatusOk)
|
||||
}
|
||||
|
||||
pub async fn create_tenant_user(
|
||||
state: SessionState,
|
||||
request: user_api::CreateTenantUserRequest,
|
||||
) -> UserResponse<()> {
|
||||
let key_manager_state = &(&state).into();
|
||||
|
||||
let (merchant_id, org_id) = state
|
||||
.store
|
||||
.list_merchant_and_org_ids(key_manager_state, 1, None)
|
||||
.await
|
||||
.change_context(UserErrors::InternalServerError)
|
||||
.attach_printable("Failed to get merchants list for org")?
|
||||
.pop()
|
||||
.ok_or(UserErrors::InternalServerError)
|
||||
.attach_printable("No merchants found in the tenancy")?;
|
||||
|
||||
let new_user = domain::NewUser::try_from((
|
||||
request,
|
||||
domain::MerchantAccountIdentifier {
|
||||
merchant_id,
|
||||
org_id,
|
||||
},
|
||||
))?;
|
||||
let mut store_user: storage_user::UserNew = new_user.clone().try_into()?;
|
||||
store_user.set_is_verified(true);
|
||||
|
||||
state
|
||||
.global_store
|
||||
.insert_user(store_user)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
if e.current_context().is_db_unique_violation() {
|
||||
e.change_context(UserErrors::UserExists)
|
||||
} else {
|
||||
e.change_context(UserErrors::InternalServerError)
|
||||
}
|
||||
})
|
||||
.map(domain::user::UserFromStorage::from)?;
|
||||
|
||||
new_user
|
||||
.get_no_level_user_role(
|
||||
common_utils::consts::ROLE_ID_TENANT_ADMIN.to_string(),
|
||||
UserStatus::Active,
|
||||
)
|
||||
.add_entity(domain::TenantLevel {
|
||||
tenant_id: state.tenant.tenant_id.clone(),
|
||||
})
|
||||
.insert_in_v2(&state)
|
||||
.await
|
||||
.change_context(UserErrors::InternalServerError)?;
|
||||
|
||||
Ok(ApplicationResponse::StatusOk)
|
||||
}
|
||||
|
||||
#[cfg(feature = "v1")]
|
||||
pub async fn create_org_merchant_for_user(
|
||||
state: SessionState,
|
||||
req: user_api::UserOrgMerchantCreateRequest,
|
||||
) -> UserResponse<()> {
|
||||
let db_organization = ForeignFrom::foreign_from(req.clone());
|
||||
let org: diesel_models::organization::Organization = state
|
||||
.store
|
||||
.insert_organization(db_organization)
|
||||
.await
|
||||
.change_context(UserErrors::InternalServerError)?;
|
||||
|
||||
let merchant_account_create_request =
|
||||
utils::user::create_merchant_account_request_for_org(req, org)?;
|
||||
|
||||
admin::create_merchant_account(state.clone(), merchant_account_create_request)
|
||||
.await
|
||||
.change_context(UserErrors::InternalServerError)
|
||||
.attach_printable("Error while creating a merchant")?;
|
||||
|
||||
Ok(ApplicationResponse::StatusOk)
|
||||
}
|
||||
|
||||
pub async fn create_merchant_account(
|
||||
state: SessionState,
|
||||
user_from_token: auth::UserFromToken,
|
||||
@ -1352,7 +1463,7 @@ pub async fn list_user_roles_details(
|
||||
merchant.push(merchant_id.clone());
|
||||
merchant_profile.push((merchant_id, profile_id))
|
||||
}
|
||||
EntityType::Organization => (),
|
||||
EntityType::Tenant | EntityType::Organization => (),
|
||||
};
|
||||
|
||||
Ok::<_, error_stack::Report<UserErrors>>((merchant, merchant_profile))
|
||||
@ -1438,7 +1549,7 @@ pub async fn list_user_roles_details(
|
||||
.ok_or(UserErrors::InternalServerError)?;
|
||||
|
||||
let (merchant, profile) = match entity_type {
|
||||
EntityType::Organization => (None, None),
|
||||
EntityType::Tenant | EntityType::Organization => (None, None),
|
||||
EntityType::Merchant => {
|
||||
let merchant_id = &user_role
|
||||
.merchant_id
|
||||
@ -2437,28 +2548,44 @@ pub async fn list_orgs_for_user(
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
let orgs = state
|
||||
.global_store
|
||||
.list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
|
||||
user_id: user_from_token.user_id.as_str(),
|
||||
tenant_id: user_from_token
|
||||
.tenant_id
|
||||
.as_ref()
|
||||
.unwrap_or(&state.tenant.tenant_id),
|
||||
org_id: None,
|
||||
merchant_id: None,
|
||||
profile_id: None,
|
||||
entity_id: None,
|
||||
version: None,
|
||||
status: Some(UserStatus::Active),
|
||||
limit: None,
|
||||
})
|
||||
.await
|
||||
.change_context(UserErrors::InternalServerError)?
|
||||
.into_iter()
|
||||
.filter_map(|user_role| user_role.org_id)
|
||||
.collect::<HashSet<_>>();
|
||||
let orgs = match role_info.get_entity_type() {
|
||||
EntityType::Tenant => {
|
||||
let key_manager_state = &(&state).into();
|
||||
state
|
||||
.store
|
||||
.list_merchant_and_org_ids(
|
||||
key_manager_state,
|
||||
consts::user::ORG_LIST_LIMIT_FOR_TENANT,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.change_context(UserErrors::InternalServerError)?
|
||||
.into_iter()
|
||||
.map(|(_, org_id)| org_id)
|
||||
.collect::<HashSet<_>>()
|
||||
}
|
||||
EntityType::Organization | EntityType::Merchant | EntityType::Profile => state
|
||||
.global_store
|
||||
.list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
|
||||
user_id: user_from_token.user_id.as_str(),
|
||||
tenant_id: user_from_token
|
||||
.tenant_id
|
||||
.as_ref()
|
||||
.unwrap_or(&state.tenant.tenant_id),
|
||||
org_id: None,
|
||||
merchant_id: None,
|
||||
profile_id: None,
|
||||
entity_id: None,
|
||||
version: None,
|
||||
status: Some(UserStatus::Active),
|
||||
limit: None,
|
||||
})
|
||||
.await
|
||||
.change_context(UserErrors::InternalServerError)?
|
||||
.into_iter()
|
||||
.filter_map(|user_role| user_role.org_id)
|
||||
.collect::<HashSet<_>>(),
|
||||
};
|
||||
|
||||
let resp = futures::future::try_join_all(
|
||||
orgs.iter()
|
||||
@ -2501,7 +2628,7 @@ pub async fn list_merchants_for_user_in_org(
|
||||
}
|
||||
|
||||
let merchant_accounts = match role_info.get_entity_type() {
|
||||
EntityType::Organization => state
|
||||
EntityType::Tenant | EntityType::Organization => state
|
||||
.store
|
||||
.list_merchant_accounts_by_organization_id(&(&state).into(), &user_from_token.org_id)
|
||||
.await
|
||||
@ -2580,7 +2707,7 @@ pub async fn list_profiles_for_user_in_org_and_merchant_account(
|
||||
.await
|
||||
.change_context(UserErrors::InternalServerError)?;
|
||||
let profiles = match role_info.get_entity_type() {
|
||||
EntityType::Organization | EntityType::Merchant => state
|
||||
EntityType::Tenant | EntityType::Organization | EntityType::Merchant => state
|
||||
.store
|
||||
.list_profile_by_merchant_id(
|
||||
key_manager_state,
|
||||
@ -2670,39 +2797,80 @@ pub async fn switch_org_for_user(
|
||||
.into());
|
||||
}
|
||||
|
||||
let user_role = state
|
||||
.global_store
|
||||
.list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
|
||||
user_id: &user_from_token.user_id,
|
||||
tenant_id: user_from_token
|
||||
.tenant_id
|
||||
.as_ref()
|
||||
.unwrap_or(&state.tenant.tenant_id),
|
||||
org_id: Some(&request.org_id),
|
||||
merchant_id: None,
|
||||
profile_id: None,
|
||||
entity_id: None,
|
||||
version: None,
|
||||
status: Some(UserStatus::Active),
|
||||
limit: Some(1),
|
||||
})
|
||||
.await
|
||||
.change_context(UserErrors::InternalServerError)
|
||||
.attach_printable("Failed to list user roles by user_id and org_id")?
|
||||
.pop()
|
||||
.ok_or(UserErrors::InvalidRoleOperationWithMessage(
|
||||
"No user role found for the requested org_id".to_string(),
|
||||
))?;
|
||||
let (merchant_id, profile_id, role_id) = match role_info.get_entity_type() {
|
||||
EntityType::Tenant => {
|
||||
let merchant_id = state
|
||||
.store
|
||||
.list_merchant_accounts_by_organization_id(&(&state).into(), &request.org_id)
|
||||
.await
|
||||
.change_context(UserErrors::InternalServerError)
|
||||
.attach_printable("Failed to get merchant list for org")?
|
||||
.pop()
|
||||
.ok_or(UserErrors::InvalidRoleOperation)
|
||||
.attach_printable("No merchants found for the org id")?
|
||||
.get_id()
|
||||
.to_owned();
|
||||
|
||||
let (merchant_id, profile_id) =
|
||||
utils::user_role::get_single_merchant_id_and_profile_id(&state, &user_role).await?;
|
||||
let key_store = state
|
||||
.store
|
||||
.get_merchant_key_store_by_merchant_id(
|
||||
&(&state).into(),
|
||||
&merchant_id,
|
||||
&state.store.get_master_key().to_vec().into(),
|
||||
)
|
||||
.await
|
||||
.change_context(UserErrors::InternalServerError)?;
|
||||
|
||||
let profile_id = state
|
||||
.store
|
||||
.list_profile_by_merchant_id(&(&state).into(), &key_store, &merchant_id)
|
||||
.await
|
||||
.change_context(UserErrors::InternalServerError)?
|
||||
.pop()
|
||||
.ok_or(UserErrors::InternalServerError)?
|
||||
.get_id()
|
||||
.to_owned();
|
||||
|
||||
(merchant_id, profile_id, user_from_token.role_id)
|
||||
}
|
||||
EntityType::Organization | EntityType::Merchant | EntityType::Profile => {
|
||||
let user_role = state
|
||||
.global_store
|
||||
.list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
|
||||
user_id: &user_from_token.user_id,
|
||||
tenant_id: user_from_token
|
||||
.tenant_id
|
||||
.as_ref()
|
||||
.unwrap_or(&state.tenant.tenant_id),
|
||||
org_id: Some(&request.org_id),
|
||||
merchant_id: None,
|
||||
profile_id: None,
|
||||
entity_id: None,
|
||||
version: None,
|
||||
status: Some(UserStatus::Active),
|
||||
limit: Some(1),
|
||||
})
|
||||
.await
|
||||
.change_context(UserErrors::InternalServerError)
|
||||
.attach_printable("Failed to list user roles by user_id and org_id")?
|
||||
.pop()
|
||||
.ok_or(UserErrors::InvalidRoleOperationWithMessage(
|
||||
"No user role found for the requested org_id".to_string(),
|
||||
))?;
|
||||
|
||||
let (merchant_id, profile_id) =
|
||||
utils::user_role::get_single_merchant_id_and_profile_id(&state, &user_role).await?;
|
||||
|
||||
(merchant_id, profile_id, user_role.role_id)
|
||||
}
|
||||
};
|
||||
|
||||
let token = utils::user::generate_jwt_auth_token_with_attributes(
|
||||
&state,
|
||||
user_from_token.user_id,
|
||||
merchant_id.clone(),
|
||||
request.org_id.clone(),
|
||||
user_role.role_id.clone(),
|
||||
role_id.clone(),
|
||||
profile_id.clone(),
|
||||
user_from_token.tenant_id,
|
||||
)
|
||||
@ -2710,7 +2878,7 @@ pub async fn switch_org_for_user(
|
||||
|
||||
utils::user_role::set_role_permissions_in_cache_by_role_id_merchant_id_org_id(
|
||||
&state,
|
||||
&user_role.role_id,
|
||||
&role_id,
|
||||
&merchant_id,
|
||||
&request.org_id,
|
||||
)
|
||||
@ -2794,7 +2962,7 @@ pub async fn switch_merchant_for_user_in_org(
|
||||
} else {
|
||||
// Match based on the other entity types
|
||||
match role_info.get_entity_type() {
|
||||
EntityType::Organization => {
|
||||
EntityType::Tenant | EntityType::Organization => {
|
||||
let merchant_key_store = state
|
||||
.store
|
||||
.get_merchant_key_store_by_merchant_id(
|
||||
@ -2937,7 +3105,7 @@ pub async fn switch_profile_for_user_in_org_and_merchant(
|
||||
.attach_printable("Failed to retrieve role information")?;
|
||||
|
||||
let (profile_id, role_id) = match role_info.get_entity_type() {
|
||||
EntityType::Organization | EntityType::Merchant => {
|
||||
EntityType::Tenant | EntityType::Organization | EntityType::Merchant => {
|
||||
let merchant_key_store = state
|
||||
.store
|
||||
.get_merchant_key_store_by_merchant_id(
|
||||
|
||||
Reference in New Issue
Block a user