Bug: feat(users_roles): support switch for new user hierarchy



Support switch
- At org level
- At merchant level
- At profile level 
- For internal users

Issue new token and set permissions accordingly
diff --git a/crates/api_models/src/events/user.rs b/crates/api_models/src/events/user.rs
index 862c361d465..590179c052c 100644
--- a/crates/api_models/src/events/user.rs
+++ b/crates/api_models/src/events/user.rs
@@ -17,10 +17,10 @@ use crate::user::{
     GetUserRoleDetailsRequest, GetUserRoleDetailsResponse, InviteUserRequest, ListUsersResponse,
     ReInviteUserRequest, RecoveryCodes, ResetPasswordRequest, RotatePasswordRequest,
     SendVerifyEmailRequest, SignInResponse, SignUpRequest, SignUpWithMerchantIdRequest,
-    SsoSignInRequest, SwitchMerchantIdRequest, TokenOrPayloadResponse, TokenResponse,
-    TwoFactorAuthStatusResponse, UpdateUserAccountDetailsRequest,
-    UpdateUserAuthenticationMethodRequest, UserFromEmailRequest, UserMerchantCreate,
-    VerifyEmailRequest, VerifyRecoveryCodeRequest, VerifyTotpRequest,
+    SsoSignInRequest, SwitchMerchantRequest, SwitchOrganizationRequest, SwitchProfileRequest,
+    TokenOrPayloadResponse, TokenResponse, TwoFactorAuthStatusResponse,
+    UpdateUserAccountDetailsRequest, UpdateUserAuthenticationMethodRequest, UserFromEmailRequest,
+    UserMerchantCreate, VerifyEmailRequest, VerifyRecoveryCodeRequest, VerifyTotpRequest,
 };
 
 impl ApiEventMetric for DashboardEntryResponse {
@@ -56,7 +56,9 @@ common_utils::impl_api_event_type!(
         GetMetaDataResponse,
         GetMetaDataRequest,
         SetMetaDataRequest,
-        SwitchMerchantIdRequest,
+        SwitchOrganizationRequest,
+        SwitchMerchantRequest,
+        SwitchProfileRequest,
         CreateInternalUserRequest,
         UserMerchantCreate,
         ListUsersResponse,
diff --git a/crates/api_models/src/events/user_role.rs b/crates/api_models/src/events/user_role.rs
index 7e8eaf17e30..5b5da0ec50a 100644
--- a/crates/api_models/src/events/user_role.rs
+++ b/crates/api_models/src/events/user_role.rs
@@ -7,7 +7,7 @@ use crate::user_role::{
         UpdateRoleRequest,
     },
     AcceptInvitationRequest, AuthorizationInfoResponse, DeleteUserRoleRequest,
-    MerchantSelectRequest, TransferOrgOwnershipRequest, UpdateUserRoleRequest,
+    MerchantSelectRequest, UpdateUserRoleRequest,
 };
 
 common_utils::impl_api_event_type!(
@@ -20,7 +20,6 @@ common_utils::impl_api_event_type!(
         MerchantSelectRequest,
         AcceptInvitationRequest,
         DeleteUserRoleRequest,
-        TransferOrgOwnershipRequest,
         CreateRoleRequest,
         UpdateRoleRequest,
         ListRolesResponse,
diff --git a/crates/api_models/src/user.rs b/crates/api_models/src/user.rs
index 1fdf47425e2..2547f082039 100644
--- a/crates/api_models/src/user.rs
+++ b/crates/api_models/src/user.rs
@@ -124,10 +124,20 @@ pub struct AcceptInviteFromEmailRequest {
 }
 
 #[derive(Debug, serde::Deserialize, serde::Serialize)]
-pub struct SwitchMerchantIdRequest {
+pub struct SwitchOrganizationRequest {
+    pub org_id: id_type::OrganizationId,
+}
+
+#[derive(Debug, serde::Deserialize, serde::Serialize)]
+pub struct SwitchMerchantRequest {
     pub merchant_id: id_type::MerchantId,
 }
 
+#[derive(Debug, serde::Deserialize, serde::Serialize)]
+pub struct SwitchProfileRequest {
+    pub profile_id: id_type::ProfileId,
+}
+
 #[derive(serde::Deserialize, Debug, serde::Serialize)]
 pub struct CreateInternalUserRequest {
     pub name: Secret<String>,
diff --git a/crates/api_models/src/user_role.rs b/crates/api_models/src/user_role.rs
index 5319c81385a..c9f222cb7be 100644
--- a/crates/api_models/src/user_role.rs
+++ b/crates/api_models/src/user_role.rs
@@ -133,8 +133,3 @@ pub struct AcceptInvitationRequest {
 pub struct DeleteUserRoleRequest {
     pub email: pii::Email,
 }
-
-#[derive(Debug, serde::Deserialize, serde::Serialize)]
-pub struct TransferOrgOwnershipRequest {
-    pub email: pii::Email,
-}
diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs
index c3d0dbae04e..9510ddd632d 100644
--- a/crates/router/src/core/user.rs
+++ b/crates/router/src/core/user.rs
@@ -1320,12 +1320,12 @@ pub async fn create_internal_user(
 
 pub async fn switch_merchant_id(
     state: SessionState,
-    request: user_api::SwitchMerchantIdRequest,
+    request: user_api::SwitchMerchantRequest,
     user_from_token: auth::UserFromToken,
 ) -> UserResponse<user_api::DashboardEntryResponse> {
     if user_from_token.merchant_id == request.merchant_id {
         return Err(UserErrors::InvalidRoleOperationWithMessage(
-            "User switching to same merchant id".to_string(),
+            "User switching to same merchant_id".to_string(),
         )
         .into());
     }
@@ -1375,9 +1375,9 @@ pub async fn switch_merchant_id(
             })?
             .organization_id;
 
-        let token = utils::user::generate_jwt_auth_token_with_custom_role_attributes(
+        let token = utils::user::generate_jwt_auth_token_with_attributes(
             &state,
-            &user,
+            user_from_token.user_id,
             request.merchant_id.clone(),
             org_id.clone(),
             user_from_token.role_id.clone(),
@@ -1557,7 +1557,7 @@ pub async fn list_users_for_merchant_account(
         .list_user_roles_by_merchant_id(&user_from_token.merchant_id, UserRoleVersion::V1)
         .await
         .change_context(UserErrors::InternalServerError)
-        .attach_printable("No user roles for given merchant id")?
+        .attach_printable("No user roles for given merchant_id")?
         .into_iter()
         .map(|role| (role.user_id.clone(), role))
         .collect();
@@ -1569,7 +1569,7 @@ pub async fn list_users_for_merchant_account(
         .find_users_by_user_ids(user_ids)
         .await
         .change_context(UserErrors::InternalServerError)
-        .attach_printable("No users for given merchant id")?;
+        .attach_printable("No users for given merchant_id")?;
 
     let users_and_user_roles: Vec<_> = users
         .into_iter()
@@ -2134,7 +2134,7 @@ pub async fn verify_recovery_code(
     state: SessionState,
     user_token: auth::UserIdFromAuth,
     req: user_api::VerifyRecoveryCodeRequest,
-) -> UserResponse<user_api::TokenResponse> {
+) -> UserResponse<()> {
     let user_from_db: domain::UserFromStorage = state
         .global_store
         .find_user_by_id(&user_token.user_id)
@@ -2760,3 +2760,442 @@ pub async fn list_profiles_for_user_in_org_and_merchant_account(
 
     Ok(ApplicationResponse::Json(profiles))
 }
+
+pub async fn switch_org_for_user(
+    state: SessionState,
+    request: user_api::SwitchOrganizationRequest,
+    user_from_token: auth::UserFromToken,
+) -> UserResponse<user_api::TokenResponse> {
+    if user_from_token.org_id == request.org_id {
+        return Err(UserErrors::InvalidRoleOperationWithMessage(
+            "User switching to same org".to_string(),
+        )
+        .into());
+    }
+
+    let key_manager_state = &(&state).into();
+    let role_info = roles::RoleInfo::from_role_id(
+        &state,
+        &user_from_token.role_id,
+        &user_from_token.merchant_id,
+        &user_from_token.org_id,
+    )
+    .await
+    .change_context(UserErrors::InternalServerError)
+    .attach_printable("Failed to retrieve role information")?;
+
+    if role_info.get_entity_type() == EntityType::Internal {
+        return Err(UserErrors::InvalidRoleOperationWithMessage(
+            "Org switching not allowed for Internal role".to_string(),
+        )
+        .into());
+    }
+
+    let user_role = state
+        .store
+        .list_user_roles(
+            &user_from_token.user_id,
+            Some(&request.org_id),
+            None,
+            None,
+            None,
+            None,
+        )
+        .await
+        .change_context(UserErrors::InternalServerError)
+        .attach_printable("Failed to list user roles by user_id and org_id")?
+        .into_iter()
+        .find(|role| role.status == UserStatus::Active)
+        .ok_or(UserErrors::InvalidRoleOperationWithMessage(
+            "No user role found for the requested org_id".to_string(),
+        ))?
+        .to_owned();
+
+    let merchant_id = if let Some(merchant_id) = &user_role.merchant_id {
+        merchant_id.clone()
+    } else {
+        state
+            .store
+            .list_merchant_accounts_by_organization_id(
+                key_manager_state,
+                request.org_id.get_string_repr(),
+            )
+            .await
+            .change_context(UserErrors::InternalServerError)
+            .attach_printable("Failed to list merchant accounts by organization_id")?
+            .first()
+            .ok_or(UserErrors::InternalServerError)
+            .attach_printable("No merchant account found for the given organization_id")?
+            .get_id()
+            .clone()
+    };
+
+    let profile_id = if let Some(profile_id) = &user_role.profile_id {
+        profile_id.clone()
+    } else {
+        let merchant_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("Failed to retrieve merchant key store by merchant_id")?;
+
+        state
+            .store
+            .list_business_profile_by_merchant_id(
+                key_manager_state,
+                &merchant_key_store,
+                &merchant_id,
+            )
+            .await
+            .change_context(UserErrors::InternalServerError)
+            .attach_printable("Failed to list business profiles by merchant_id")?
+            .first()
+            .ok_or(UserErrors::InternalServerError)
+            .attach_printable("No business profile found for the merchant_id")?
+            .profile_id
+            .clone()
+    };
+
+    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(),
+        Some(profile_id.clone()),
+    )
+    .await?;
+
+    utils::user_role::set_role_permissions_in_cache_by_role_id_merchant_id_org_id(
+        &state,
+        &user_role.role_id,
+        &merchant_id,
+        &request.org_id,
+    )
+    .await;
+
+    let response = user_api::TokenResponse {
+        token: token.clone(),
+        token_type: common_enums::TokenPurpose::UserInfo,
+    };
+
+    auth::cookies::set_cookie_response(response, token)
+}
+
+pub async fn switch_merchant_for_user_in_org(
+    state: SessionState,
+    request: user_api::SwitchMerchantRequest,
+    user_from_token: auth::UserFromToken,
+) -> UserResponse<user_api::TokenResponse> {
+    if user_from_token.merchant_id == request.merchant_id {
+        return Err(UserErrors::InvalidRoleOperationWithMessage(
+            "User switching to same merchant".to_string(),
+        )
+        .into());
+    }
+
+    let key_manager_state = &(&state).into();
+    let role_info = roles::RoleInfo::from_role_id(
+        &state,
+        &user_from_token.role_id,
+        &user_from_token.merchant_id,
+        &user_from_token.org_id,
+    )
+    .await
+    .change_context(UserErrors::InternalServerError)
+    .attach_printable("Failed to retrieve role information")?;
+
+    let (org_id, merchant_id, profile_id, role_id) = match role_info.get_entity_type() {
+        EntityType::Internal => {
+            let merchant_key_store = state
+                .store
+                .get_merchant_key_store_by_merchant_id(
+                    key_manager_state,
+                    &request.merchant_id,
+                    &state.store.get_master_key().to_vec().into(),
+                )
+                .await
+                .to_not_found_response(UserErrors::MerchantIdNotFound)?;
+
+            let merchant_account = state
+                .store
+                .find_merchant_account_by_merchant_id(
+                    key_manager_state,
+                    &request.merchant_id,
+                    &merchant_key_store,
+                )
+                .await
+                .to_not_found_response(UserErrors::MerchantIdNotFound)?;
+
+            let profile_id = state
+                .store
+                .list_business_profile_by_merchant_id(
+                    key_manager_state,
+                    &merchant_key_store,
+                    &request.merchant_id,
+                )
+                .await
+                .change_context(UserErrors::InternalServerError)
+                .attach_printable("Failed to list business profiles by merchant_id")?
+                .first()
+                .ok_or(UserErrors::InternalServerError)
+                .attach_printable("No business profile found for the given merchant_id")?
+                .profile_id
+                .clone();
+
+            (
+                merchant_account.organization_id,
+                request.merchant_id,
+                profile_id,
+                user_from_token.role_id.clone(),
+            )
+        }
+
+        EntityType::Organization => {
+            let merchant_key_store = state
+                .store
+                .get_merchant_key_store_by_merchant_id(
+                    key_manager_state,
+                    &request.merchant_id,
+                    &state.store.get_master_key().to_vec().into(),
+                )
+                .await
+                .to_not_found_response(UserErrors::MerchantIdNotFound)?;
+
+            let merchant_id = state
+                .store
+                .find_merchant_account_by_merchant_id(
+                    key_manager_state,
+                    &request.merchant_id,
+                    &merchant_key_store,
+                )
+                .await
+                .change_context(UserErrors::MerchantIdNotFound)?
+                .organization_id
+                .eq(&user_from_token.org_id)
+                .then(|| request.merchant_id.clone())
+                .ok_or_else(|| {
+                    UserErrors::InvalidRoleOperationWithMessage(
+                        "No such merchant_id found for the user in the org".to_string(),
+                    )
+                })?;
+
+            let profile_id = state
+                .store
+                .list_business_profile_by_merchant_id(
+                    key_manager_state,
+                    &merchant_key_store,
+                    &merchant_id,
+                )
+                .await
+                .change_context(UserErrors::InternalServerError)
+                .attach_printable("Failed to list business profiles by merchant_id")?
+                .first()
+                .ok_or(UserErrors::InternalServerError)
+                .attach_printable("No business profile found for the merchant_id")?
+                .profile_id
+                .clone();
+
+            (
+                user_from_token.org_id.clone(),
+                merchant_id,
+                profile_id,
+                user_from_token.role_id.clone(),
+            )
+        }
+
+        EntityType::Merchant | EntityType::Profile => {
+            let user_role = state
+                .store
+                .list_user_roles(
+                    &user_from_token.user_id,
+                    Some(&user_from_token.org_id),
+                    Some(&request.merchant_id),
+                    None,
+                    None,
+                    None,
+                )
+                .await
+                .change_context(UserErrors::InternalServerError)
+                .attach_printable(
+                    "Failed to list user roles for the given user_id, org_id and merchant_id",
+                )?
+                .into_iter()
+                .find(|role| role.status == UserStatus::Active)
+                .ok_or(UserErrors::InvalidRoleOperationWithMessage(
+                    "No user role associated with the requested merchant_id".to_string(),
+                ))?
+                .to_owned();
+
+            let profile_id = if let Some(profile_id) = &user_role.profile_id {
+                profile_id.clone()
+            } else {
+                let merchant_key_store = state
+                    .store
+                    .get_merchant_key_store_by_merchant_id(
+                        key_manager_state,
+                        &request.merchant_id,
+                        &state.store.get_master_key().to_vec().into(),
+                    )
+                    .await
+                    .change_context(UserErrors::InternalServerError)
+                    .attach_printable("Failed to retrieve merchant key store by merchant_id")?;
+
+                state
+                    .store
+                    .list_business_profile_by_merchant_id(
+                        key_manager_state,
+                        &merchant_key_store,
+                        &request.merchant_id,
+                    )
+                    .await
+                    .change_context(UserErrors::InternalServerError)
+                    .attach_printable("Failed to list business profiles for the given merchant_id")?
+                    .first()
+                    .ok_or(UserErrors::InternalServerError)
+                    .attach_printable("No business profile found for the given merchant_id")?
+                    .profile_id
+                    .clone()
+            };
+            (
+                user_from_token.org_id,
+                request.merchant_id,
+                profile_id,
+                user_role.role_id,
+            )
+        }
+    };
+
+    let token = utils::user::generate_jwt_auth_token_with_attributes(
+        &state,
+        user_from_token.user_id,
+        merchant_id.clone(),
+        org_id.clone(),
+        role_id.clone(),
+        Some(profile_id),
+    )
+    .await?;
+
+    utils::user_role::set_role_permissions_in_cache_by_role_id_merchant_id_org_id(
+        &state,
+        &role_id,
+        &merchant_id,
+        &org_id,
+    )
+    .await;
+
+    let response = user_api::TokenResponse {
+        token: token.clone(),
+        token_type: common_enums::TokenPurpose::UserInfo,
+    };
+
+    auth::cookies::set_cookie_response(response, token)
+}
+
+pub async fn switch_profile_for_user_in_org_and_merchant(
+    state: SessionState,
+    request: user_api::SwitchProfileRequest,
+    user_from_token: auth::UserFromToken,
+) -> UserResponse<user_api::TokenResponse> {
+    if user_from_token.profile_id == Some(request.profile_id.clone()) {
+        return Err(UserErrors::InvalidRoleOperationWithMessage(
+            "User switching to same profile".to_string(),
+        )
+        .into());
+    }
+
+    let key_manager_state = &(&state).into();
+    let role_info = roles::RoleInfo::from_role_id(
+        &state,
+        &user_from_token.role_id,
+        &user_from_token.merchant_id,
+        &user_from_token.org_id,
+    )
+    .await
+    .change_context(UserErrors::InternalServerError)
+    .attach_printable("Failed to retrieve role information")?;
+
+    let (profile_id, role_id) = match role_info.get_entity_type() {
+        EntityType::Internal | EntityType::Organization | EntityType::Merchant => {
+            let merchant_key_store = state
+                .store
+                .get_merchant_key_store_by_merchant_id(
+                    key_manager_state,
+                    &user_from_token.merchant_id,
+                    &state.store.get_master_key().to_vec().into(),
+                )
+                .await
+                .change_context(UserErrors::InternalServerError)
+                .attach_printable("Failed to retrieve merchant key store by merchant_id")?;
+
+            let profile_id = state
+                .store
+                .find_business_profile_by_merchant_id_profile_id(
+                    key_manager_state,
+                    &merchant_key_store,
+                    &user_from_token.merchant_id,
+                    &request.profile_id,
+                )
+                .await
+                .change_context(UserErrors::InvalidRoleOperationWithMessage(
+                    "No such profile found for the merchant".to_string(),
+                ))?
+                .profile_id;
+            (profile_id, user_from_token.role_id)
+        }
+
+        EntityType::Profile => {
+            let user_role = state
+                .store
+                .list_user_roles(
+                    &user_from_token.user_id,
+                    Some(&user_from_token.org_id),
+                    Some(&user_from_token.merchant_id),
+                    Some(&request.profile_id),
+                    None,
+                    None,
+                )
+                .await
+                .change_context(UserErrors::InternalServerError)
+                .attach_printable("Failed to list user roles for the given user_id, org_id, merchant_id and profile_id")?
+                .into_iter()
+                .find(|role| role.status == UserStatus::Active)
+                .ok_or(UserErrors::InvalidRoleOperationWithMessage(
+                    "No user role associated with the profile".to_string(),
+                ))?
+                .to_owned();
+
+            (request.profile_id, user_role.role_id)
+        }
+    };
+
+    let token = utils::user::generate_jwt_auth_token_with_attributes(
+        &state,
+        user_from_token.user_id,
+        user_from_token.merchant_id.clone(),
+        user_from_token.org_id.clone(),
+        role_id.clone(),
+        Some(profile_id),
+    )
+    .await?;
+
+    utils::user_role::set_role_permissions_in_cache_by_role_id_merchant_id_org_id(
+        &state,
+        &role_id,
+        &user_from_token.merchant_id,
+        &user_from_token.org_id,
+    )
+    .await;
+
+    let response = user_api::TokenResponse {
+        token: token.clone(),
+        token_type: common_enums::TokenPurpose::UserInfo,
+    };
+
+    auth::cookies::set_cookie_response(response, token)
+}
diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs
index 06971f381ad..97042485d6b 100644
--- a/crates/router/src/routes/app.rs
+++ b/crates/router/src/routes/app.rs
@@ -1707,6 +1707,19 @@ impl User {
                 ),
         );
 
+        route = route.service(
+            web::scope("/switch")
+                .service(web::resource("/org").route(web::post().to(switch_org_for_user)))
+                .service(
+                    web::resource("/merchant")
+                        .route(web::post().to(switch_merchant_for_user_in_org)),
+                )
+                .service(
+                    web::resource("/profile")
+                        .route(web::post().to(switch_profile_for_user_in_org_and_merchant)),
+                ),
+        );
+
         // Two factor auth routes
         route = route.service(
             web::scope("/2fa")
diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs
index e08b477d29a..f64fab1f408 100644
--- a/crates/router/src/routes/lock_utils.rs
+++ b/crates/router/src/routes/lock_utils.rs
@@ -212,7 +212,10 @@ impl From<Flow> for ApiIdentifier {
             | Flow::GetMultipleDashboardMetadata
             | Flow::VerifyPaymentConnector
             | Flow::InternalUserSignup
+            | Flow::SwitchOrg
             | Flow::SwitchMerchant
+            | Flow::SwitchMerchantV2
+            | Flow::SwitchProfile
             | Flow::UserMerchantAccountCreate
             | Flow::GenerateSampleData
             | Flow::DeleteSampleData
@@ -258,7 +261,6 @@ impl From<Flow> for ApiIdentifier {
             | Flow::AcceptInvitation
             | Flow::MerchantSelect
             | Flow::DeleteUserRole
-            | Flow::TransferOrgOwnership
             | Flow::CreateRole
             | Flow::UpdateRole
             | Flow::UserFromEmail => Self::UserRole,
diff --git a/crates/router/src/routes/user.rs b/crates/router/src/routes/user.rs
index 39ec3a24ff2..c52c5d1321c 100644
--- a/crates/router/src/routes/user.rs
+++ b/crates/router/src/routes/user.rs
@@ -240,7 +240,7 @@ pub async fn internal_user_signup(
 pub async fn switch_merchant_id(
     state: web::Data<AppState>,
     http_req: HttpRequest,
-    json_payload: web::Json<user_api::SwitchMerchantIdRequest>,
+    json_payload: web::Json<user_api::SwitchMerchantRequest>,
 ) -> HttpResponse {
     let flow = Flow::SwitchMerchant;
     Box::pin(api::server_wrap(
@@ -969,3 +969,59 @@ pub async fn list_profiles_for_user_in_org_and_merchant(
     ))
     .await
 }
+
+pub async fn switch_org_for_user(
+    state: web::Data<AppState>,
+    http_req: HttpRequest,
+    json_payload: web::Json<user_api::SwitchOrganizationRequest>,
+) -> HttpResponse {
+    let flow = Flow::SwitchOrg;
+    Box::pin(api::server_wrap(
+        flow,
+        state.clone(),
+        &http_req,
+        json_payload.into_inner(),
+        |state, user, req, _| user_core::switch_org_for_user(state, req, user),
+        &auth::DashboardNoPermissionAuth,
+        api_locking::LockAction::NotApplicable,
+    ))
+    .await
+}
+
+pub async fn switch_merchant_for_user_in_org(
+    state: web::Data<AppState>,
+    http_req: HttpRequest,
+    json_payload: web::Json<user_api::SwitchMerchantRequest>,
+) -> HttpResponse {
+    let flow = Flow::SwitchMerchantV2;
+    Box::pin(api::server_wrap(
+        flow,
+        state.clone(),
+        &http_req,
+        json_payload.into_inner(),
+        |state, user, req, _| user_core::switch_merchant_for_user_in_org(state, req, user),
+        &auth::DashboardNoPermissionAuth,
+        api_locking::LockAction::NotApplicable,
+    ))
+    .await
+}
+
+pub async fn switch_profile_for_user_in_org_and_merchant(
+    state: web::Data<AppState>,
+    http_req: HttpRequest,
+    json_payload: web::Json<user_api::SwitchProfileRequest>,
+) -> HttpResponse {
+    let flow = Flow::SwitchProfile;
+    Box::pin(api::server_wrap(
+        flow,
+        state.clone(),
+        &http_req,
+        json_payload.into_inner(),
+        |state, user, req, _| {
+            user_core::switch_profile_for_user_in_org_and_merchant(state, req, user)
+        },
+        &auth::DashboardNoPermissionAuth,
+        api_locking::LockAction::NotApplicable,
+    ))
+    .await
+}
diff --git a/crates/router/src/utils/user.rs b/crates/router/src/utils/user.rs
index 30a0e1ca788..b2170e7294a 100644
--- a/crates/router/src/utils/user.rs
+++ b/crates/router/src/utils/user.rs
@@ -108,16 +108,16 @@ pub async fn generate_jwt_auth_token_without_profile(
     Ok(Secret::new(token))
 }
 
-pub async fn generate_jwt_auth_token_with_custom_role_attributes(
+pub async fn generate_jwt_auth_token_with_attributes(
     state: &SessionState,
-    user: &UserFromStorage,
+    user_id: String,
     merchant_id: id_type::MerchantId,
     org_id: id_type::OrganizationId,
     role_id: String,
     profile_id: Option<id_type::ProfileId>,
 ) -> UserResult<Secret<String>> {
     let token = AuthToken::new_token(
-        user.get_user_id().to_string(),
+        user_id,
         merchant_id,
         role_id,
         &state.conf,
diff --git a/crates/router/src/utils/user_role.rs b/crates/router/src/utils/user_role.rs
index 792a79c91eb..1791b5308b4 100644
--- a/crates/router/src/utils/user_role.rs
+++ b/crates/router/src/utils/user_role.rs
@@ -128,6 +128,18 @@ pub async fn set_role_permissions_in_cache_by_user_role(
     .is_ok()
 }
 
+pub async fn set_role_permissions_in_cache_by_role_id_merchant_id_org_id(
+    state: &SessionState,
+    role_id: &str,
+    merchant_id: &id_type::MerchantId,
+    org_id: &id_type::OrganizationId,
+) -> bool {
+    set_role_permissions_in_cache_if_required(state, role_id, merchant_id, org_id)
+        .await
+        .map_err(|e| logger::error!("Error setting permissions in cache {:?}", e))
+        .is_ok()
+}
+
 pub async fn set_role_permissions_in_cache_if_required(
     state: &SessionState,
     role_id: &str,
diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs
index 4fbbfdf5518..88eec0f791a 100644
--- a/crates/router_env/src/logger/types.rs
+++ b/crates/router_env/src/logger/types.rs
@@ -352,8 +352,14 @@ pub enum Flow {
     VerifyPaymentConnector,
     /// Internal user signup
     InternalUserSignup,
+    /// Switch org
+    SwitchOrg,
     /// Switch merchant
     SwitchMerchant,
+    /// Switch merchant v2
+    SwitchMerchantV2,
+    /// Switch profile
+    SwitchProfile,
     /// Get permission info
     GetAuthorizationInfo,
     /// Get Roles info
@@ -366,8 +372,6 @@ pub enum Flow {
     GetRoleFromToken,
     /// Update user role
     UpdateUserRole,
-    /// Transfer organization ownership
-    TransferOrgOwnership,
     /// Create merchant account for user in a org
     UserMerchantAccountCreate,
     /// Generate Sample Data

## Type of Change
<!-- Put an `x` in the boxes that apply -->

- [ ] Bugfix
- [x] New feature
- [ ] Enhancement
- [ ] Refactoring
- [ ] Dependency updates
- [ ] Documentation
- [ ] CI/CD

## Description
Support switch for org, merchant, profile and internal users.
For now switch exist only for merchant.

After new switch, new token will be issued.


### Additional Changes

- [ ] This PR modifies the API contract
- [ ] This PR modifies the database schema
- [ ] This PR modifies application configuration/environment variables

<!--
Provide links to the files with corresponding changes.

Following are the paths where you can find config files:
1. `config`
2. `crates/router/src/configs`
3. `loadtest/config`
-->


## Motivation and Context
Closes #5689 

## How did you test it?
For org level switch
```
curl --location 'http://localhost:8080/user/switch/org' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer JWT' \
--data '{
    "org_id": "some_org_id"
}'
```
For merchant level switch
```
curl --location 'http://localhost:8080/user/switch/merchant' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer JWT' \
--data '{
    "merchant_id": "some_merchant_id"
}'
```
For profile level switch
```
curl --location 'http://localhost:8080/user/switch/profile' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer JWT' \
--data '{
    "profile_id": "some_profile_id"
}'
```

Response
```
{
    "token": new_token",
    "token_type": "user_info"
}
```
If switch happened correctly then we will get new token in response else error accordingly.


## Checklist
<!-- Put an `x` in the boxes that apply -->

- [x] I formatted the code `cargo +nightly fmt --all`
- [x] I addressed lints thrown by `cargo clippy`
- [x] I reviewed the submitted code
- [ ] I added unit tests for my changes where possible
