From d539dbd699ede2f4b5ce925fa1fb9b20d298f69e Mon Sep 17 00:00:00 2001 From: Apoorv Dixit Date: Mon, 2 Dec 2024 02:41:26 +0530 Subject: [PATCH 01/10] feat(users): add support for tenant level users --- crates/api_models/src/events/user.rs | 8 +- crates/api_models/src/user.rs | 14 + crates/common_enums/src/enums.rs | 1 + crates/common_utils/src/consts.rs | 2 + .../src/query/merchant_account.rs | 20 ++ crates/router/src/analytics.rs | 6 + crates/router/src/core/user.rs | 262 ++++++++++++++---- crates/router/src/core/user_role.rs | 14 +- crates/router/src/core/user_role/role.rs | 4 +- crates/router/src/db/kafka_store.rs | 12 + crates/router/src/db/merchant_account.rs | 98 +++++++ crates/router/src/routes/app.rs | 4 + crates/router/src/routes/lock_utils.rs | 2 + crates/router/src/routes/user.rs | 39 +++ .../src/services/authorization/permissions.rs | 3 +- .../authorization/roles/predefined_roles.rs | 34 ++- crates/router/src/types/domain/user.rs | 80 +++++- crates/router/src/types/transformers.rs | 18 ++ crates/router/src/utils/user.rs | 37 +++ crates/router/src/utils/user_role.rs | 57 +++- crates/router_env/src/logger/types.rs | 4 + 21 files changed, 638 insertions(+), 81 deletions(-) diff --git a/crates/api_models/src/events/user.rs b/crates/api_models/src/events/user.rs index 4dc2a1a301a..7c316fbfc24 100644 --- a/crates/api_models/src/events/user.rs +++ b/crates/api_models/src/events/user.rs @@ -11,7 +11,7 @@ use crate::user::{ GetMetaDataRequest, GetMetaDataResponse, GetMultipleMetaDataPayload, SetMetaDataRequest, }, AcceptInviteFromEmailRequest, AuthSelectRequest, AuthorizeResponse, BeginTotpResponse, - ChangePasswordRequest, ConnectAccountRequest, CreateInternalUserRequest, + ChangePasswordRequest, ConnectAccountRequest, CreateInternalUserRequest, CreateTenantRequest, CreateUserAuthenticationMethodRequest, ForgotPasswordRequest, GetSsoAuthUrlRequest, GetUserAuthenticationMethodsRequest, GetUserDetailsResponse, GetUserRoleDetailsRequest, GetUserRoleDetailsResponseV2, InviteUserRequest, ReInviteUserRequest, RecoveryCodes, @@ -19,8 +19,8 @@ use crate::user::{ SignUpWithMerchantIdRequest, SsoSignInRequest, SwitchMerchantRequest, SwitchOrganizationRequest, SwitchProfileRequest, TokenResponse, TwoFactorAuthStatusResponse, TwoFactorStatus, UpdateUserAccountDetailsRequest, UpdateUserAuthenticationMethodRequest, - UserFromEmailRequest, UserMerchantCreate, VerifyEmailRequest, VerifyRecoveryCodeRequest, - VerifyTotpRequest, + UserFromEmailRequest, UserMerchantCreate, UserOrgCreateRequest, VerifyEmailRequest, + VerifyRecoveryCodeRequest, VerifyTotpRequest, }; #[cfg(feature = "recon")] @@ -46,6 +46,8 @@ common_utils::impl_api_event_type!( SwitchMerchantRequest, SwitchProfileRequest, CreateInternalUserRequest, + CreateTenantRequest, + UserOrgCreateRequest, UserMerchantCreate, AuthorizeResponse, ConnectAccountRequest, diff --git a/crates/api_models/src/user.rs b/crates/api_models/src/user.rs index 089426c68ba..03684c16972 100644 --- a/crates/api_models/src/user.rs +++ b/crates/api_models/src/user.rs @@ -113,6 +113,20 @@ pub struct CreateInternalUserRequest { pub password: Secret, } +#[derive(serde::Deserialize, Debug, serde::Serialize)] +pub struct CreateTenantRequest { + pub name: Secret, + pub email: pii::Email, + pub password: Secret, +} +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +pub struct UserOrgCreateRequest { + pub organization_name: String, + pub organization_details: Option, + pub metadata: Option, + pub merchant_name: Option, +} + #[derive(Debug, serde::Deserialize, serde::Serialize)] pub struct UserMerchantCreate { pub company_name: String, diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index 781b5e3710a..3223a2bd6b6 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -3192,6 +3192,7 @@ pub enum ApiVersion { #[strum(serialize_all = "snake_case")] #[serde(rename_all = "snake_case")] pub enum EntityType { + Tenant = 3, Organization = 2, Merchant = 1, Profile = 0, diff --git a/crates/common_utils/src/consts.rs b/crates/common_utils/src/consts.rs index b43efcb1feb..e430d59e54d 100644 --- a/crates/common_utils/src/consts.rs +++ b/crates/common_utils/src/consts.rs @@ -119,6 +119,8 @@ pub const MAX_ALLOWED_MERCHANT_NAME_LENGTH: usize = 64; /// Default locale pub const DEFAULT_LOCALE: &str = "en"; +/// Role ID for Tenant Admin +pub const ROLE_ID_TENANT_ADMIN: &str = "tenant_admin"; /// Role ID for Org Admin pub const ROLE_ID_ORGANIZATION_ADMIN: &str = "org_admin"; /// Role ID for Internal View Only diff --git a/crates/diesel_models/src/query/merchant_account.rs b/crates/diesel_models/src/query/merchant_account.rs index fd5a171b7eb..2abae291bf3 100644 --- a/crates/diesel_models/src/query/merchant_account.rs +++ b/crates/diesel_models/src/query/merchant_account.rs @@ -121,6 +121,26 @@ impl MerchantAccount { .await } + pub async fn list_all_merchant_accounts( + conn: &PgPooledConn, + limit: Option, + offset: Option, + ) -> StorageResult> { + generics::generic_filter::< + ::Table, + _, + <::Table as Table>::PrimaryKey, + _, + >( + conn, + dsl_identifier.ne_all(vec![""]), + limit.map(i64::from), + offset.map(i64::from), + None, + ) + .await + } + pub async fn update_all_merchant_accounts( conn: &PgPooledConn, merchant_account: MerchantAccountUpdateInternal, diff --git a/crates/router/src/analytics.rs b/crates/router/src/analytics.rs index d957c3071ff..752c3a52284 100644 --- a/crates/router/src/analytics.rs +++ b/crates/router/src/analytics.rs @@ -1931,6 +1931,9 @@ pub mod routes { EntityType::Organization => Some(AuthInfo::OrgLevel { org_id: user_role.org_id.clone()?, }), + EntityType::Tenant => Some(AuthInfo::OrgLevel { + org_id: auth.org_id.clone(), + }), }) }) .collect(); @@ -2054,6 +2057,9 @@ pub mod routes { EntityType::Organization => Some(AuthInfo::OrgLevel { org_id: user_role.org_id.clone()?, }), + EntityType::Tenant => Some(AuthInfo::OrgLevel { + org_id: auth.org_id.clone(), + }), }) }) .collect(); diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs index 22623c4ca66..7ab56c1246d 100644 --- a/crates/router/src/core/user.rs +++ b/crates/router/src/core/user.rs @@ -651,6 +651,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), @@ -704,6 +710,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 { @@ -750,6 +762,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, @@ -833,6 +851,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 { @@ -883,6 +907,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, @@ -1242,6 +1272,77 @@ pub async fn create_internal_user( Ok(ApplicationResponse::StatusOk) } +pub async fn create_tenant_user( + state: SessionState, + request: user_api::CreateTenantRequest, +) -> UserResponse<()> { + let key_manager_state = &(&state).into(); + + let existing_merchant = state + .store + .list_all_merchant_accounts(key_manager_state, Some(1), None) + .await + .change_context(UserErrors::InternalServerError) + .attach_printable("Failed to get merchants list for org")? + .first() + .ok_or(UserErrors::InternalServerError) + .attach_printable("No merchants found for the tenancy")? + .clone(); + + let new_user = domain::NewUser::try_from((request, existing_merchant))?; + 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) +} + +pub async fn create_org_for_user( + state: SessionState, + req: user_api::UserOrgCreateRequest, +) -> UserResponse<()> { + let db_organization = crate::types::transformers::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)?; + + super::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, @@ -1359,7 +1460,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>((merchant, merchant_profile)) @@ -1445,7 +1546,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 @@ -2474,27 +2575,39 @@ 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::>(); + let orgs = if matches!(role_info.get_entity_type(), EntityType::Tenant) { + let key_manager_state = &(&state).into(); + state + .store + .list_all_merchant_accounts(key_manager_state, None, None) + .await + .change_context(UserErrors::InternalServerError)? + .into_iter() + .filter_map(|merchant_account| Some(merchant_account.get_org_id().to_owned())) + .collect::>() + } else { + 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::>() + }; let resp = futures::future::try_join_all( orgs.iter() @@ -2537,7 +2650,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 @@ -2616,7 +2729,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, @@ -2706,39 +2819,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) = 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")? + .first() + .ok_or(UserErrors::InternalServerError) + .attach_printable("No merchants found for org id")? + .get_id() + .clone(); - 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) + } + 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) + } + }; 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(), + user_from_token.role_id.clone(), profile_id.clone(), user_from_token.tenant_id, ) @@ -2746,7 +2900,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, + &user_from_token.role_id, &merchant_id, &request.org_id, ) @@ -2830,7 +2984,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( @@ -2973,7 +3127,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( diff --git a/crates/router/src/core/user_role.rs b/crates/router/src/core/user_role.rs index eaa655a07f3..74fdc26e017 100644 --- a/crates/router/src/core/user_role.rs +++ b/crates/router/src/core/user_role.rs @@ -698,7 +698,7 @@ pub async fn list_users_in_lineage( requestor_role_info.get_entity_type(), request.entity_type, )? { - EntityType::Organization => { + EntityType::Tenant | EntityType::Organization => { utils::user_role::fetch_user_roles_by_payload( &state, ListUserRolesByOrgIdPayload { @@ -871,6 +871,12 @@ pub async fn list_invitations_for_user( .attach_printable("Failed to compute entity id and type")?; match entity_type { + EntityType::Tenant => { + return Err(UserErrors::InvalidRoleOperationWithMessage( + "Tenant roles are not allowed for this operation".to_string(), + ) + .into()); + } EntityType::Organization => org_ids.push( user_role .org_id @@ -975,6 +981,12 @@ pub async fn list_invitations_for_user( .attach_printable("Failed to compute entity id and type")?; let entity_name = match entity_type { + EntityType::Tenant => { + return Err(UserErrors::InvalidRoleOperationWithMessage( + "Tenant roles are not allowed for this operation".to_string(), + ) + .into()); + } EntityType::Organization => user_role .org_id .as_ref() diff --git a/crates/router/src/core/user_role/role.rs b/crates/router/src/core/user_role/role.rs index 0250415d4fd..1b73f2c9fbf 100644 --- a/crates/router/src/core/user_role/role.rs +++ b/crates/router/src/core/user_role/role.rs @@ -274,7 +274,7 @@ pub async fn list_roles_with_info( let user_role_entity = user_role_info.get_entity_type(); let custom_roles = match utils::user_role::get_min_entity(user_role_entity, request.entity_type)? { - EntityType::Organization => state + EntityType::Tenant | EntityType::Organization => state .store .list_roles_for_org_by_parameters( &user_from_token.org_id, @@ -347,7 +347,7 @@ pub async fn list_roles_at_entity_level( .collect::>(); let custom_roles = match req.entity_type { - EntityType::Organization => state + EntityType::Tenant | EntityType::Organization => state .store .list_roles_for_org_by_parameters( &user_from_token.org_id, diff --git a/crates/router/src/db/kafka_store.rs b/crates/router/src/db/kafka_store.rs index 436755ea720..8c33df07b3c 100644 --- a/crates/router/src/db/kafka_store.rs +++ b/crates/router/src/db/kafka_store.rs @@ -1048,6 +1048,18 @@ impl MerchantAccountInterface for KafkaStore { .list_multiple_merchant_accounts(state, merchant_ids) .await } + + #[cfg(feature = "olap")] + async fn list_all_merchant_accounts( + &self, + state: &KeyManagerState, + limit: Option, + offset: Option, + ) -> CustomResult, errors::StorageError> { + self.diesel_store + .list_all_merchant_accounts(state, limit, offset) + .await + } } #[async_trait::async_trait] diff --git a/crates/router/src/db/merchant_account.rs b/crates/router/src/db/merchant_account.rs index 6eb355e4434..c1d6fba859d 100644 --- a/crates/router/src/db/merchant_account.rs +++ b/crates/router/src/db/merchant_account.rs @@ -87,6 +87,14 @@ where state: &KeyManagerState, merchant_ids: Vec, ) -> CustomResult, errors::StorageError>; + + #[cfg(feature = "olap")] + async fn list_all_merchant_accounts( + &self, + state: &KeyManagerState, + limit: Option, + offset: Option, + ) -> CustomResult, errors::StorageError>; } #[async_trait::async_trait] @@ -411,6 +419,58 @@ impl MerchantAccountInterface for Store { Ok(merchant_accounts) } + #[cfg(feature = "olap")] + #[instrument(skip_all)] + async fn list_all_merchant_accounts( + &self, + state: &KeyManagerState, + limit: Option, + offset: Option, + ) -> CustomResult, errors::StorageError> { + let conn = connection::pg_connection_read(self).await?; + let encrypted_merchant_accounts = + storage::MerchantAccount::list_all_merchant_accounts(&conn, limit, offset) + .await + .map_err(|error| report!(errors::StorageError::from(error)))?; + let db_master_key = self.get_master_key().to_vec().into(); + let merchant_key_stores = self + .list_multiple_key_stores( + state, + encrypted_merchant_accounts + .iter() + .map(|merchant_account| merchant_account.get_id()) + .cloned() + .collect(), + &db_master_key, + ) + .await?; + let key_stores_by_id: HashMap<_, _> = merchant_key_stores + .iter() + .map(|key_store| (key_store.merchant_id.to_owned(), key_store)) + .collect(); + let merchant_accounts = + futures::future::try_join_all(encrypted_merchant_accounts.into_iter().map( + |merchant_account| async { + let key_store = key_stores_by_id.get(merchant_account.get_id()).ok_or( + errors::StorageError::ValueNotFound(format!( + "merchant_key_store with merchant_id = {:?}", + merchant_account.get_id() + )), + )?; + merchant_account + .convert( + state, + key_store.key.get_inner(), + key_store.merchant_id.clone().into(), + ) + .await + .change_context(errors::StorageError::DecryptionError) + }, + )) + .await?; + Ok(merchant_accounts) + } + async fn update_all_merchant_account( &self, merchant_account: storage::MerchantAccountUpdate, @@ -694,6 +754,44 @@ impl MerchantAccountInterface for MockDb { .into_iter() .collect() } + + #[cfg(feature = "olap")] + async fn list_all_merchant_accounts( + &self, + state: &KeyManagerState, + limit: Option, + offset: Option, + ) -> CustomResult, errors::StorageError> { + let accounts = self.merchant_accounts.lock().await; + let offset = offset.unwrap_or(0).try_into().unwrap_or(0); + let limit = limit.map_or(accounts.len(), |l| l.try_into().unwrap_or(accounts.len())); + + let filtered_accounts = accounts + .iter() + .skip(offset) + .take(limit) + .cloned() + .collect::>(); + + let futures = filtered_accounts.into_iter().map(|account| async { + let key_store = self + .get_merchant_key_store_by_merchant_id( + state, + account.get_id(), + &self.get_master_key().to_vec().into(), + ) + .await; + match key_store { + Ok(key) => account + .convert(state, key.key.get_inner(), key.merchant_id.clone().into()) + .await + .change_context(errors::StorageError::DecryptionError), + Err(err) => Err(err), + } + }); + + futures::future::try_join_all(futures).await + } } #[cfg(feature = "accounts_cache")] diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index bb0c547d7f1..4f72d7a4937 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -1869,6 +1869,10 @@ impl User { .service( web::resource("/internal_signup").route(web::post().to(user::internal_user_signup)), ) + .service( + web::resource("/create_tenant").route(web::post().to(user::create_tenant_user)), + ) + .service(web::resource("/create_org").route(web::post().to(user::user_org_create))) .service( web::resource("/create_merchant") .route(web::post().to(user::user_merchant_account_create)), diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs index 4d3718b967d..78b4c153378 100644 --- a/crates/router/src/routes/lock_utils.rs +++ b/crates/router/src/routes/lock_utils.rs @@ -223,9 +223,11 @@ impl From for ApiIdentifier { | Flow::GetMultipleDashboardMetadata | Flow::VerifyPaymentConnector | Flow::InternalUserSignup + | Flow::TenantUserCreate | Flow::SwitchOrg | Flow::SwitchMerchantV2 | Flow::SwitchProfile + | Flow::UserOrgCreate | Flow::UserMerchantAccountCreate | Flow::GenerateSampleData | Flow::DeleteSampleData diff --git a/crates/router/src/routes/user.rs b/crates/router/src/routes/user.rs index 8fc0dad452a..622a0a11bbf 100644 --- a/crates/router/src/routes/user.rs +++ b/crates/router/src/routes/user.rs @@ -227,6 +227,45 @@ pub async fn internal_user_signup( .await } +pub async fn create_tenant_user( + state: web::Data, + http_req: HttpRequest, + json_payload: web::Json, +) -> HttpResponse { + let flow = Flow::TenantUserCreate; + Box::pin(api::server_wrap( + flow, + state.clone(), + &http_req, + json_payload.into_inner(), + |state, _, req, _| user_core::create_tenant_user(state, req), + &auth::AdminApiAuth, + api_locking::LockAction::NotApplicable, + )) + .await +} +pub async fn user_org_create( + state: web::Data, + req: HttpRequest, + json_payload: web::Json, +) -> HttpResponse { + let flow = Flow::UserOrgCreate; + Box::pin(api::server_wrap( + flow, + state, + &req, + json_payload.into_inner(), + |state, _auth: auth::UserFromToken, json_payload, _| { + user_core::create_org_for_user(state, json_payload) + }, + &auth::JWTAuth { + permission: Permission::TenantAccountWrite, + }, + api_locking::LockAction::NotApplicable, + )) + .await +} + pub async fn user_merchant_account_create( state: web::Data, req: HttpRequest, diff --git a/crates/router/src/services/authorization/permissions.rs b/crates/router/src/services/authorization/permissions.rs index 6e472d55623..db93e397f93 100644 --- a/crates/router/src/services/authorization/permissions.rs +++ b/crates/router/src/services/authorization/permissions.rs @@ -33,7 +33,7 @@ generate_permissions! { }, Account: { scopes: [Read, Write], - entities: [Profile, Merchant, Organization] + entities: [Profile, Merchant, Organization, Tenant] }, Connector: { scopes: [Read, Write], @@ -95,6 +95,7 @@ pub fn get_resource_name(resource: &Resource, entity_type: &EntityType) -> &'sta (Resource::Account, EntityType::Profile) => "Business Profile Account", (Resource::Account, EntityType::Merchant) => "Merchant Account", (Resource::Account, EntityType::Organization) => "Organization Account", + (Resource::Account, EntityType::Tenant) => "Tenant Account", } } diff --git a/crates/router/src/services/authorization/roles/predefined_roles.rs b/crates/router/src/services/authorization/roles/predefined_roles.rs index 39f6d47f824..ccd53242692 100644 --- a/crates/router/src/services/authorization/roles/predefined_roles.rs +++ b/crates/router/src/services/authorization/roles/predefined_roles.rs @@ -63,7 +63,39 @@ pub static PREDEFINED_ROLES: Lazy> = Lazy::new(| }, ); - // Merchant Roles + // Tenant Roles + roles.insert( + common_utils::consts::ROLE_ID_TENANT_ADMIN, + RoleInfo { + groups: vec![ + PermissionGroup::OperationsView, + PermissionGroup::OperationsManage, + PermissionGroup::ConnectorsView, + PermissionGroup::ConnectorsManage, + PermissionGroup::WorkflowsView, + PermissionGroup::WorkflowsManage, + PermissionGroup::AnalyticsView, + PermissionGroup::UsersView, + PermissionGroup::UsersManage, + PermissionGroup::MerchantDetailsView, + PermissionGroup::AccountView, + PermissionGroup::MerchantDetailsManage, + PermissionGroup::AccountManage, + PermissionGroup::OrganizationManage, + PermissionGroup::ReconOps, + ], + role_id: common_utils::consts::ROLE_ID_TENANT_ADMIN.to_string(), + role_name: "tenant_admin".to_string(), + scope: RoleScope::Organization, + entity_type: EntityType::Tenant, + is_invitable: true, + is_deletable: true, + is_updatable: true, + is_internal: false, + }, + ); + + // Organization Roles roles.insert( common_utils::consts::ROLE_ID_ORGANIZATION_ADMIN, RoleInfo { diff --git a/crates/router/src/types/domain/user.rs b/crates/router/src/types/domain/user.rs index 6d0d2a4ea07..6cdf970af32 100644 --- a/crates/router/src/types/domain/user.rs +++ b/crates/router/src/types/domain/user.rs @@ -19,6 +19,7 @@ use diesel_models::{ user_role::{UserRole, UserRoleNew}, }; use error_stack::{report, ResultExt}; +use hyperswitch_domain_models::merchant_account::MerchantAccount; use masking::{ExposeInterface, PeekInterface, Secret}; use once_cell::sync::Lazy; use rand::distributions::{Alphanumeric, DistString}; @@ -308,6 +309,17 @@ impl From for NewUserOrganization { } } +impl From<(user_api::CreateTenantRequest, MerchantAccount)> for NewUserOrganization { + fn from((_value, merchant_account): (user_api::CreateTenantRequest, MerchantAccount)) -> Self { + let new_organization = api_org::OrganizationNew { + org_id: merchant_account.get_org_id().clone(), + org_name: None, + }; + let db_organization = ForeignFrom::foreign_from(new_organization); + Self(db_organization) + } +} + #[derive(Clone)] pub struct MerchantId(String); @@ -531,6 +543,19 @@ impl TryFrom for NewUserMerchant { } } +impl TryFrom<(user_api::CreateTenantRequest, MerchantAccount)> for NewUserMerchant { + type Error = error_stack::Report; + fn try_from(value: (user_api::CreateTenantRequest, MerchantAccount)) -> UserResult { + let merchant_id = value.1.get_id().clone(); + let new_organization = NewUserOrganization::from(value); + Ok(Self { + company_name: None, + merchant_id, + new_organization, + }) + } +} + type UserMerchantCreateRequestWithToken = (UserFromStorage, user_api::UserMerchantCreate, UserFromToken); @@ -852,6 +877,31 @@ impl TryFrom for NewUser { } } +impl TryFrom<(user_api::CreateTenantRequest, MerchantAccount)> for NewUser { + type Error = error_stack::Report; + + fn try_from( + (value, merchant_account): (user_api::CreateTenantRequest, MerchantAccount), + ) -> UserResult { + let user_id = uuid::Uuid::new_v4().to_string(); + let email = value.email.clone().try_into()?; + let name = UserName::new(value.name.clone())?; + let password = NewUserPassword { + password: UserPassword::new(value.password.clone())?, + is_temporary: false, + }; + let new_merchant = NewUserMerchant::try_from((value, merchant_account))?; + + Ok(Self { + user_id, + name, + email, + password: Some(password), + new_merchant, + }) + } +} + #[derive(Clone)] pub struct UserFromStorage(pub storage_user::User); @@ -1104,6 +1154,11 @@ impl RecoveryCodes { #[derive(Clone)] pub struct NoLevel; +#[derive(Clone)] +pub struct TenantLevel { + pub tenant_id: id_type::TenantId, +} + #[derive(Clone)] pub struct OrganizationLevel { pub tenant_id: id_type::TenantId, @@ -1157,20 +1212,33 @@ impl NewUserRole { pub struct EntityInfo { tenant_id: id_type::TenantId, - org_id: id_type::OrganizationId, + org_id: Option, merchant_id: Option, profile_id: Option, entity_id: String, entity_type: EntityType, } +impl From for EntityInfo { + fn from(value: TenantLevel) -> Self { + Self { + entity_id: value.tenant_id.get_string_repr().to_owned(), + entity_type: EntityType::Tenant, + tenant_id: value.tenant_id, + org_id: None, + merchant_id: None, + profile_id: None, + } + } +} + impl From for EntityInfo { fn from(value: OrganizationLevel) -> Self { Self { entity_id: value.org_id.get_string_repr().to_owned(), entity_type: EntityType::Organization, tenant_id: value.tenant_id, - org_id: value.org_id, + org_id: Some(value.org_id), merchant_id: None, profile_id: None, } @@ -1183,9 +1251,9 @@ impl From for EntityInfo { entity_id: value.merchant_id.get_string_repr().to_owned(), entity_type: EntityType::Merchant, tenant_id: value.tenant_id, - org_id: value.org_id, - profile_id: None, + org_id: Some(value.org_id), merchant_id: Some(value.merchant_id), + profile_id: None, } } } @@ -1196,7 +1264,7 @@ impl From for EntityInfo { entity_id: value.profile_id.get_string_repr().to_owned(), entity_type: EntityType::Profile, tenant_id: value.tenant_id, - org_id: value.org_id, + org_id: Some(value.org_id), merchant_id: Some(value.merchant_id), profile_id: Some(value.profile_id), } @@ -1216,7 +1284,7 @@ where last_modified_by: self.last_modified_by, created_at: self.created_at, last_modified: self.last_modified, - org_id: Some(entity.org_id), + org_id: entity.org_id, merchant_id: entity.merchant_id, profile_id: entity.profile_id, entity_id: Some(entity.entity_id), diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index 4ae02668957..565aa98f48d 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -1672,6 +1672,24 @@ impl ForeignFrom } } +impl ForeignFrom + for diesel_models::organization::OrganizationNew +{ + fn foreign_from(item: api_models::user::UserOrgCreateRequest) -> Self { + let org_new = api_models::organization::OrganizationNew::new(None); + let api_models::user::UserOrgCreateRequest { + organization_name, + organization_details, + metadata, + .. + } = item; + let mut org_new_db = Self::new(org_new.org_id, Some(organization_name)); + org_new_db.organization_details = organization_details; + org_new_db.metadata = metadata; + org_new_db + } +} + impl ForeignFrom for storage::GatewayStatusMappingNew { fn foreign_from(value: gsm_api_types::GsmCreateRequest) -> Self { Self { diff --git a/crates/router/src/utils/user.rs b/crates/router/src/utils/user.rs index f115a16c062..2aaf91ed5f0 100644 --- a/crates/router/src/utils/user.rs +++ b/crates/router/src/utils/user.rs @@ -5,9 +5,11 @@ use common_enums::UserAuthType; use common_utils::{ encryption::Encryption, errors::CustomResult, id_type, type_name, types::keymanager::Identifier, }; +use diesel_models::{organization, organization::OrganizationBridge}; use error_stack::ResultExt; use masking::{ExposeInterface, Secret}; use redis_interface::RedisConnectionPool; +use router_env::env; use crate::{ consts::user::{REDIS_SSO_PREFIX, REDIS_SSO_TTL}, @@ -278,3 +280,38 @@ pub fn is_sso_auth_type(auth_type: &UserAuthType) -> bool { UserAuthType::Password | UserAuthType::MagicLink => false, } } + +pub fn create_merchant_account_request_for_org( + req: user_api::UserOrgCreateRequest, + org: organization::Organization, +) -> UserResult { + let merchant_id = if matches!(env::which(), env::Env::Production) { + id_type::MerchantId::try_from(domain::MerchantId::new(req.merchant_name.clone().ok_or( + UserErrors::InvalidRoleOperationWithMessage("Merchant name required".to_string()), + )?)?)? + } else { + id_type::MerchantId::new_from_unix_timestamp() + }; + Ok(api_models::admin::MerchantAccountCreate { + merchant_id, + metadata: None, + locker_id: None, + return_url: None, + merchant_name: None, + webhook_details: None, + publishable_key: None, + organization_id: Some(org.get_organization_id()), + merchant_details: None, + routing_algorithm: None, + parent_merchant_id: None, + sub_merchants_enabled: None, + frm_routing_algorithm: None, + #[cfg(feature = "payouts")] + payout_routing_algorithm: None, + primary_business_details: None, + payment_response_hash_key: None, + enable_payment_response_hash: None, + redirect_to_merchant_with_http_post: None, + pm_collect_link_config: None, + }) +} diff --git a/crates/router/src/utils/user_role.rs b/crates/router/src/utils/user_role.rs index 0bd0e81149f..1ce46660f72 100644 --- a/crates/router/src/utils/user_role.rs +++ b/crates/router/src/utils/user_role.rs @@ -13,7 +13,10 @@ use storage_impl::errors::StorageError; use crate::{ consts, core::errors::{UserErrors, UserResult}, - db::user_role::{ListUserRolesByOrgIdPayload, ListUserRolesByUserIdPayload}, + db::{ + errors::StorageErrorExt, + user_role::{ListUserRolesByOrgIdPayload, ListUserRolesByUserIdPayload}, + }, routes::SessionState, services::authorization::{self as authz, roles}, types::domain, @@ -179,24 +182,46 @@ pub async fn update_v1_and_v2_user_roles_in_db( (updated_v1_role, updated_v2_role) } +pub async fn get_single_org_id( + state: &SessionState, + user_role: &UserRole, +) -> UserResult { + match user_role.entity_type { + Some(EntityType::Tenant) => Ok(state + .store + .list_all_merchant_accounts(&state.into(), Some(1), None) + .await + .change_context(UserErrors::InternalServerError) + .attach_printable("Failed to get merchants list for org")? + .first() + .ok_or(UserErrors::InternalServerError) + .attach_printable("No merchants to get the org id")? + .get_org_id() + .clone()), + Some(EntityType::Organization) + | Some(EntityType::Merchant) + | Some(EntityType::Profile) + | None => user_role + .org_id + .clone() + .ok_or(UserErrors::InternalServerError) + .attach_printable("Org_id not found"), + } +} + pub async fn get_single_merchant_id( state: &SessionState, user_role: &UserRole, ) -> UserResult { + let org_id = get_single_org_id(state, user_role).await?; match user_role.entity_type { - Some(EntityType::Organization) => Ok(state + Some(EntityType::Tenant) | Some(EntityType::Organization) => Ok(state .store - .list_merchant_accounts_by_organization_id( - &state.into(), - user_role - .org_id - .as_ref() - .ok_or(UserErrors::InternalServerError) - .attach_printable("org_id not found")?, - ) + .list_merchant_accounts_by_organization_id(&state.into(), &org_id) .await - .change_context(UserErrors::InternalServerError) - .attach_printable("Failed to get merchant list for org")? + .to_not_found_response(UserErrors::InvalidRoleOperationWithMessage( + "Invalid Org Id".to_string(), + ))? .first() .ok_or(UserErrors::InternalServerError) .attach_printable("No merchants found for org_id")? @@ -224,6 +249,12 @@ pub async fn get_lineage_for_user_id_and_entity_for_accepting_invite( )>, > { match entity_type { + EntityType::Tenant => { + return Err(UserErrors::InvalidRoleOperationWithMessage( + "Tenant roles are not allowed for this operation".to_string(), + ) + .into()); + } EntityType::Organization => { let Ok(org_id) = id_type::OrganizationId::try_from(std::borrow::Cow::from(entity_id.clone())) @@ -378,7 +409,7 @@ pub async fn get_single_merchant_id_and_profile_id( .get_entity_id_and_type() .ok_or(UserErrors::InternalServerError)?; let profile_id = match entity_type { - EntityType::Organization | EntityType::Merchant => { + EntityType::Tenant | EntityType::Organization | EntityType::Merchant => { let key_store = state .store .get_merchant_key_store_by_merchant_id( diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs index 5d59e7ddbac..80085789141 100644 --- a/crates/router_env/src/logger/types.rs +++ b/crates/router_env/src/logger/types.rs @@ -366,6 +366,8 @@ pub enum Flow { VerifyPaymentConnector, /// Internal user signup InternalUserSignup, + /// Create tenant level user + TenantUserCreate, /// Switch org SwitchOrg, /// Switch merchant v2 @@ -396,6 +398,8 @@ pub enum Flow { UpdateUserRole, /// Create merchant account for user in a org UserMerchantAccountCreate, + /// Create Org in a given tenancy + UserOrgCreate, /// Generate Sample Data GenerateSampleData, /// Delete Sample Data From 8a56ce5efb4f4b518353726c0dcbe2aa202f86af Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Sun, 1 Dec 2024 21:16:54 +0000 Subject: [PATCH 02/10] chore: run formatter --- crates/router/src/core/user.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs index 7ab56c1246d..c51cc01d88a 100644 --- a/crates/router/src/core/user.rs +++ b/crates/router/src/core/user.rs @@ -1339,7 +1339,7 @@ pub async fn create_org_for_user( .await .change_context(UserErrors::InternalServerError) .attach_printable("Error while creating a merchant")?; - + Ok(ApplicationResponse::StatusOk) } From 3ebd47eb80a37af72d3e292457a8cb1cfedeed66 Mon Sep 17 00:00:00 2001 From: Apoorv Dixit Date: Mon, 2 Dec 2024 03:44:19 +0530 Subject: [PATCH 03/10] fix: address failing checks --- crates/api_models/src/user.rs | 2 +- crates/router/src/core/user.rs | 2 +- crates/router/src/utils/user.rs | 27 +++++++++++++++++++++++++-- crates/router/src/utils/user_role.rs | 10 ++++------ 4 files changed, 31 insertions(+), 10 deletions(-) diff --git a/crates/api_models/src/user.rs b/crates/api_models/src/user.rs index 03684c16972..4e6530a6a30 100644 --- a/crates/api_models/src/user.rs +++ b/crates/api_models/src/user.rs @@ -124,7 +124,7 @@ pub struct UserOrgCreateRequest { pub organization_name: String, pub organization_details: Option, pub metadata: Option, - pub merchant_name: Option, + pub company_name: Option, } #[derive(Debug, serde::Deserialize, serde::Serialize)] diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs index c51cc01d88a..9c373c88f43 100644 --- a/crates/router/src/core/user.rs +++ b/crates/router/src/core/user.rs @@ -2583,7 +2583,7 @@ pub async fn list_orgs_for_user( .await .change_context(UserErrors::InternalServerError)? .into_iter() - .filter_map(|merchant_account| Some(merchant_account.get_org_id().to_owned())) + .map(|merchant_account| merchant_account.get_org_id().to_owned()) .collect::>() } else { state diff --git a/crates/router/src/utils/user.rs b/crates/router/src/utils/user.rs index 2aaf91ed5f0..186d5b091b3 100644 --- a/crates/router/src/utils/user.rs +++ b/crates/router/src/utils/user.rs @@ -281,13 +281,14 @@ pub fn is_sso_auth_type(auth_type: &UserAuthType) -> bool { } } +#[cfg(feature = "v1")] pub fn create_merchant_account_request_for_org( req: user_api::UserOrgCreateRequest, org: organization::Organization, ) -> UserResult { let merchant_id = if matches!(env::which(), env::Env::Production) { - id_type::MerchantId::try_from(domain::MerchantId::new(req.merchant_name.clone().ok_or( - UserErrors::InvalidRoleOperationWithMessage("Merchant name required".to_string()), + id_type::MerchantId::try_from(domain::MerchantId::new(req.company_name.clone().ok_or( + UserErrors::InvalidRoleOperationWithMessage("Company name required".to_string()), )?)?)? } else { id_type::MerchantId::new_from_unix_timestamp() @@ -315,3 +316,25 @@ pub fn create_merchant_account_request_for_org( pm_collect_link_config: None, }) } + +#[cfg(feature = "v2")] +pub fn create_merchant_account_request_for_org( + req: user_api::UserOrgCreateRequest, + org: organization::Organization, +) -> UserResult { + let merchant_name = if let Some(company_name) = org.company_name.clone() { + MerchantName::try_from(company_name) + } else { + MerchantName::try_new("merchant".to_string()) + .change_context(UserErrors::InternalServerError) + .attach_printable("merchant name validation failed") + } + .map(Secret::new)?; + + Ok(api_models::admin::MerchantAccountCreate { + merchant_name, + organization_id: Some(org.get_organization_id()), + metadata: None, + merchant_details: None, + }) +} diff --git a/crates/router/src/utils/user_role.rs b/crates/router/src/utils/user_role.rs index 1ce46660f72..38956d963ce 100644 --- a/crates/router/src/utils/user_role.rs +++ b/crates/router/src/utils/user_role.rs @@ -249,12 +249,10 @@ pub async fn get_lineage_for_user_id_and_entity_for_accepting_invite( )>, > { match entity_type { - EntityType::Tenant => { - return Err(UserErrors::InvalidRoleOperationWithMessage( - "Tenant roles are not allowed for this operation".to_string(), - ) - .into()); - } + EntityType::Tenant => Err(UserErrors::InvalidRoleOperationWithMessage( + "Tenant roles are not allowed for this operation".to_string(), + ) + .into()), EntityType::Organization => { let Ok(org_id) = id_type::OrganizationId::try_from(std::borrow::Cow::from(entity_id.clone())) From e2ab37ba7b48b51e397fc41101aa0f0001d03998 Mon Sep 17 00:00:00 2001 From: Apoorv Dixit Date: Mon, 2 Dec 2024 15:41:05 +0530 Subject: [PATCH 04/10] fix: v2 check for req create --- crates/api_models/src/user.rs | 2 +- crates/router/src/core/user.rs | 1 + crates/router/src/routes/user.rs | 2 ++ crates/router/src/utils/user.rs | 26 ++------------------------ 4 files changed, 6 insertions(+), 25 deletions(-) diff --git a/crates/api_models/src/user.rs b/crates/api_models/src/user.rs index 4e6530a6a30..03684c16972 100644 --- a/crates/api_models/src/user.rs +++ b/crates/api_models/src/user.rs @@ -124,7 +124,7 @@ pub struct UserOrgCreateRequest { pub organization_name: String, pub organization_details: Option, pub metadata: Option, - pub company_name: Option, + pub merchant_name: Option, } #[derive(Debug, serde::Deserialize, serde::Serialize)] diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs index 94ee11d10f1..3e5d0fe974e 100644 --- a/crates/router/src/core/user.rs +++ b/crates/router/src/core/user.rs @@ -1313,6 +1313,7 @@ pub async fn create_tenant_user( Ok(ApplicationResponse::StatusOk) } +#[cfg(feature = "v1")] pub async fn create_org_for_user( state: SessionState, req: user_api::UserOrgCreateRequest, diff --git a/crates/router/src/routes/user.rs b/crates/router/src/routes/user.rs index 622a0a11bbf..bc1732a5b53 100644 --- a/crates/router/src/routes/user.rs +++ b/crates/router/src/routes/user.rs @@ -244,6 +244,8 @@ pub async fn create_tenant_user( )) .await } + +#[cfg(feature = "v1")] pub async fn user_org_create( state: web::Data, req: HttpRequest, diff --git a/crates/router/src/utils/user.rs b/crates/router/src/utils/user.rs index 05d0f217294..a80c4eea413 100644 --- a/crates/router/src/utils/user.rs +++ b/crates/router/src/utils/user.rs @@ -287,8 +287,8 @@ pub fn create_merchant_account_request_for_org( org: organization::Organization, ) -> UserResult { let merchant_id = if matches!(env::which(), env::Env::Production) { - id_type::MerchantId::try_from(domain::MerchantId::new(req.company_name.clone().ok_or( - UserErrors::InvalidRoleOperationWithMessage("Company name required".to_string()), + id_type::MerchantId::try_from(domain::MerchantId::new(req.merchant_name.clone().ok_or( + UserErrors::InvalidRoleOperationWithMessage("Merchant name required".to_string()), )?)?)? } else { id_type::MerchantId::new_from_unix_timestamp() @@ -316,25 +316,3 @@ pub fn create_merchant_account_request_for_org( pm_collect_link_config: None, }) } - -#[cfg(feature = "v2")] -pub fn create_merchant_account_request_for_org( - req: user_api::UserOrgCreateRequest, - org: organization::Organization, -) -> UserResult { - let merchant_name = if let Some(company_name) = org.company_name.clone() { - MerchantName::try_from(company_name) - } else { - MerchantName::try_new("merchant".to_string()) - .change_context(UserErrors::InternalServerError) - .attach_printable("merchant name validation failed") - } - .map(Secret::new)?; - - Ok(api_models::admin::MerchantAccountCreate { - merchant_name, - organization_id: Some(org.get_organization_id()), - metadata: None, - merchant_details: None, - }) -} From 2d7abe3ba544d2c9657e93ff969a1ec4ade04d1d Mon Sep 17 00:00:00 2001 From: Apoorv Dixit Date: Mon, 2 Dec 2024 16:23:24 +0530 Subject: [PATCH 05/10] fix: token changes --- crates/api_models/src/user.rs | 2 +- .../src/types/domain/user/decision_manager.rs | 57 ++++++++++++++++--- crates/router/src/utils/user.rs | 8 +-- 3 files changed, 55 insertions(+), 12 deletions(-) diff --git a/crates/api_models/src/user.rs b/crates/api_models/src/user.rs index 03684c16972..9c62f09fbbd 100644 --- a/crates/api_models/src/user.rs +++ b/crates/api_models/src/user.rs @@ -124,7 +124,7 @@ pub struct UserOrgCreateRequest { pub organization_name: String, pub organization_details: Option, pub metadata: Option, - pub merchant_name: Option, + pub merchant_name: String, } #[derive(Debug, serde::Deserialize, serde::Serialize)] diff --git a/crates/router/src/types/domain/user/decision_manager.rs b/crates/router/src/types/domain/user/decision_manager.rs index 10990da6ccb..c19645d28ad 100644 --- a/crates/router/src/types/domain/user/decision_manager.rs +++ b/crates/router/src/types/domain/user/decision_manager.rs @@ -124,18 +124,61 @@ impl JWTFlow { next_flow: &NextFlow, user_role: &UserRole, ) -> UserResult> { - let (merchant_id, profile_id) = - utils::user_role::get_single_merchant_id_and_profile_id(state, user_role).await?; + let (org_id, merchant_id, profile_id) = match user_role.entity_type { + Some(common_enums::EntityType::Tenant) => { + let key_manager_state = &(&*state).into(); + + let merchant_account = state + .store + .list_all_merchant_accounts(key_manager_state, Some(1), None) + .await + .change_context(UserErrors::InternalServerError)? + .pop() + .ok_or(UserErrors::InternalServerError)?; + + let merchant_id = merchant_account.get_id().to_owned(); + let org_id = merchant_account.get_org_id().to_owned(); + + 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(); + (org_id, merchant_id, profile_id) + } + _ => { + let org_id = user_role + .org_id + .clone() + .ok_or(report!(UserErrors::InternalServerError)) + .attach_printable("org_id not found")?; + + let (merchant_id, profile_id) = + utils::user_role::get_single_merchant_id_and_profile_id(state, user_role) + .await?; + (org_id, merchant_id, profile_id) + } + }; auth::AuthToken::new_token( next_flow.user.get_user_id().to_string(), merchant_id, user_role.role_id.clone(), &state.conf, - user_role - .org_id - .clone() - .ok_or(report!(UserErrors::InternalServerError)) - .attach_printable("org_id not found")?, + org_id, profile_id, Some(user_role.tenant_id.clone()), ) diff --git a/crates/router/src/utils/user.rs b/crates/router/src/utils/user.rs index a80c4eea413..acbcac40a0f 100644 --- a/crates/router/src/utils/user.rs +++ b/crates/router/src/utils/user.rs @@ -287,18 +287,18 @@ pub fn create_merchant_account_request_for_org( org: organization::Organization, ) -> UserResult { let merchant_id = if matches!(env::which(), env::Env::Production) { - id_type::MerchantId::try_from(domain::MerchantId::new(req.merchant_name.clone().ok_or( - UserErrors::InvalidRoleOperationWithMessage("Merchant name required".to_string()), - )?)?)? + id_type::MerchantId::try_from(domain::MerchantId::new(req.merchant_name.clone())?)? } else { id_type::MerchantId::new_from_unix_timestamp() }; + + let company_name = domain::UserCompanyName::new(req.merchant_name.clone())?; Ok(api_models::admin::MerchantAccountCreate { merchant_id, metadata: None, locker_id: None, return_url: None, - merchant_name: None, + merchant_name: Some(Secret::new(company_name.get_secret())), webhook_details: None, publishable_key: None, organization_id: Some(org.get_organization_id()), From 1fc97d88ffd2127c1415792f05307f94f6e6971d Mon Sep 17 00:00:00 2001 From: Apoorv Dixit Date: Tue, 3 Dec 2024 04:42:45 +0530 Subject: [PATCH 06/10] fix: resolve review comments --- crates/router/src/core/user.rs | 55 ++++++++++--------- .../authorization/roles/predefined_roles.rs | 6 +- .../src/types/domain/user/decision_manager.rs | 2 +- 3 files changed, 32 insertions(+), 31 deletions(-) diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs index 3e5d0fe974e..033c62069dd 100644 --- a/crates/router/src/core/user.rs +++ b/crates/router/src/core/user.rs @@ -28,6 +28,8 @@ use user_api::dashboard_metadata::SetMetaDataRequest; 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, @@ -1276,10 +1278,9 @@ pub async fn create_tenant_user( .await .change_context(UserErrors::InternalServerError) .attach_printable("Failed to get merchants list for org")? - .first() + .pop() .ok_or(UserErrors::InternalServerError) - .attach_printable("No merchants found for the tenancy")? - .clone(); + .attach_printable("No merchants found in the tenancy")?; let new_user = domain::NewUser::try_from((request, existing_merchant))?; let mut store_user: storage_user::UserNew = new_user.clone().try_into()?; @@ -1318,7 +1319,7 @@ pub async fn create_org_for_user( state: SessionState, req: user_api::UserOrgCreateRequest, ) -> UserResponse<()> { - let db_organization = crate::types::transformers::ForeignFrom::foreign_from(req.clone()); + let db_organization = ForeignFrom::foreign_from(req.clone()); let org: diesel_models::organization::Organization = state .store .insert_organization(db_organization) @@ -2559,19 +2560,19 @@ pub async fn list_orgs_for_user( ) .into()); } - - let orgs = if matches!(role_info.get_entity_type(), EntityType::Tenant) { - let key_manager_state = &(&state).into(); - state - .store - .list_all_merchant_accounts(key_manager_state, None, None) - .await - .change_context(UserErrors::InternalServerError)? - .into_iter() - .map(|merchant_account| merchant_account.get_org_id().to_owned()) - .collect::>() - } else { - state + let orgs = match role_info.get_entity_type() { + EntityType::Tenant => { + let key_manager_state = &(&state).into(); + state + .store + .list_all_merchant_accounts(key_manager_state, None, None) + .await + .change_context(UserErrors::InternalServerError)? + .into_iter() + .map(|merchant_account| merchant_account.get_org_id().to_owned()) + .collect::>() + } + 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(), @@ -2591,7 +2592,7 @@ pub async fn list_orgs_for_user( .change_context(UserErrors::InternalServerError)? .into_iter() .filter_map(|user_role| user_role.org_id) - .collect::>() + .collect::>(), }; let resp = futures::future::try_join_all( @@ -2804,7 +2805,7 @@ pub async fn switch_org_for_user( .into()); } - let (merchant_id, profile_id) = match role_info.get_entity_type() { + let (merchant_id, profile_id, role_id) = match role_info.get_entity_type() { EntityType::Tenant => { let merchant_id = state .store @@ -2812,11 +2813,11 @@ pub async fn switch_org_for_user( .await .change_context(UserErrors::InternalServerError) .attach_printable("Failed to get merchant list for org")? - .first() - .ok_or(UserErrors::InternalServerError) - .attach_printable("No merchants found for org id")? + .pop() + .ok_or(UserErrors::InvalidRoleOperation) + .attach_printable("No merchants found for the org id")? .get_id() - .clone(); + .to_owned(); let key_store = state .store @@ -2838,7 +2839,7 @@ pub async fn switch_org_for_user( .get_id() .to_owned(); - (merchant_id, profile_id) + (merchant_id, profile_id, user_from_token.role_id) } EntityType::Organization | EntityType::Merchant | EntityType::Profile => { let user_role = state @@ -2868,7 +2869,7 @@ pub async fn switch_org_for_user( let (merchant_id, profile_id) = utils::user_role::get_single_merchant_id_and_profile_id(&state, &user_role).await?; - (merchant_id, profile_id) + (merchant_id, profile_id, user_role.role_id) } }; @@ -2877,7 +2878,7 @@ pub async fn switch_org_for_user( user_from_token.user_id, merchant_id.clone(), request.org_id.clone(), - user_from_token.role_id.clone(), + role_id.clone(), profile_id.clone(), user_from_token.tenant_id, ) @@ -2885,7 +2886,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_from_token.role_id, + &role_id, &merchant_id, &request.org_id, ) diff --git a/crates/router/src/services/authorization/roles/predefined_roles.rs b/crates/router/src/services/authorization/roles/predefined_roles.rs index ccd53242692..15031bd3828 100644 --- a/crates/router/src/services/authorization/roles/predefined_roles.rs +++ b/crates/router/src/services/authorization/roles/predefined_roles.rs @@ -88,9 +88,9 @@ pub static PREDEFINED_ROLES: Lazy> = Lazy::new(| role_name: "tenant_admin".to_string(), scope: RoleScope::Organization, entity_type: EntityType::Tenant, - is_invitable: true, - is_deletable: true, - is_updatable: true, + is_invitable: false, + is_deletable: false, + is_updatable: false, is_internal: false, }, ); diff --git a/crates/router/src/types/domain/user/decision_manager.rs b/crates/router/src/types/domain/user/decision_manager.rs index c19645d28ad..f54bd1fef01 100644 --- a/crates/router/src/types/domain/user/decision_manager.rs +++ b/crates/router/src/types/domain/user/decision_manager.rs @@ -126,7 +126,7 @@ impl JWTFlow { ) -> UserResult> { let (org_id, merchant_id, profile_id) = match user_role.entity_type { Some(common_enums::EntityType::Tenant) => { - let key_manager_state = &(&*state).into(); + let key_manager_state = &state.into(); let merchant_account = state .store From e1af16e985c992c9b40355933f6383f6c65e1920 Mon Sep 17 00:00:00 2001 From: Apoorv Dixit Date: Tue, 3 Dec 2024 16:28:12 +0530 Subject: [PATCH 07/10] refactor: cover nitpicks --- crates/api_models/src/events/user.rs | 4 +- crates/api_models/src/user.rs | 2 +- .../src/query/merchant_account.rs | 8 +- crates/router/src/consts/user.rs | 2 + crates/router/src/core/user.rs | 32 +++-- crates/router/src/db/kafka_store.rs | 11 +- crates/router/src/db/merchant_account.rs | 118 +++++++----------- crates/router/src/routes/lock_utils.rs | 2 +- crates/router/src/routes/user.rs | 6 +- crates/router/src/types/domain/user.rs | 18 +++ .../src/types/domain/user/decision_manager.rs | 37 +----- crates/router/src/types/transformers.rs | 18 --- crates/router/src/utils/user.rs | 2 +- crates/router/src/utils/user_role.rs | 94 ++++++++------ crates/router_env/src/logger/types.rs | 2 +- 15 files changed, 168 insertions(+), 188 deletions(-) diff --git a/crates/api_models/src/events/user.rs b/crates/api_models/src/events/user.rs index 7c316fbfc24..0b4ae48b482 100644 --- a/crates/api_models/src/events/user.rs +++ b/crates/api_models/src/events/user.rs @@ -19,7 +19,7 @@ use crate::user::{ SignUpWithMerchantIdRequest, SsoSignInRequest, SwitchMerchantRequest, SwitchOrganizationRequest, SwitchProfileRequest, TokenResponse, TwoFactorAuthStatusResponse, TwoFactorStatus, UpdateUserAccountDetailsRequest, UpdateUserAuthenticationMethodRequest, - UserFromEmailRequest, UserMerchantCreate, UserOrgCreateRequest, VerifyEmailRequest, + UserFromEmailRequest, UserMerchantCreate, UserOrgMerchantCreateRequest, VerifyEmailRequest, VerifyRecoveryCodeRequest, VerifyTotpRequest, }; @@ -47,7 +47,7 @@ common_utils::impl_api_event_type!( SwitchProfileRequest, CreateInternalUserRequest, CreateTenantRequest, - UserOrgCreateRequest, + UserOrgMerchantCreateRequest, UserMerchantCreate, AuthorizeResponse, ConnectAccountRequest, diff --git a/crates/api_models/src/user.rs b/crates/api_models/src/user.rs index 9c62f09fbbd..0faccda72b4 100644 --- a/crates/api_models/src/user.rs +++ b/crates/api_models/src/user.rs @@ -120,7 +120,7 @@ pub struct CreateTenantRequest { pub password: Secret, } #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] -pub struct UserOrgCreateRequest { +pub struct UserOrgMerchantCreateRequest { pub organization_name: String, pub organization_details: Option, pub metadata: Option, diff --git a/crates/diesel_models/src/query/merchant_account.rs b/crates/diesel_models/src/query/merchant_account.rs index 2abae291bf3..202c47049ff 100644 --- a/crates/diesel_models/src/query/merchant_account.rs +++ b/crates/diesel_models/src/query/merchant_account.rs @@ -123,8 +123,8 @@ impl MerchantAccount { pub async fn list_all_merchant_accounts( conn: &PgPooledConn, - limit: Option, - offset: Option, + limit: u32, + offset: u32, ) -> StorageResult> { generics::generic_filter::< ::Table, @@ -134,8 +134,8 @@ impl MerchantAccount { >( conn, dsl_identifier.ne_all(vec![""]), - limit.map(i64::from), - offset.map(i64::from), + Some(i64::from(limit)), + Some(i64::from(offset)), None, ) .await diff --git a/crates/router/src/consts/user.rs b/crates/router/src/consts/user.rs index 32ca4ad31d7..d4eb12e39bf 100644 --- a/crates/router/src/consts/user.rs +++ b/crates/router/src/consts/user.rs @@ -19,6 +19,8 @@ pub const TOTP_TOLERANCE: u8 = 1; pub const TOTP_MAX_ATTEMPTS: u8 = 4; /// Number of maximum attempts user has for recovery code pub const RECOVERY_CODE_MAX_ATTEMPTS: u8 = 4; +/// The default number of organizations to fetch for a tenant-level user +pub const ORG_LIST_LIMIT_FOR_TENANT: u32 = 20; pub const MAX_PASSWORD_LENGTH: usize = 70; pub const MIN_PASSWORD_LENGTH: usize = 8; diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs index 033c62069dd..b006264b954 100644 --- a/crates/router/src/core/user.rs +++ b/crates/router/src/core/user.rs @@ -1272,9 +1272,9 @@ pub async fn create_tenant_user( ) -> UserResponse<()> { let key_manager_state = &(&state).into(); - let existing_merchant = state + let (merchant_id, _) = state .store - .list_all_merchant_accounts(key_manager_state, Some(1), None) + .list_merchant_and_org_ids(key_manager_state, 1, 0) .await .change_context(UserErrors::InternalServerError) .attach_printable("Failed to get merchants list for org")? @@ -1282,7 +1282,25 @@ pub async fn create_tenant_user( .ok_or(UserErrors::InternalServerError) .attach_printable("No merchants found in the tenancy")?; - let new_user = domain::NewUser::try_from((request, existing_merchant))?; + let key_store = state + .store + .get_merchant_key_store_by_merchant_id( + key_manager_state, + &merchant_id, + &state.store.get_master_key().to_vec().into(), + ) + .await + .change_context(UserErrors::InternalServerError) + .attach_printable("Error while fetching the key store by merchant_id")?; + + let merchant_account = state + .store + .find_merchant_account_by_merchant_id(key_manager_state, &merchant_id, &key_store) + .await + .change_context(UserErrors::InternalServerError) + .attach_printable("Error while fetching the merchant_account by merchant_id")?; + + let new_user = domain::NewUser::try_from((request, merchant_account))?; let mut store_user: storage_user::UserNew = new_user.clone().try_into()?; store_user.set_is_verified(true); @@ -1315,9 +1333,9 @@ pub async fn create_tenant_user( } #[cfg(feature = "v1")] -pub async fn create_org_for_user( +pub async fn create_org_merchant_for_user( state: SessionState, - req: user_api::UserOrgCreateRequest, + req: user_api::UserOrgMerchantCreateRequest, ) -> UserResponse<()> { let db_organization = ForeignFrom::foreign_from(req.clone()); let org: diesel_models::organization::Organization = state @@ -2565,11 +2583,11 @@ pub async fn list_orgs_for_user( let key_manager_state = &(&state).into(); state .store - .list_all_merchant_accounts(key_manager_state, None, None) + .list_merchant_and_org_ids(key_manager_state, consts::user::ORG_LIST_LIMIT_FOR_TENANT, 0) .await .change_context(UserErrors::InternalServerError)? .into_iter() - .map(|merchant_account| merchant_account.get_org_id().to_owned()) + .map(|(_, org_id)| org_id) // Extract the org_id from the tuple .collect::>() } EntityType::Organization | EntityType::Merchant | EntityType::Profile => state diff --git a/crates/router/src/db/kafka_store.rs b/crates/router/src/db/kafka_store.rs index bedd1ff3672..3b6c75a994a 100644 --- a/crates/router/src/db/kafka_store.rs +++ b/crates/router/src/db/kafka_store.rs @@ -1050,14 +1050,15 @@ impl MerchantAccountInterface for KafkaStore { } #[cfg(feature = "olap")] - async fn list_all_merchant_accounts( + async fn list_merchant_and_org_ids( &self, state: &KeyManagerState, - limit: Option, - offset: Option, - ) -> CustomResult, errors::StorageError> { + limit: u32, + offset: u32, + ) -> CustomResult, errors::StorageError> + { self.diesel_store - .list_all_merchant_accounts(state, limit, offset) + .list_merchant_and_org_ids(state, limit, offset) .await } } diff --git a/crates/router/src/db/merchant_account.rs b/crates/router/src/db/merchant_account.rs index c1d6fba859d..d6f7369f48e 100644 --- a/crates/router/src/db/merchant_account.rs +++ b/crates/router/src/db/merchant_account.rs @@ -89,12 +89,18 @@ where ) -> CustomResult, errors::StorageError>; #[cfg(feature = "olap")] - async fn list_all_merchant_accounts( + async fn list_merchant_and_org_ids( &self, state: &KeyManagerState, - limit: Option, - offset: Option, - ) -> CustomResult, errors::StorageError>; + limit: u32, + offset: u32, + ) -> CustomResult< + Vec<( + common_utils::id_type::MerchantId, + common_utils::id_type::OrganizationId, + )>, + errors::StorageError, + >; } #[async_trait::async_trait] @@ -421,54 +427,33 @@ impl MerchantAccountInterface for Store { #[cfg(feature = "olap")] #[instrument(skip_all)] - async fn list_all_merchant_accounts( + async fn list_merchant_and_org_ids( &self, - state: &KeyManagerState, - limit: Option, - offset: Option, - ) -> CustomResult, errors::StorageError> { + _state: &KeyManagerState, + limit: u32, + offset: u32, + ) -> CustomResult< + Vec<( + common_utils::id_type::MerchantId, + common_utils::id_type::OrganizationId, + )>, + errors::StorageError, + > { let conn = connection::pg_connection_read(self).await?; let encrypted_merchant_accounts = storage::MerchantAccount::list_all_merchant_accounts(&conn, limit, offset) .await .map_err(|error| report!(errors::StorageError::from(error)))?; - let db_master_key = self.get_master_key().to_vec().into(); - let merchant_key_stores = self - .list_multiple_key_stores( - state, - encrypted_merchant_accounts - .iter() - .map(|merchant_account| merchant_account.get_id()) - .cloned() - .collect(), - &db_master_key, - ) - .await?; - let key_stores_by_id: HashMap<_, _> = merchant_key_stores - .iter() - .map(|key_store| (key_store.merchant_id.to_owned(), key_store)) + + let merchant_and_org_ids = encrypted_merchant_accounts + .into_iter() + .map(|merchant_account| { + let merchant_id = merchant_account.get_id().clone(); + let org_id = merchant_account.organization_id; + (merchant_id, org_id) + }) .collect(); - let merchant_accounts = - futures::future::try_join_all(encrypted_merchant_accounts.into_iter().map( - |merchant_account| async { - let key_store = key_stores_by_id.get(merchant_account.get_id()).ok_or( - errors::StorageError::ValueNotFound(format!( - "merchant_key_store with merchant_id = {:?}", - merchant_account.get_id() - )), - )?; - merchant_account - .convert( - state, - key_store.key.get_inner(), - key_store.merchant_id.clone().into(), - ) - .await - .change_context(errors::StorageError::DecryptionError) - }, - )) - .await?; - Ok(merchant_accounts) + Ok(merchant_and_org_ids) } async fn update_all_merchant_account( @@ -756,41 +741,30 @@ impl MerchantAccountInterface for MockDb { } #[cfg(feature = "olap")] - async fn list_all_merchant_accounts( + async fn list_merchant_and_org_ids( &self, - state: &KeyManagerState, - limit: Option, - offset: Option, - ) -> CustomResult, errors::StorageError> { + _state: &KeyManagerState, + limit: u32, + offset: u32, + ) -> CustomResult< + Vec<( + common_utils::id_type::MerchantId, + common_utils::id_type::OrganizationId, + )>, + errors::StorageError, + > { let accounts = self.merchant_accounts.lock().await; - let offset = offset.unwrap_or(0).try_into().unwrap_or(0); - let limit = limit.map_or(accounts.len(), |l| l.try_into().unwrap_or(accounts.len())); + let offset = offset.try_into().unwrap_or(0); + let limit = limit.try_into().unwrap_or(accounts.len()); - let filtered_accounts = accounts + let merchant_and_org_ids = accounts .iter() .skip(offset) .take(limit) - .cloned() + .map(|account| (account.get_id().clone(), account.organization_id.clone())) .collect::>(); - let futures = filtered_accounts.into_iter().map(|account| async { - let key_store = self - .get_merchant_key_store_by_merchant_id( - state, - account.get_id(), - &self.get_master_key().to_vec().into(), - ) - .await; - match key_store { - Ok(key) => account - .convert(state, key.key.get_inner(), key.merchant_id.clone().into()) - .await - .change_context(errors::StorageError::DecryptionError), - Err(err) => Err(err), - } - }); - - futures::future::try_join_all(futures).await + Ok(merchant_and_org_ids) } } diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs index 78b4c153378..b9b5391b5da 100644 --- a/crates/router/src/routes/lock_utils.rs +++ b/crates/router/src/routes/lock_utils.rs @@ -227,7 +227,7 @@ impl From for ApiIdentifier { | Flow::SwitchOrg | Flow::SwitchMerchantV2 | Flow::SwitchProfile - | Flow::UserOrgCreate + | Flow::UserOrgMerchantCreate | Flow::UserMerchantAccountCreate | Flow::GenerateSampleData | Flow::DeleteSampleData diff --git a/crates/router/src/routes/user.rs b/crates/router/src/routes/user.rs index bc1732a5b53..f81d9c86e32 100644 --- a/crates/router/src/routes/user.rs +++ b/crates/router/src/routes/user.rs @@ -249,16 +249,16 @@ pub async fn create_tenant_user( pub async fn user_org_create( state: web::Data, req: HttpRequest, - json_payload: web::Json, + json_payload: web::Json, ) -> HttpResponse { - let flow = Flow::UserOrgCreate; + let flow = Flow::UserOrgMerchantCreate; Box::pin(api::server_wrap( flow, state, &req, json_payload.into_inner(), |state, _auth: auth::UserFromToken, json_payload, _| { - user_core::create_org_for_user(state, json_payload) + user_core::create_org_merchant_for_user(state, json_payload) }, &auth::JWTAuth { permission: Permission::TenantAccountWrite, diff --git a/crates/router/src/types/domain/user.rs b/crates/router/src/types/domain/user.rs index 4394476be93..6016e4fe866 100644 --- a/crates/router/src/types/domain/user.rs +++ b/crates/router/src/types/domain/user.rs @@ -324,6 +324,24 @@ impl From<(user_api::CreateTenantRequest, MerchantAccount)> for NewUserOrganizat } } +impl ForeignFrom + for diesel_models::organization::OrganizationNew +{ + fn foreign_from(item: api_models::user::UserOrgMerchantCreateRequest) -> Self { + let org_new = api_models::organization::OrganizationNew::new(None); + let api_models::user::UserOrgMerchantCreateRequest { + organization_name, + organization_details, + metadata, + .. + } = item; + let mut org_new_db = Self::new(org_new.org_id, Some(organization_name)); + org_new_db.organization_details = organization_details; + org_new_db.metadata = metadata; + org_new_db + } +} + #[derive(Clone)] pub struct MerchantId(String); diff --git a/crates/router/src/types/domain/user/decision_manager.rs b/crates/router/src/types/domain/user/decision_manager.rs index f54bd1fef01..b7f1d8e7017 100644 --- a/crates/router/src/types/domain/user/decision_manager.rs +++ b/crates/router/src/types/domain/user/decision_manager.rs @@ -126,38 +126,11 @@ impl JWTFlow { ) -> UserResult> { let (org_id, merchant_id, profile_id) = match user_role.entity_type { Some(common_enums::EntityType::Tenant) => { - let key_manager_state = &state.into(); - - let merchant_account = state - .store - .list_all_merchant_accounts(key_manager_state, Some(1), None) - .await - .change_context(UserErrors::InternalServerError)? - .pop() - .ok_or(UserErrors::InternalServerError)?; - - let merchant_id = merchant_account.get_id().to_owned(); - let org_id = merchant_account.get_org_id().to_owned(); - - 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(); + let org_id = utils::user_role::get_single_org_id(state, user_role).await?; + let merchant_id = + utils::user_role::get_single_merchant_id(state, user_role, &org_id).await?; + let profile_id = + utils::user_role::get_single_profile_id(state, user_role, &merchant_id).await?; (org_id, merchant_id, profile_id) } _ => { diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index 565aa98f48d..4ae02668957 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -1672,24 +1672,6 @@ impl ForeignFrom } } -impl ForeignFrom - for diesel_models::organization::OrganizationNew -{ - fn foreign_from(item: api_models::user::UserOrgCreateRequest) -> Self { - let org_new = api_models::organization::OrganizationNew::new(None); - let api_models::user::UserOrgCreateRequest { - organization_name, - organization_details, - metadata, - .. - } = item; - let mut org_new_db = Self::new(org_new.org_id, Some(organization_name)); - org_new_db.organization_details = organization_details; - org_new_db.metadata = metadata; - org_new_db - } -} - impl ForeignFrom for storage::GatewayStatusMappingNew { fn foreign_from(value: gsm_api_types::GsmCreateRequest) -> Self { Self { diff --git a/crates/router/src/utils/user.rs b/crates/router/src/utils/user.rs index acbcac40a0f..b50647ff1ac 100644 --- a/crates/router/src/utils/user.rs +++ b/crates/router/src/utils/user.rs @@ -283,7 +283,7 @@ pub fn is_sso_auth_type(auth_type: &UserAuthType) -> bool { #[cfg(feature = "v1")] pub fn create_merchant_account_request_for_org( - req: user_api::UserOrgCreateRequest, + req: user_api::UserOrgMerchantCreateRequest, org: organization::Organization, ) -> UserResult { let merchant_id = if matches!(env::which(), env::Env::Production) { diff --git a/crates/router/src/utils/user_role.rs b/crates/router/src/utils/user_role.rs index 38956d963ce..5e68a979460 100644 --- a/crates/router/src/utils/user_role.rs +++ b/crates/router/src/utils/user_role.rs @@ -189,15 +189,14 @@ pub async fn get_single_org_id( match user_role.entity_type { Some(EntityType::Tenant) => Ok(state .store - .list_all_merchant_accounts(&state.into(), Some(1), None) + .list_merchant_and_org_ids(&state.into(), 1, 0) .await .change_context(UserErrors::InternalServerError) .attach_printable("Failed to get merchants list for org")? - .first() + .pop() .ok_or(UserErrors::InternalServerError) - .attach_printable("No merchants to get the org id")? - .get_org_id() - .clone()), + .attach_printable("No merchants to get merchant or org id")? + .1), Some(EntityType::Organization) | Some(EntityType::Merchant) | Some(EntityType::Profile) @@ -212,12 +211,15 @@ pub async fn get_single_org_id( pub async fn get_single_merchant_id( state: &SessionState, user_role: &UserRole, + org_id: &id_type::OrganizationId, ) -> UserResult { - let org_id = get_single_org_id(state, user_role).await?; - match user_role.entity_type { - Some(EntityType::Tenant) | Some(EntityType::Organization) => Ok(state + let (_, entity_type) = user_role + .get_entity_id_and_type() + .ok_or(UserErrors::InternalServerError)?; + match entity_type { + EntityType::Tenant | EntityType::Organization => Ok(state .store - .list_merchant_accounts_by_organization_id(&state.into(), &org_id) + .list_merchant_accounts_by_organization_id(&state.into(), org_id) .await .to_not_found_response(UserErrors::InvalidRoleOperationWithMessage( "Invalid Org Id".to_string(), @@ -227,7 +229,7 @@ pub async fn get_single_merchant_id( .attach_printable("No merchants found for org_id")? .get_id() .clone()), - Some(EntityType::Merchant) | Some(EntityType::Profile) | None => user_role + EntityType::Merchant | EntityType::Profile => user_role .merchant_id .clone() .ok_or(UserErrors::InternalServerError) @@ -235,6 +237,44 @@ pub async fn get_single_merchant_id( } } +pub async fn get_single_profile_id( + state: &SessionState, + user_role: &UserRole, + merchant_id: &id_type::MerchantId, +) -> UserResult { + let (_, entity_type) = user_role + .get_entity_id_and_type() + .ok_or(UserErrors::InternalServerError)?; + match entity_type { + EntityType::Tenant | EntityType::Organization | EntityType::Merchant => { + 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)?; + + Ok(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()) + } + EntityType::Profile => user_role + .profile_id + .clone() + .ok_or(UserErrors::InternalServerError) + .attach_printable("profile_id not found"), + } +} + pub async fn get_lineage_for_user_id_and_entity_for_accepting_invite( state: &SessionState, user_id: &str, @@ -402,37 +442,9 @@ pub async fn get_single_merchant_id_and_profile_id( state: &SessionState, user_role: &UserRole, ) -> UserResult<(id_type::MerchantId, id_type::ProfileId)> { - let merchant_id = get_single_merchant_id(state, user_role).await?; - let (_, entity_type) = user_role - .get_entity_id_and_type() - .ok_or(UserErrors::InternalServerError)?; - let profile_id = match entity_type { - EntityType::Tenant | EntityType::Organization | EntityType::Merchant => { - 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)?; - - 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() - } - EntityType::Profile => user_role - .profile_id - .clone() - .ok_or(UserErrors::InternalServerError)?, - }; + let org_id = get_single_org_id(state, user_role).await?; + let merchant_id = get_single_merchant_id(state, user_role, &org_id).await?; + let profile_id = get_single_profile_id(state, user_role, &merchant_id).await?; Ok((merchant_id, profile_id)) } diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs index 80085789141..4c30714fb98 100644 --- a/crates/router_env/src/logger/types.rs +++ b/crates/router_env/src/logger/types.rs @@ -399,7 +399,7 @@ pub enum Flow { /// Create merchant account for user in a org UserMerchantAccountCreate, /// Create Org in a given tenancy - UserOrgCreate, + UserOrgMerchantCreate, /// Generate Sample Data GenerateSampleData, /// Delete Sample Data From 834dfce1af66d9a3a9ad47346b4f58885b6067c9 Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Tue, 3 Dec 2024 11:29:07 +0000 Subject: [PATCH 08/10] chore: run formatter --- crates/router/src/core/user.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs index b006264b954..1fb0c678836 100644 --- a/crates/router/src/core/user.rs +++ b/crates/router/src/core/user.rs @@ -2583,7 +2583,11 @@ pub async fn list_orgs_for_user( let key_manager_state = &(&state).into(); state .store - .list_merchant_and_org_ids(key_manager_state, consts::user::ORG_LIST_LIMIT_FOR_TENANT, 0) + .list_merchant_and_org_ids( + key_manager_state, + consts::user::ORG_LIST_LIMIT_FOR_TENANT, + 0, + ) .await .change_context(UserErrors::InternalServerError)? .into_iter() From e83452cf9b0ead8b5cab61289ca8079fbe117bf0 Mon Sep 17 00:00:00 2001 From: Apoorv Dixit Date: Wed, 4 Dec 2024 12:23:59 +0530 Subject: [PATCH 09/10] fix: resolve review comments iter 2 --- crates/api_models/src/events/user.rs | 22 ++++----- crates/api_models/src/user.rs | 11 ++++- .../src/query/merchant_account.rs | 4 +- crates/router/src/core/user.rs | 40 ++++++---------- crates/router/src/core/user_role.rs | 12 ++--- crates/router/src/db/kafka_store.rs | 2 +- crates/router/src/db/merchant_account.rs | 8 ++-- crates/router/src/routes/app.rs | 2 +- crates/router/src/routes/user.rs | 2 +- crates/router/src/types/domain/user.rs | 47 +++++++++++++++---- .../src/types/domain/user/decision_manager.rs | 30 +++--------- crates/router/src/utils/user.rs | 6 ++- crates/router/src/utils/user_role.rs | 18 +++---- 13 files changed, 105 insertions(+), 99 deletions(-) diff --git a/crates/api_models/src/events/user.rs b/crates/api_models/src/events/user.rs index 0b4ae48b482..bcc8339a37a 100644 --- a/crates/api_models/src/events/user.rs +++ b/crates/api_models/src/events/user.rs @@ -11,16 +11,16 @@ use crate::user::{ GetMetaDataRequest, GetMetaDataResponse, GetMultipleMetaDataPayload, SetMetaDataRequest, }, AcceptInviteFromEmailRequest, AuthSelectRequest, AuthorizeResponse, BeginTotpResponse, - ChangePasswordRequest, ConnectAccountRequest, CreateInternalUserRequest, CreateTenantRequest, - CreateUserAuthenticationMethodRequest, ForgotPasswordRequest, GetSsoAuthUrlRequest, - GetUserAuthenticationMethodsRequest, GetUserDetailsResponse, GetUserRoleDetailsRequest, - GetUserRoleDetailsResponseV2, InviteUserRequest, ReInviteUserRequest, RecoveryCodes, - ResetPasswordRequest, RotatePasswordRequest, SendVerifyEmailRequest, SignUpRequest, - SignUpWithMerchantIdRequest, SsoSignInRequest, SwitchMerchantRequest, - SwitchOrganizationRequest, SwitchProfileRequest, TokenResponse, TwoFactorAuthStatusResponse, - TwoFactorStatus, UpdateUserAccountDetailsRequest, UpdateUserAuthenticationMethodRequest, - UserFromEmailRequest, UserMerchantCreate, UserOrgMerchantCreateRequest, VerifyEmailRequest, - VerifyRecoveryCodeRequest, VerifyTotpRequest, + ChangePasswordRequest, ConnectAccountRequest, CreateInternalUserRequest, + CreateTenantUserRequest, CreateUserAuthenticationMethodRequest, ForgotPasswordRequest, + GetSsoAuthUrlRequest, GetUserAuthenticationMethodsRequest, GetUserDetailsResponse, + GetUserRoleDetailsRequest, GetUserRoleDetailsResponseV2, InviteUserRequest, + ReInviteUserRequest, RecoveryCodes, ResetPasswordRequest, RotatePasswordRequest, + SendVerifyEmailRequest, SignUpRequest, SignUpWithMerchantIdRequest, SsoSignInRequest, + SwitchMerchantRequest, SwitchOrganizationRequest, SwitchProfileRequest, TokenResponse, + TwoFactorAuthStatusResponse, TwoFactorStatus, UpdateUserAccountDetailsRequest, + UpdateUserAuthenticationMethodRequest, UserFromEmailRequest, UserMerchantCreate, + UserOrgMerchantCreateRequest, VerifyEmailRequest, VerifyRecoveryCodeRequest, VerifyTotpRequest, }; #[cfg(feature = "recon")] @@ -46,7 +46,7 @@ common_utils::impl_api_event_type!( SwitchMerchantRequest, SwitchProfileRequest, CreateInternalUserRequest, - CreateTenantRequest, + CreateTenantUserRequest, UserOrgMerchantCreateRequest, UserMerchantCreate, AuthorizeResponse, diff --git a/crates/api_models/src/user.rs b/crates/api_models/src/user.rs index 0faccda72b4..6de271f32e1 100644 --- a/crates/api_models/src/user.rs +++ b/crates/api_models/src/user.rs @@ -114,17 +114,24 @@ pub struct CreateInternalUserRequest { } #[derive(serde::Deserialize, Debug, serde::Serialize)] -pub struct CreateTenantRequest { +pub struct CreateTenantUserRequest { pub name: Secret, pub email: pii::Email, pub password: Secret, } + #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] pub struct UserOrgMerchantCreateRequest { pub organization_name: String, pub organization_details: Option, pub metadata: Option, - pub merchant_name: String, + pub merchant_name: Secret, +} + +#[derive(Debug, Clone)] +pub struct MerchantAccountIdentifier { + pub merchant_id: id_type::MerchantId, + pub org_id: id_type::OrganizationId, } #[derive(Debug, serde::Deserialize, serde::Serialize)] diff --git a/crates/diesel_models/src/query/merchant_account.rs b/crates/diesel_models/src/query/merchant_account.rs index 202c47049ff..03945646014 100644 --- a/crates/diesel_models/src/query/merchant_account.rs +++ b/crates/diesel_models/src/query/merchant_account.rs @@ -124,7 +124,7 @@ impl MerchantAccount { pub async fn list_all_merchant_accounts( conn: &PgPooledConn, limit: u32, - offset: u32, + offset: Option, ) -> StorageResult> { generics::generic_filter::< ::Table, @@ -135,7 +135,7 @@ impl MerchantAccount { conn, dsl_identifier.ne_all(vec![""]), Some(i64::from(limit)), - Some(i64::from(offset)), + offset.map(i64::from), None, ) .await diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs index 1fb0c678836..639e1c33b99 100644 --- a/crates/router/src/core/user.rs +++ b/crates/router/src/core/user.rs @@ -25,6 +25,8 @@ 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; @@ -1268,13 +1270,13 @@ pub async fn create_internal_user( pub async fn create_tenant_user( state: SessionState, - request: user_api::CreateTenantRequest, + request: user_api::CreateTenantUserRequest, ) -> UserResponse<()> { let key_manager_state = &(&state).into(); - let (merchant_id, _) = state + let (merchant_id, org_id) = state .store - .list_merchant_and_org_ids(key_manager_state, 1, 0) + .list_merchant_and_org_ids(key_manager_state, 1, None) .await .change_context(UserErrors::InternalServerError) .attach_printable("Failed to get merchants list for org")? @@ -1282,25 +1284,13 @@ pub async fn create_tenant_user( .ok_or(UserErrors::InternalServerError) .attach_printable("No merchants found in the tenancy")?; - let key_store = state - .store - .get_merchant_key_store_by_merchant_id( - key_manager_state, - &merchant_id, - &state.store.get_master_key().to_vec().into(), - ) - .await - .change_context(UserErrors::InternalServerError) - .attach_printable("Error while fetching the key store by merchant_id")?; - - let merchant_account = state - .store - .find_merchant_account_by_merchant_id(key_manager_state, &merchant_id, &key_store) - .await - .change_context(UserErrors::InternalServerError) - .attach_printable("Error while fetching the merchant_account by merchant_id")?; - - let new_user = domain::NewUser::try_from((request, merchant_account))?; + let new_user = domain::NewUser::try_from(( + request, + user_api::MerchantAccountIdentifier { + merchant_id, + org_id, + }, + ))?; let mut store_user: storage_user::UserNew = new_user.clone().try_into()?; store_user.set_is_verified(true); @@ -1347,7 +1337,7 @@ pub async fn create_org_merchant_for_user( let merchant_account_create_request = utils::user::create_merchant_account_request_for_org(req, org)?; - super::admin::create_merchant_account(state.clone(), merchant_account_create_request) + admin::create_merchant_account(state.clone(), merchant_account_create_request) .await .change_context(UserErrors::InternalServerError) .attach_printable("Error while creating a merchant")?; @@ -2586,12 +2576,12 @@ pub async fn list_orgs_for_user( .list_merchant_and_org_ids( key_manager_state, consts::user::ORG_LIST_LIMIT_FOR_TENANT, - 0, + None, ) .await .change_context(UserErrors::InternalServerError)? .into_iter() - .map(|(_, org_id)| org_id) // Extract the org_id from the tuple + .map(|(_, org_id)| org_id) .collect::>() } EntityType::Organization | EntityType::Merchant | EntityType::Profile => state diff --git a/crates/router/src/core/user_role.rs b/crates/router/src/core/user_role.rs index 3ad32b09502..31ec665b2ab 100644 --- a/crates/router/src/core/user_role.rs +++ b/crates/router/src/core/user_role.rs @@ -872,10 +872,8 @@ pub async fn list_invitations_for_user( match entity_type { EntityType::Tenant => { - return Err(UserErrors::InvalidRoleOperationWithMessage( - "Tenant roles are not allowed for this operation".to_string(), - ) - .into()); + return Err(report!(UserErrors::InternalServerError)) + .attach_printable("Tenant roles are not allowed for this operation"); } EntityType::Organization => org_ids.push( user_role @@ -982,10 +980,8 @@ pub async fn list_invitations_for_user( let entity_name = match entity_type { EntityType::Tenant => { - return Err(UserErrors::InvalidRoleOperationWithMessage( - "Tenant roles are not allowed for this operation".to_string(), - ) - .into()); + return Err(report!(UserErrors::InternalServerError)) + .attach_printable("Tenant roles are not allowed for this operation"); } EntityType::Organization => user_role .org_id diff --git a/crates/router/src/db/kafka_store.rs b/crates/router/src/db/kafka_store.rs index 3b6c75a994a..cfcf630efb9 100644 --- a/crates/router/src/db/kafka_store.rs +++ b/crates/router/src/db/kafka_store.rs @@ -1054,7 +1054,7 @@ impl MerchantAccountInterface for KafkaStore { &self, state: &KeyManagerState, limit: u32, - offset: u32, + offset: Option, ) -> CustomResult, errors::StorageError> { self.diesel_store diff --git a/crates/router/src/db/merchant_account.rs b/crates/router/src/db/merchant_account.rs index d6f7369f48e..4f4a3f1cf00 100644 --- a/crates/router/src/db/merchant_account.rs +++ b/crates/router/src/db/merchant_account.rs @@ -93,7 +93,7 @@ where &self, state: &KeyManagerState, limit: u32, - offset: u32, + offset: Option, ) -> CustomResult< Vec<( common_utils::id_type::MerchantId, @@ -431,7 +431,7 @@ impl MerchantAccountInterface for Store { &self, _state: &KeyManagerState, limit: u32, - offset: u32, + offset: Option, ) -> CustomResult< Vec<( common_utils::id_type::MerchantId, @@ -745,7 +745,7 @@ impl MerchantAccountInterface for MockDb { &self, _state: &KeyManagerState, limit: u32, - offset: u32, + offset: Option, ) -> CustomResult< Vec<( common_utils::id_type::MerchantId, @@ -754,8 +754,8 @@ impl MerchantAccountInterface for MockDb { errors::StorageError, > { let accounts = self.merchant_accounts.lock().await; - let offset = offset.try_into().unwrap_or(0); let limit = limit.try_into().unwrap_or(accounts.len()); + let offset = offset.unwrap_or(0).try_into().unwrap_or(0); let merchant_and_org_ids = accounts .iter() diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 8cf19bb2b4b..c84cc4ad654 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -1870,7 +1870,7 @@ impl User { web::resource("/internal_signup").route(web::post().to(user::internal_user_signup)), ) .service( - web::resource("/create_tenant").route(web::post().to(user::create_tenant_user)), + web::resource("/tenant_signup").route(web::post().to(user::create_tenant_user)), ) .service(web::resource("/create_org").route(web::post().to(user::user_org_create))) .service( diff --git a/crates/router/src/routes/user.rs b/crates/router/src/routes/user.rs index f81d9c86e32..f356727bb7d 100644 --- a/crates/router/src/routes/user.rs +++ b/crates/router/src/routes/user.rs @@ -230,7 +230,7 @@ pub async fn internal_user_signup( pub async fn create_tenant_user( state: web::Data, http_req: HttpRequest, - json_payload: web::Json, + json_payload: web::Json, ) -> HttpResponse { let flow = Flow::TenantUserCreate; Box::pin(api::server_wrap( diff --git a/crates/router/src/types/domain/user.rs b/crates/router/src/types/domain/user.rs index 6016e4fe866..0a3e3aed2c0 100644 --- a/crates/router/src/types/domain/user.rs +++ b/crates/router/src/types/domain/user.rs @@ -19,7 +19,6 @@ use diesel_models::{ user_role::{UserRole, UserRoleNew}, }; use error_stack::{report, ResultExt}; -use hyperswitch_domain_models::merchant_account::MerchantAccount; use masking::{ExposeInterface, PeekInterface, Secret}; use once_cell::sync::Lazy; use rand::distributions::{Alphanumeric, DistString}; @@ -313,10 +312,20 @@ impl From for NewUserOrganization { } } -impl From<(user_api::CreateTenantRequest, MerchantAccount)> for NewUserOrganization { - fn from((_value, merchant_account): (user_api::CreateTenantRequest, MerchantAccount)) -> Self { +impl + From<( + user_api::CreateTenantUserRequest, + user_api::MerchantAccountIdentifier, + )> for NewUserOrganization +{ + fn from( + (_value, merchant_account_identifier): ( + user_api::CreateTenantUserRequest, + user_api::MerchantAccountIdentifier, + ), + ) -> Self { let new_organization = api_org::OrganizationNew { - org_id: merchant_account.get_org_id().clone(), + org_id: merchant_account_identifier.org_id, org_name: None, }; let db_organization = ForeignFrom::foreign_from(new_organization); @@ -565,10 +574,20 @@ impl TryFrom for NewUserMerchant { } } -impl TryFrom<(user_api::CreateTenantRequest, MerchantAccount)> for NewUserMerchant { +impl + TryFrom<( + user_api::CreateTenantUserRequest, + user_api::MerchantAccountIdentifier, + )> for NewUserMerchant +{ type Error = error_stack::Report; - fn try_from(value: (user_api::CreateTenantRequest, MerchantAccount)) -> UserResult { - let merchant_id = value.1.get_id().clone(); + fn try_from( + value: ( + user_api::CreateTenantUserRequest, + user_api::MerchantAccountIdentifier, + ), + ) -> UserResult { + let merchant_id = value.1.merchant_id.clone(); let new_organization = NewUserOrganization::from(value); Ok(Self { company_name: None, @@ -899,11 +918,19 @@ impl TryFrom for NewUser { } } -impl TryFrom<(user_api::CreateTenantRequest, MerchantAccount)> for NewUser { +impl + TryFrom<( + user_api::CreateTenantUserRequest, + user_api::MerchantAccountIdentifier, + )> for NewUser +{ type Error = error_stack::Report; fn try_from( - (value, merchant_account): (user_api::CreateTenantRequest, MerchantAccount), + (value, merchant_account_identifier): ( + user_api::CreateTenantUserRequest, + user_api::MerchantAccountIdentifier, + ), ) -> UserResult { let user_id = uuid::Uuid::new_v4().to_string(); let email = value.email.clone().try_into()?; @@ -912,7 +939,7 @@ impl TryFrom<(user_api::CreateTenantRequest, MerchantAccount)> for NewUser { password: UserPassword::new(value.password.clone())?, is_temporary: false, }; - let new_merchant = NewUserMerchant::try_from((value, merchant_account))?; + let new_merchant = NewUserMerchant::try_from((value, merchant_account_identifier))?; Ok(Self { user_id, diff --git a/crates/router/src/types/domain/user/decision_manager.rs b/crates/router/src/types/domain/user/decision_manager.rs index b7f1d8e7017..519edf4e9ce 100644 --- a/crates/router/src/types/domain/user/decision_manager.rs +++ b/crates/router/src/types/domain/user/decision_manager.rs @@ -1,7 +1,7 @@ use common_enums::TokenPurpose; use common_utils::id_type; use diesel_models::{enums::UserStatus, user_role::UserRole}; -use error_stack::{report, ResultExt}; +use error_stack::ResultExt; use masking::Secret; use super::UserFromStorage; @@ -124,28 +124,12 @@ impl JWTFlow { next_flow: &NextFlow, user_role: &UserRole, ) -> UserResult> { - let (org_id, merchant_id, profile_id) = match user_role.entity_type { - Some(common_enums::EntityType::Tenant) => { - let org_id = utils::user_role::get_single_org_id(state, user_role).await?; - let merchant_id = - utils::user_role::get_single_merchant_id(state, user_role, &org_id).await?; - let profile_id = - utils::user_role::get_single_profile_id(state, user_role, &merchant_id).await?; - (org_id, merchant_id, profile_id) - } - _ => { - let org_id = user_role - .org_id - .clone() - .ok_or(report!(UserErrors::InternalServerError)) - .attach_printable("org_id not found")?; - - let (merchant_id, profile_id) = - utils::user_role::get_single_merchant_id_and_profile_id(state, user_role) - .await?; - (org_id, merchant_id, profile_id) - } - }; + let org_id = utils::user_role::get_single_org_id(state, user_role).await?; + let merchant_id = + utils::user_role::get_single_merchant_id(state, user_role, &org_id).await?; + let profile_id = + utils::user_role::get_single_profile_id(state, user_role, &merchant_id).await?; + auth::AuthToken::new_token( next_flow.user.get_user_id().to_string(), merchant_id, diff --git a/crates/router/src/utils/user.rs b/crates/router/src/utils/user.rs index b50647ff1ac..46a76eac059 100644 --- a/crates/router/src/utils/user.rs +++ b/crates/router/src/utils/user.rs @@ -7,6 +7,8 @@ use common_utils::{ }; use diesel_models::{organization, organization::OrganizationBridge}; use error_stack::ResultExt; +#[cfg(feature = "v1")] +use masking::PeekInterface; use masking::{ExposeInterface, Secret}; use redis_interface::RedisConnectionPool; use router_env::env; @@ -287,12 +289,12 @@ pub fn create_merchant_account_request_for_org( org: organization::Organization, ) -> UserResult { let merchant_id = if matches!(env::which(), env::Env::Production) { - id_type::MerchantId::try_from(domain::MerchantId::new(req.merchant_name.clone())?)? + id_type::MerchantId::try_from(domain::MerchantId::new(req.merchant_name.peek().clone())?)? } else { id_type::MerchantId::new_from_unix_timestamp() }; - let company_name = domain::UserCompanyName::new(req.merchant_name.clone())?; + let company_name = domain::UserCompanyName::new(req.merchant_name.peek().clone())?; Ok(api_models::admin::MerchantAccountCreate { merchant_id, metadata: None, diff --git a/crates/router/src/utils/user_role.rs b/crates/router/src/utils/user_role.rs index 5e68a979460..428e8dc8f1d 100644 --- a/crates/router/src/utils/user_role.rs +++ b/crates/router/src/utils/user_role.rs @@ -186,10 +186,13 @@ pub async fn get_single_org_id( state: &SessionState, user_role: &UserRole, ) -> UserResult { - match user_role.entity_type { - Some(EntityType::Tenant) => Ok(state + let (_, entity_type) = user_role + .get_entity_id_and_type() + .ok_or(UserErrors::InternalServerError)?; + match entity_type { + EntityType::Tenant => Ok(state .store - .list_merchant_and_org_ids(&state.into(), 1, 0) + .list_merchant_and_org_ids(&state.into(), 1, None) .await .change_context(UserErrors::InternalServerError) .attach_printable("Failed to get merchants list for org")? @@ -197,10 +200,7 @@ pub async fn get_single_org_id( .ok_or(UserErrors::InternalServerError) .attach_printable("No merchants to get merchant or org id")? .1), - Some(EntityType::Organization) - | Some(EntityType::Merchant) - | Some(EntityType::Profile) - | None => user_role + EntityType::Organization | EntityType::Merchant | EntityType::Profile => user_role .org_id .clone() .ok_or(UserErrors::InternalServerError) @@ -251,7 +251,7 @@ pub async fn get_single_profile_id( .store .get_merchant_key_store_by_merchant_id( &state.into(), - &merchant_id, + merchant_id, &state.store.get_master_key().to_vec().into(), ) .await @@ -259,7 +259,7 @@ pub async fn get_single_profile_id( Ok(state .store - .list_profile_by_merchant_id(&state.into(), &key_store, &merchant_id) + .list_profile_by_merchant_id(&state.into(), &key_store, merchant_id) .await .change_context(UserErrors::InternalServerError)? .pop() From ad79f35a3647ffe2dde02c809cc768d3c60c1d54 Mon Sep 17 00:00:00 2001 From: Apoorv Dixit Date: Wed, 4 Dec 2024 14:15:05 +0530 Subject: [PATCH 10/10] fix: cover nitpicks --- crates/api_models/src/user.rs | 6 ----- crates/router/src/core/user.rs | 2 +- crates/router/src/types/domain/user.rs | 36 +++++++++----------------- crates/router/src/utils/user.rs | 6 ++--- 4 files changed, 15 insertions(+), 35 deletions(-) diff --git a/crates/api_models/src/user.rs b/crates/api_models/src/user.rs index 6de271f32e1..a3eec29d261 100644 --- a/crates/api_models/src/user.rs +++ b/crates/api_models/src/user.rs @@ -128,12 +128,6 @@ pub struct UserOrgMerchantCreateRequest { pub merchant_name: Secret, } -#[derive(Debug, Clone)] -pub struct MerchantAccountIdentifier { - pub merchant_id: id_type::MerchantId, - pub org_id: id_type::OrganizationId, -} - #[derive(Debug, serde::Deserialize, serde::Serialize)] pub struct UserMerchantCreate { pub company_name: String, diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs index 639e1c33b99..878557e6ef1 100644 --- a/crates/router/src/core/user.rs +++ b/crates/router/src/core/user.rs @@ -1286,7 +1286,7 @@ pub async fn create_tenant_user( let new_user = domain::NewUser::try_from(( request, - user_api::MerchantAccountIdentifier { + domain::MerchantAccountIdentifier { merchant_id, org_id, }, diff --git a/crates/router/src/types/domain/user.rs b/crates/router/src/types/domain/user.rs index 0a3e3aed2c0..c5f86de023e 100644 --- a/crates/router/src/types/domain/user.rs +++ b/crates/router/src/types/domain/user.rs @@ -312,16 +312,11 @@ impl From for NewUserOrganization { } } -impl - From<( - user_api::CreateTenantUserRequest, - user_api::MerchantAccountIdentifier, - )> for NewUserOrganization -{ +impl From<(user_api::CreateTenantUserRequest, MerchantAccountIdentifier)> for NewUserOrganization { fn from( (_value, merchant_account_identifier): ( user_api::CreateTenantUserRequest, - user_api::MerchantAccountIdentifier, + MerchantAccountIdentifier, ), ) -> Self { let new_organization = api_org::OrganizationNew { @@ -574,18 +569,10 @@ impl TryFrom for NewUserMerchant { } } -impl - TryFrom<( - user_api::CreateTenantUserRequest, - user_api::MerchantAccountIdentifier, - )> for NewUserMerchant -{ +impl TryFrom<(user_api::CreateTenantUserRequest, MerchantAccountIdentifier)> for NewUserMerchant { type Error = error_stack::Report; fn try_from( - value: ( - user_api::CreateTenantUserRequest, - user_api::MerchantAccountIdentifier, - ), + value: (user_api::CreateTenantUserRequest, MerchantAccountIdentifier), ) -> UserResult { let merchant_id = value.1.merchant_id.clone(); let new_organization = NewUserOrganization::from(value); @@ -617,6 +604,12 @@ impl TryFrom for NewUserMerchant { } } +#[derive(Debug, Clone)] +pub struct MerchantAccountIdentifier { + pub merchant_id: id_type::MerchantId, + pub org_id: id_type::OrganizationId, +} + #[derive(Clone)] pub struct NewUser { user_id: String, @@ -918,18 +911,13 @@ impl TryFrom for NewUser { } } -impl - TryFrom<( - user_api::CreateTenantUserRequest, - user_api::MerchantAccountIdentifier, - )> for NewUser -{ +impl TryFrom<(user_api::CreateTenantUserRequest, MerchantAccountIdentifier)> for NewUser { type Error = error_stack::Report; fn try_from( (value, merchant_account_identifier): ( user_api::CreateTenantUserRequest, - user_api::MerchantAccountIdentifier, + MerchantAccountIdentifier, ), ) -> UserResult { let user_id = uuid::Uuid::new_v4().to_string(); diff --git a/crates/router/src/utils/user.rs b/crates/router/src/utils/user.rs index 46a76eac059..23a3a08f780 100644 --- a/crates/router/src/utils/user.rs +++ b/crates/router/src/utils/user.rs @@ -7,8 +7,6 @@ use common_utils::{ }; use diesel_models::{organization, organization::OrganizationBridge}; use error_stack::ResultExt; -#[cfg(feature = "v1")] -use masking::PeekInterface; use masking::{ExposeInterface, Secret}; use redis_interface::RedisConnectionPool; use router_env::env; @@ -289,12 +287,12 @@ pub fn create_merchant_account_request_for_org( org: organization::Organization, ) -> UserResult { let merchant_id = if matches!(env::which(), env::Env::Production) { - id_type::MerchantId::try_from(domain::MerchantId::new(req.merchant_name.peek().clone())?)? + id_type::MerchantId::try_from(domain::MerchantId::new(req.merchant_name.clone().expose())?)? } else { id_type::MerchantId::new_from_unix_timestamp() }; - let company_name = domain::UserCompanyName::new(req.merchant_name.peek().clone())?; + let company_name = domain::UserCompanyName::new(req.merchant_name.expose())?; Ok(api_models::admin::MerchantAccountCreate { merchant_id, metadata: None,