Bug: feat(users): List users in lineage API



- Currently user roles are grouped by user email in dashboard.
- Payload might look something like this
	```json
	[
		{
			"email": "e1",
			"roles": [
				{
					"role_id": "r1",
					"role_name": "name"
				}
			]
		}
	]
	```

diff --git a/crates/api_models/src/user.rs b/crates/api_models/src/user.rs
index 2547f082039..97d0c63fdf1 100644
--- a/crates/api_models/src/user.rs
+++ b/crates/api_models/src/user.rs
@@ -418,3 +418,15 @@ pub struct ListProfilesForUserInOrgAndMerchantAccountResponse {
     pub profile_id: id_type::ProfileId,
     pub profile_name: String,
 }
+
+#[derive(Debug, serde::Serialize)]
+pub struct ListUsersInEntityResponse {
+    pub email: pii::Email,
+    pub roles: Vec<MinimalRoleInfo>,
+}
+
+#[derive(Debug, serde::Serialize, Clone)]
+pub struct MinimalRoleInfo {
+    pub role_id: String,
+    pub role_name: String,
+}
diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs
index dd935b9a50e..d221a95c255 100644
--- a/crates/common_enums/src/enums.rs
+++ b/crates/common_enums/src/enums.rs
@@ -3094,6 +3094,7 @@ pub enum ApiVersion {
     strum::Display,
     strum::EnumString,
     ToSchema,
+    Hash,
 )]
 #[router_derive::diesel_enum(storage_type = "text")]
 #[strum(serialize_all = "snake_case")]
diff --git a/crates/common_utils/src/consts.rs b/crates/common_utils/src/consts.rs
index 1f4353f25d7..f59fedf5c4e 100644
--- a/crates/common_utils/src/consts.rs
+++ b/crates/common_utils/src/consts.rs
@@ -142,3 +142,10 @@ pub const MAX_ALLOWED_MERCHANT_NAME_LENGTH: usize = 64;
 
 /// Default locale
 pub const DEFAULT_LOCALE: &str = "en";
+
+/// Role ID for Org Admin
+pub const ROLE_ID_ORGANIZATION_ADMIN: &str = "org_admin";
+/// Role ID for Internal View Only
+pub const ROLE_ID_INTERNAL_VIEW_ONLY_USER: &str = "internal_view_only";
+/// Role ID for Internal Admin
+pub const ROLE_ID_INTERNAL_ADMIN: &str = "internal_admin";
diff --git a/crates/diesel_models/src/query/user_role.rs b/crates/diesel_models/src/query/user_role.rs
index 7904d3c2645..85f866f2fc2 100644
--- a/crates/diesel_models/src/query/user_role.rs
+++ b/crates/diesel_models/src/query/user_role.rs
@@ -185,7 +185,7 @@ impl UserRole {
             .await
     }
 
-    pub async fn generic_user_roles_list(
+    pub async fn generic_user_roles_list_for_user(
         conn: &PgPooledConn,
         user_id: String,
         org_id: Option<id_type::OrganizationId>,
@@ -235,4 +235,50 @@ impl UserRole {
             },
         }
     }
+
+    pub async fn generic_user_roles_list_for_org_and_extra(
+        conn: &PgPooledConn,
+        user_id: Option<String>,
+        org_id: id_type::OrganizationId,
+        merchant_id: Option<id_type::MerchantId>,
+        profile_id: Option<id_type::ProfileId>,
+        version: Option<UserRoleVersion>,
+    ) -> StorageResult<Vec<Self>> {
+        let mut query = <Self as HasTable>::table()
+            .filter(dsl::org_id.eq(org_id))
+            .into_boxed();
+
+        if let Some(user_id) = user_id {
+            query = query.filter(dsl::user_id.eq(user_id));
+        }
+
+        if let Some(merchant_id) = merchant_id {
+            query = query.filter(dsl::merchant_id.eq(merchant_id));
+        }
+
+        if let Some(profile_id) = profile_id {
+            query = query.filter(dsl::profile_id.eq(profile_id));
+        }
+
+        if let Some(version) = version {
+            query = query.filter(dsl::version.eq(version));
+        }
+
+        router_env::logger::debug!(query = %debug_query::<Pg,_>(&query).to_string());
+
+        match generics::db_metrics::track_database_call::<Self, _, _>(
+            query.get_results_async(conn),
+            generics::db_metrics::DatabaseOperation::Filter,
+        )
+        .await
+        {
+            Ok(value) => Ok(value),
+            Err(err) => match err {
+                DieselError::NotFound => {
+                    Err(report!(err)).change_context(errors::DatabaseError::NotFound)
+                }
+                _ => Err(report!(err)).change_context(errors::DatabaseError::Others),
+            },
+        }
+    }
 }
diff --git a/crates/diesel_models/src/user_role.rs b/crates/diesel_models/src/user_role.rs
index b508c86211b..ac7def5ec2a 100644
--- a/crates/diesel_models/src/user_role.rs
+++ b/crates/diesel_models/src/user_role.rs
@@ -1,11 +1,13 @@
+use std::hash::Hash;
+
 use common_enums::EntityType;
-use common_utils::id_type;
+use common_utils::{consts, id_type};
 use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable};
 use time::PrimitiveDateTime;
 
 use crate::{enums, schema::user_roles};
 
-#[derive(Clone, Debug, Identifiable, Queryable, Selectable)]
+#[derive(Clone, Debug, Identifiable, Queryable, Selectable, Eq)]
 #[diesel(table_name = user_roles, check_for_backend(diesel::pg::Pg))]
 pub struct UserRole {
     pub id: i32,
@@ -24,6 +26,55 @@ pub struct UserRole {
     pub version: enums::UserRoleVersion,
 }
 
+fn get_entity_id_and_type(user_role: &UserRole) -> (Option<String>, Option<EntityType>) {
+    match (user_role.version, user_role.role_id.as_str()) {
+        (enums::UserRoleVersion::V1, consts::ROLE_ID_ORGANIZATION_ADMIN) => (
+            user_role
+                .org_id
+                .clone()
+                .map(|org_id| org_id.get_string_repr().to_string()),
+            Some(EntityType::Organization),
+        ),
+        (enums::UserRoleVersion::V1, consts::ROLE_ID_INTERNAL_VIEW_ONLY_USER)
+        | (enums::UserRoleVersion::V1, consts::ROLE_ID_INTERNAL_ADMIN) => (
+            user_role
+                .merchant_id
+                .clone()
+                .map(|merchant_id| merchant_id.get_string_repr().to_string()),
+            Some(EntityType::Internal),
+        ),
+        (enums::UserRoleVersion::V1, _) => (
+            user_role
+                .merchant_id
+                .clone()
+                .map(|merchant_id| merchant_id.get_string_repr().to_string()),
+            Some(EntityType::Merchant),
+        ),
+        (enums::UserRoleVersion::V2, _) => (user_role.entity_id.clone(), user_role.entity_type),
+    }
+}
+
+impl Hash for UserRole {
+    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+        let (entity_id, entity_type) = get_entity_id_and_type(self);
+
+        self.user_id.hash(state);
+        entity_id.hash(state);
+        entity_type.hash(state);
+    }
+}
+
+impl PartialEq for UserRole {
+    fn eq(&self, other: &Self) -> bool {
+        let (self_entity_id, self_entity_type) = get_entity_id_and_type(self);
+        let (other_entity_id, other_entity_type) = get_entity_id_and_type(other);
+
+        self.user_id == other.user_id
+            && self_entity_id == other_entity_id
+            && self_entity_type == other_entity_type
+    }
+}
+
 #[derive(router_derive::Setter, Clone, Debug, Insertable, router_derive::DebugAsDisplay)]
 #[diesel(table_name = user_roles)]
 pub struct UserRoleNew {
diff --git a/crates/router/src/consts/user_role.rs b/crates/router/src/consts/user_role.rs
index 0a5d6556a11..672e5830013 100644
--- a/crates/router/src/consts/user_role.rs
+++ b/crates/router/src/consts/user_role.rs
@@ -1,8 +1,5 @@
 // User Roles
-pub const ROLE_ID_INTERNAL_VIEW_ONLY_USER: &str = "internal_view_only";
-pub const ROLE_ID_INTERNAL_ADMIN: &str = "internal_admin";
 pub const ROLE_ID_MERCHANT_ADMIN: &str = "merchant_admin";
-pub const ROLE_ID_ORGANIZATION_ADMIN: &str = "org_admin";
 pub const ROLE_ID_MERCHANT_VIEW_ONLY: &str = "merchant_view_only";
 pub const ROLE_ID_MERCHANT_IAM_ADMIN: &str = "merchant_iam_admin";
 pub const ROLE_ID_MERCHANT_DEVELOPER: &str = "merchant_developer";
diff --git a/crates/router/src/core/errors/user.rs b/crates/router/src/core/errors/user.rs
index 1cd37679ad4..e3c4bedab72 100644
--- a/crates/router/src/core/errors/user.rs
+++ b/crates/router/src/core/errors/user.rs
@@ -88,6 +88,8 @@ pub enum UserErrors {
     AuthConfigParsingError,
     #[error("Invalid SSO request")]
     SSOFailed,
+    #[error("profile_id missing in JWT")]
+    JwtProfileIdMissing,
 }
 
 impl common_utils::errors::ErrorSwitch<api_models::errors::types::ApiErrorResponse> for UserErrors {
@@ -224,6 +226,9 @@ impl common_utils::errors::ErrorSwitch<api_models::errors::types::ApiErrorRespon
             Self::SSOFailed => {
                 AER::BadRequest(ApiError::new(sub_code, 46, self.get_error_message(), None))
             }
+            Self::JwtProfileIdMissing => {
+                AER::Unauthorized(ApiError::new(sub_code, 47, self.get_error_message(), None))
+            }
         }
     }
 }
@@ -271,6 +276,7 @@ impl UserErrors {
             Self::InvalidUserAuthMethodOperation => "Invalid user auth method operation",
             Self::AuthConfigParsingError => "Auth config parsing error",
             Self::SSOFailed => "Invalid SSO request",
+            Self::JwtProfileIdMissing => "profile_id missing in JWT",
         }
     }
 }
diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs
index 9510ddd632d..17ebd00a994 100644
--- a/crates/router/src/core/user.rs
+++ b/crates/router/src/core/user.rs
@@ -62,7 +62,7 @@ pub async fn signup_with_merchant_id(
     let user_role = new_user
         .insert_user_role_in_db(
             state.clone(),
-            consts::user_role::ROLE_ID_ORGANIZATION_ADMIN.to_string(),
+            common_utils::consts::ROLE_ID_ORGANIZATION_ADMIN.to_string(),
             UserStatus::Active,
         )
         .await?;
@@ -134,7 +134,7 @@ pub async fn signup(
     let user_role = new_user
         .insert_user_role_in_db(
             state.clone(),
-            consts::user_role::ROLE_ID_ORGANIZATION_ADMIN.to_string(),
+            common_utils::consts::ROLE_ID_ORGANIZATION_ADMIN.to_string(),
             UserStatus::Active,
         )
         .await?;
@@ -165,7 +165,7 @@ pub async fn signup_token_only_flow(
     let user_role = new_user
         .insert_user_role_in_db(
             state.clone(),
-            consts::user_role::ROLE_ID_ORGANIZATION_ADMIN.to_string(),
+            common_utils::consts::ROLE_ID_ORGANIZATION_ADMIN.to_string(),
             UserStatus::Active,
         )
         .await?;
@@ -316,7 +316,7 @@ pub async fn connect_account(
         let user_role = new_user
             .insert_user_role_in_db(
                 state.clone(),
-                consts::user_role::ROLE_ID_ORGANIZATION_ADMIN.to_string(),
+                common_utils::consts::ROLE_ID_ORGANIZATION_ADMIN.to_string(),
                 UserStatus::Active,
             )
             .await?;
@@ -1310,7 +1310,7 @@ pub async fn create_internal_user(
     new_user
         .insert_user_role_in_db(
             state,
-            consts::user_role::ROLE_ID_INTERNAL_VIEW_ONLY_USER.to_string(),
+            common_utils::consts::ROLE_ID_INTERNAL_VIEW_ONLY_USER.to_string(),
             UserStatus::Active,
         )
         .await?;
@@ -1389,7 +1389,7 @@ pub async fn switch_merchant_id(
     } else {
         let user_roles = state
             .store
-            .list_user_roles_by_user_id(&user_from_token.user_id, UserRoleVersion::V1)
+            .list_user_roles_by_user_id_and_version(&user_from_token.user_id, UserRoleVersion::V1)
             .await
             .change_context(UserErrors::InternalServerError)?;
 
@@ -1450,7 +1450,7 @@ pub async fn create_merchant_account(
     let role_insertion_res = new_user
         .insert_user_role_in_db(
             state.clone(),
-            consts::user_role::ROLE_ID_ORGANIZATION_ADMIN.to_string(),
+            common_utils::consts::ROLE_ID_ORGANIZATION_ADMIN.to_string(),
             UserStatus::Active,
         )
         .await;
@@ -1471,7 +1471,10 @@ pub async fn list_merchants_for_user(
 ) -> UserResponse<Vec<user_api::UserMerchantAccount>> {
     let user_roles = state
         .store
-        .list_user_roles_by_user_id(user_from_token.user_id.as_str(), UserRoleVersion::V1)
+        .list_user_roles_by_user_id_and_version(
+            user_from_token.user_id.as_str(),
+            UserRoleVersion::V1,
+        )
         .await
         .change_context(UserErrors::InternalServerError)?;
 
@@ -2572,7 +2575,7 @@ pub async fn list_orgs_for_user(
 ) -> UserResponse<Vec<user_api::ListOrgsForUserResponse>> {
     let orgs = state
         .store
-        .list_user_roles(
+        .list_user_roles_by_user_id(
             user_from_token.user_id.as_str(),
             None,
             None,
@@ -2638,7 +2641,7 @@ pub async fn list_merchants_for_user_in_org(
     } else {
         let merchant_ids = state
             .store
-            .list_user_roles(
+            .list_user_roles_by_user_id(
                 user_from_token.user_id.as_str(),
                 Some(&user_from_token.org_id),
                 None,
@@ -2721,7 +2724,7 @@ pub async fn list_profiles_for_user_in_org_and_merchant_account(
         } else {
             let profile_ids = state
                 .store
-                .list_user_roles(
+                .list_user_roles_by_user_id(
                     user_from_token.user_id.as_str(),
                     Some(&user_from_token.org_id),
                     Some(&user_from_token.merchant_id),
@@ -2793,7 +2796,7 @@ pub async fn switch_org_for_user(
 
     let user_role = state
         .store
-        .list_user_roles(
+        .list_user_roles_by_user_id(
             &user_from_token.user_id,
             Some(&request.org_id),
             None,
@@ -3012,7 +3015,7 @@ pub async fn switch_merchant_for_user_in_org(
         EntityType::Merchant | EntityType::Profile => {
             let user_role = state
                 .store
-                .list_user_roles(
+                .list_user_roles_by_user_id(
                     &user_from_token.user_id,
                     Some(&user_from_token.org_id),
                     Some(&request.merchant_id),
@@ -3152,7 +3155,7 @@ pub async fn switch_profile_for_user_in_org_and_merchant(
         EntityType::Profile => {
             let user_role = state
                 .store
-                .list_user_roles(
+                .list_user_roles_by_user_id(
                     &user_from_token.user_id,
                     Some(&user_from_token.org_id),
                     Some(&user_from_token.merchant_id),
diff --git a/crates/router/src/core/user_role.rs b/crates/router/src/core/user_role.rs
index b599a26fdfc..971b34b0667 100644
--- a/crates/router/src/core/user_role.rs
+++ b/crates/router/src/core/user_role.rs
@@ -1,4 +1,4 @@
-use std::collections::HashMap;
+use std::collections::{HashMap, HashSet};
 
 use api_models::{user as user_api, user_role as user_role_api};
 use diesel_models::{
@@ -10,6 +10,7 @@ use once_cell::sync::Lazy;
 
 use crate::{
     core::errors::{StorageErrorExt, UserErrors, UserResponse},
+    db::user_role::ListUserRolesByOrgIdPayload,
     routes::{app::ReqState, SessionState},
     services::{
         authentication as auth,
@@ -20,7 +21,7 @@ use crate::{
     utils,
 };
 pub mod role;
-use common_enums::PermissionGroup;
+use common_enums::{EntityType, PermissionGroup};
 use strum::IntoEnumIterator;
 
 // TODO: To be deprecated once groups are stable
@@ -618,13 +619,13 @@ pub async fn delete_user_role(
     // Check if user has any more role associations
     let user_roles_v2 = state
         .store
-        .list_user_roles_by_user_id(user_from_db.get_user_id(), UserRoleVersion::V2)
+        .list_user_roles_by_user_id_and_version(user_from_db.get_user_id(), UserRoleVersion::V2)
         .await
         .change_context(UserErrors::InternalServerError)?;
 
     let user_roles_v1 = state
         .store
-        .list_user_roles_by_user_id(user_from_db.get_user_id(), UserRoleVersion::V1)
+        .list_user_roles_by_user_id_and_version(user_from_db.get_user_id(), UserRoleVersion::V1)
         .await
         .change_context(UserErrors::InternalServerError)?;
 
@@ -641,3 +642,135 @@ pub async fn delete_user_role(
     auth::blacklist::insert_user_in_blacklist(&state, user_from_db.get_user_id()).await?;
     Ok(ApplicationResponse::StatusOk)
 }
+
+pub async fn list_users_in_lineage(
+    state: SessionState,
+    user_from_token: auth::UserFromToken,
+) -> UserResponse<Vec<user_api::ListUsersInEntityResponse>> {
+    let requestor_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)?;
+
+    let user_roles_set: HashSet<_> = match requestor_role_info.get_entity_type() {
+        EntityType::Organization => state
+            .store
+            .list_user_roles_by_org_id(ListUserRolesByOrgIdPayload {
+                user_id: None,
+                org_id: &user_from_token.org_id,
+                merchant_id: None,
+                profile_id: None,
+                version: None,
+            })
+            .await
+            .change_context(UserErrors::InternalServerError)?
+            .into_iter()
+            .collect(),
+        EntityType::Merchant => state
+            .store
+            .list_user_roles_by_org_id(ListUserRolesByOrgIdPayload {
+                user_id: None,
+                org_id: &user_from_token.org_id,
+                merchant_id: Some(&user_from_token.merchant_id),
+                profile_id: None,
+                version: None,
+            })
+            .await
+            .change_context(UserErrors::InternalServerError)?
+            .into_iter()
+            .collect(),
+        EntityType::Profile => {
+            let Some(profile_id) = user_from_token.profile_id.as_ref() else {
+                return Err(UserErrors::JwtProfileIdMissing.into());
+            };
+
+            state
+                .store
+                .list_user_roles_by_org_id(ListUserRolesByOrgIdPayload {
+                    user_id: None,
+                    org_id: &user_from_token.org_id,
+                    merchant_id: Some(&user_from_token.merchant_id),
+                    profile_id: Some(profile_id),
+                    version: None,
+                })
+                .await
+                .change_context(UserErrors::InternalServerError)?
+                .into_iter()
+                .collect()
+        }
+        EntityType::Internal => HashSet::new(),
+    };
+
+    let mut email_map = state
+        .global_store
+        .find_users_by_user_ids(
+            user_roles_set
+                .iter()
+                .map(|user_role| user_role.user_id.clone())
+                .collect(),
+        )
+        .await
+        .change_context(UserErrors::InternalServerError)?
+        .into_iter()
+        .map(|user| (user.user_id.clone(), user.email))
+        .collect::<HashMap<_, _>>();
+
+    let role_info_map =
+        futures::future::try_join_all(user_roles_set.iter().map(|user_role| async {
+            roles::RoleInfo::from_role_id(
+                &state,
+                &user_role.role_id,
+                &user_from_token.merchant_id,
+                &user_from_token.org_id,
+            )
+            .await
+            .map(|role_info| {
+                (
+                    user_role.role_id.clone(),
+                    user_api::MinimalRoleInfo {
+                        role_id: user_role.role_id.clone(),
+                        role_name: role_info.get_role_name().to_string(),
+                    },
+                )
+            })
+        }))
+        .await
+        .change_context(UserErrors::InternalServerError)?
+        .into_iter()
+        .collect::<HashMap<_, _>>();
+
+    let user_role_map = user_roles_set
+        .into_iter()
+        .fold(HashMap::new(), |mut map, user_role| {
+            map.entry(user_role.user_id)
+                .or_insert(Vec::with_capacity(1))
+                .push(user_role.role_id);
+            map
+        });
+
+    Ok(ApplicationResponse::Json(
+        user_role_map
+            .into_iter()
+            .map(|(user_id, role_id_vec)| {
+                Ok::<_, error_stack::Report<UserErrors>>(user_api::ListUsersInEntityResponse {
+                    email: email_map
+                        .remove(&user_id)
+                        .ok_or(UserErrors::InternalServerError)?,
+                    roles: role_id_vec
+                        .into_iter()
+                        .map(|role_id| {
+                            role_info_map
+                                .get(&role_id)
+                                .cloned()
+                                .ok_or(UserErrors::InternalServerError)
+                        })
+                        .collect::<Result<Vec<_>, _>>()?,
+                })
+            })
+            .collect::<Result<Vec<_>, _>>()?,
+    ))
+}
diff --git a/crates/router/src/core/user_role/role.rs b/crates/router/src/core/user_role/role.rs
index a286180e202..d79b3136422 100644
--- a/crates/router/src/core/user_role/role.rs
+++ b/crates/router/src/core/user_role/role.rs
@@ -5,7 +5,6 @@ use diesel_models::role::{RoleNew, RoleUpdate};
 use error_stack::{report, ResultExt};
 
 use crate::{
-    consts,
     core::errors::{StorageErrorExt, UserErrors, UserResponse},
     routes::{app::ReqState, SessionState},
     services::{
@@ -72,7 +71,7 @@ pub async fn create_role(
     .await?;
 
     if matches!(req.role_scope, RoleScope::Organization)
-        && user_from_token.role_id != consts::user_role::ROLE_ID_ORGANIZATION_ADMIN
+        && user_from_token.role_id != common_utils::consts::ROLE_ID_ORGANIZATION_ADMIN
     {
         return Err(report!(UserErrors::InvalidRoleOperation))
             .attach_printable("Non org admin user creating org level role");
@@ -292,7 +291,7 @@ pub async fn update_role(
     .to_not_found_response(UserErrors::InvalidRoleOperation)?;
 
     if matches!(role_info.get_scope(), RoleScope::Organization)
-        && user_from_token.role_id != consts::user_role::ROLE_ID_ORGANIZATION_ADMIN
+        && user_from_token.role_id != common_utils::consts::ROLE_ID_ORGANIZATION_ADMIN
     {
         return Err(report!(UserErrors::InvalidRoleOperation))
             .attach_printable("Non org admin user changing org level role");
diff --git a/crates/router/src/db/kafka_store.rs b/crates/router/src/db/kafka_store.rs
index 403b79a0377..0d8b753463b 100644
--- a/crates/router/src/db/kafka_store.rs
+++ b/crates/router/src/db/kafka_store.rs
@@ -35,7 +35,7 @@ use super::{
     user::{sample_data::BatchSampleDataInterface, UserInterface},
     user_authentication_method::UserAuthenticationMethodInterface,
     user_key_store::UserKeyStoreInterface,
-    user_role::UserRoleInterface,
+    user_role::{ListUserRolesByOrgIdPayload, UserRoleInterface},
 };
 #[cfg(feature = "payouts")]
 use crate::services::kafka::payout::KafkaPayout;
@@ -2792,13 +2792,13 @@ impl UserRoleInterface for KafkaStore {
             .await
     }
 
-    async fn list_user_roles_by_user_id(
+    async fn list_user_roles_by_user_id_and_version(
         &self,
         user_id: &str,
         version: enums::UserRoleVersion,
     ) -> CustomResult<Vec<user_storage::UserRole>, errors::StorageError> {
         self.diesel_store
-            .list_user_roles_by_user_id(user_id, version)
+            .list_user_roles_by_user_id_and_version(user_id, version)
             .await
     }
 
@@ -2871,7 +2871,7 @@ impl UserRoleInterface for KafkaStore {
             .await
     }
 
-    async fn list_user_roles(
+    async fn list_user_roles_by_user_id(
         &self,
         user_id: &str,
         org_id: Option<&id_type::OrganizationId>,
@@ -2881,9 +2881,23 @@ impl UserRoleInterface for KafkaStore {
         version: Option<enums::UserRoleVersion>,
     ) -> CustomResult<Vec<storage::UserRole>, errors::StorageError> {
         self.diesel_store
-            .list_user_roles(user_id, org_id, merchant_id, profile_id, entity_id, version)
+            .list_user_roles_by_user_id(
+                user_id,
+                org_id,
+                merchant_id,
+                profile_id,
+                entity_id,
+                version,
+            )
             .await
     }
+
+    async fn list_user_roles_by_org_id<'a>(
+        &self,
+        payload: ListUserRolesByOrgIdPayload<'a>,
+    ) -> CustomResult<Vec<user_storage::UserRole>, errors::StorageError> {
+        self.diesel_store.list_user_roles_by_org_id(payload).await
+    }
 }
 
 #[async_trait::async_trait]
diff --git a/crates/router/src/db/user_role.rs b/crates/router/src/db/user_role.rs
index 524eaaaab79..8d07f53e12d 100644
--- a/crates/router/src/db/user_role.rs
+++ b/crates/router/src/db/user_role.rs
@@ -30,7 +30,7 @@ pub trait UserRoleInterface {
         version: enums::UserRoleVersion,
     ) -> CustomResult<storage::UserRole, errors::StorageError>;
 
-    async fn list_user_roles_by_user_id(
+    async fn list_user_roles_by_user_id_and_version(
         &self,
         user_id: &str,
         version: enums::UserRoleVersion,
@@ -70,7 +70,7 @@ pub trait UserRoleInterface {
         version: enums::UserRoleVersion,
     ) -> CustomResult<storage::UserRole, errors::StorageError>;
 
-    async fn list_user_roles(
+    async fn list_user_roles_by_user_id(
         &self,
         user_id: &str,
         org_id: Option<&id_type::OrganizationId>,
@@ -79,6 +79,19 @@ pub trait UserRoleInterface {
         entity_id: Option<&String>,
         version: Option<enums::UserRoleVersion>,
     ) -> CustomResult<Vec<storage::UserRole>, errors::StorageError>;
+
+    async fn list_user_roles_by_org_id<'a>(
+        &self,
+        payload: ListUserRolesByOrgIdPayload<'a>,
+    ) -> CustomResult<Vec<storage::UserRole>, errors::StorageError>;
+}
+
+pub struct ListUserRolesByOrgIdPayload<'a> {
+    pub user_id: Option<&'a String>,
+    pub org_id: &'a id_type::OrganizationId,
+    pub merchant_id: Option<&'a id_type::MerchantId>,
+    pub profile_id: Option<&'a id_type::ProfileId>,
+    pub version: Option<enums::UserRoleVersion>,
 }
 
 #[async_trait::async_trait]
@@ -126,7 +139,7 @@ impl UserRoleInterface for Store {
     }
 
     #[instrument(skip_all)]
-    async fn list_user_roles_by_user_id(
+    async fn list_user_roles_by_user_id_and_version(
         &self,
         user_id: &str,
         version: enums::UserRoleVersion,
@@ -217,7 +230,7 @@ impl UserRoleInterface for Store {
         .map_err(|error| report!(errors::StorageError::from(error)))
     }
 
-    async fn list_user_roles(
+    async fn list_user_roles_by_user_id(
         &self,
         user_id: &str,
         org_id: Option<&id_type::OrganizationId>,
@@ -227,7 +240,7 @@ impl UserRoleInterface for Store {
         version: Option<enums::UserRoleVersion>,
     ) -> CustomResult<Vec<storage::UserRole>, errors::StorageError> {
         let conn = connection::pg_connection_read(self).await?;
-        storage::UserRole::generic_user_roles_list(
+        storage::UserRole::generic_user_roles_list_for_user(
             &conn,
             user_id.to_owned(),
             org_id.cloned(),
@@ -239,6 +252,23 @@ impl UserRoleInterface for Store {
         .await
         .map_err(|error| report!(errors::StorageError::from(error)))
     }
+
+    async fn list_user_roles_by_org_id<'a>(
+        &self,
+        payload: ListUserRolesByOrgIdPayload<'a>,
+    ) -> CustomResult<Vec<storage::UserRole>, errors::StorageError> {
+        let conn = connection::pg_connection_read(self).await?;
+        storage::UserRole::generic_user_roles_list_for_org_and_extra(
+            &conn,
+            payload.user_id.cloned(),
+            payload.org_id.to_owned(),
+            payload.merchant_id.cloned(),
+            payload.profile_id.cloned(),
+            payload.version,
+        )
+        .await
+        .map_err(|error| report!(errors::StorageError::from(error)))
+    }
 }
 
 #[async_trait::async_trait]
@@ -328,7 +358,7 @@ impl UserRoleInterface for MockDb {
         .into())
     }
 
-    async fn list_user_roles_by_user_id(
+    async fn list_user_roles_by_user_id_and_version(
         &self,
         user_id: &str,
         version: enums::UserRoleVersion,
@@ -505,7 +535,7 @@ impl UserRoleInterface for MockDb {
         }
     }
 
-    async fn list_user_roles(
+    async fn list_user_roles_by_user_id(
         &self,
         user_id: &str,
         org_id: Option<&id_type::OrganizationId>,
@@ -551,4 +581,48 @@ impl UserRoleInterface for MockDb {
 
         Ok(filtered_roles)
     }
+
+    async fn list_user_roles_by_org_id<'a>(
+        &self,
+        payload: ListUserRolesByOrgIdPayload<'a>,
+    ) -> CustomResult<Vec<storage::UserRole>, errors::StorageError> {
+        let user_roles = self.user_roles.lock().await;
+
+        let mut filtered_roles = Vec::new();
+
+        for role in user_roles.iter() {
+            let role_org_id = role
+                .org_id
+                .as_ref()
+                .ok_or(report!(errors::StorageError::MockDbError))?;
+
+            let mut filter_condition = role_org_id == payload.org_id;
+
+            if let Some(user_id) = payload.user_id {
+                filter_condition = filter_condition && user_id == &role.user_id
+            }
+
+            role.merchant_id.as_ref().zip(payload.merchant_id).inspect(
+                |(role_merchant_id, merchant_id)| {
+                    filter_condition = filter_condition && role_merchant_id == merchant_id
+                },
+            );
+
+            role.profile_id.as_ref().zip(payload.profile_id).inspect(
+                |(role_profile_id, profile_id)| {
+                    filter_condition = filter_condition && role_profile_id == profile_id
+                },
+            );
+
+            payload
+                .version
+                .inspect(|ver| filter_condition = filter_condition && ver == &role.version);
+
+            if filter_condition {
+                filtered_roles.push(role.clone())
+            }
+        }
+
+        Ok(filtered_roles)
+    }
 }
diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs
index 97042485d6b..32bcc34d6bb 100644
--- a/crates/router/src/routes/app.rs
+++ b/crates/router/src/routes/app.rs
@@ -1799,6 +1799,7 @@ impl User {
                 .service(
                     web::resource("/list").route(web::get().to(list_users_for_merchant_account)),
                 )
+                .service(web::resource("/v2/list").route(web::get().to(list_users_in_lineage)))
                 .service(
                     web::resource("/invite_multiple").route(web::post().to(invite_multiple_user)),
                 )
diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs
index f64fab1f408..f2a1d7e3458 100644
--- a/crates/router/src/routes/lock_utils.rs
+++ b/crates/router/src/routes/lock_utils.rs
@@ -263,7 +263,8 @@ impl From<Flow> for ApiIdentifier {
             | Flow::DeleteUserRole
             | Flow::CreateRole
             | Flow::UpdateRole
-            | Flow::UserFromEmail => Self::UserRole,
+            | Flow::UserFromEmail
+            | Flow::ListUsersInLineage => Self::UserRole,
 
             Flow::GetActionUrl | Flow::SyncOnboardingStatus | Flow::ResetTrackingId => {
                 Self::ConnectorOnboarding
diff --git a/crates/router/src/routes/user_role.rs b/crates/router/src/routes/user_role.rs
index 526722af98c..a730321e190 100644
--- a/crates/router/src/routes/user_role.rs
+++ b/crates/router/src/routes/user_role.rs
@@ -269,3 +269,20 @@ pub async fn get_role_information(
     ))
     .await
 }
+
+pub async fn list_users_in_lineage(state: web::Data<AppState>, req: HttpRequest) -> HttpResponse {
+    let flow = Flow::ListUsersInLineage;
+
+    Box::pin(api::server_wrap(
+        flow,
+        state.clone(),
+        &req,
+        (),
+        |state, user_from_token, _, _| {
+            user_role_core::list_users_in_lineage(state, user_from_token)
+        },
+        &auth::DashboardNoPermissionAuth,
+        api_locking::LockAction::NotApplicable,
+    ))
+    .await
+}
diff --git a/crates/router/src/services/authorization/roles/predefined_roles.rs b/crates/router/src/services/authorization/roles/predefined_roles.rs
index 2f67af9f23a..f5320c41335 100644
--- a/crates/router/src/services/authorization/roles/predefined_roles.rs
+++ b/crates/router/src/services/authorization/roles/predefined_roles.rs
@@ -9,7 +9,7 @@ use crate::consts;
 pub static PREDEFINED_ROLES: Lazy<HashMap<&'static str, RoleInfo>> = Lazy::new(|| {
     let mut roles = HashMap::new();
     roles.insert(
-        consts::user_role::ROLE_ID_INTERNAL_ADMIN,
+        common_utils::consts::ROLE_ID_INTERNAL_ADMIN,
         RoleInfo {
             groups: vec![
                 PermissionGroup::OperationsView,
@@ -25,7 +25,7 @@ pub static PREDEFINED_ROLES: Lazy<HashMap<&'static str, RoleInfo>> = Lazy::new(|
                 PermissionGroup::MerchantDetailsManage,
                 PermissionGroup::OrganizationManage,
             ],
-            role_id: consts::user_role::ROLE_ID_INTERNAL_ADMIN.to_string(),
+            role_id: common_utils::consts::ROLE_ID_INTERNAL_ADMIN.to_string(),
             role_name: "internal_admin".to_string(),
             scope: RoleScope::Organization,
             entity_type: EntityType::Internal,
@@ -36,7 +36,7 @@ pub static PREDEFINED_ROLES: Lazy<HashMap<&'static str, RoleInfo>> = Lazy::new(|
         },
     );
     roles.insert(
-        consts::user_role::ROLE_ID_INTERNAL_VIEW_ONLY_USER,
+        common_utils::consts::ROLE_ID_INTERNAL_VIEW_ONLY_USER,
         RoleInfo {
             groups: vec![
                 PermissionGroup::OperationsView,
@@ -46,7 +46,7 @@ pub static PREDEFINED_ROLES: Lazy<HashMap<&'static str, RoleInfo>> = Lazy::new(|
                 PermissionGroup::UsersView,
                 PermissionGroup::MerchantDetailsView,
             ],
-            role_id: consts::user_role::ROLE_ID_INTERNAL_VIEW_ONLY_USER.to_string(),
+            role_id: common_utils::consts::ROLE_ID_INTERNAL_VIEW_ONLY_USER.to_string(),
             role_name: "internal_view_only".to_string(),
             scope: RoleScope::Organization,
             entity_type: EntityType::Internal,
@@ -58,7 +58,7 @@ pub static PREDEFINED_ROLES: Lazy<HashMap<&'static str, RoleInfo>> = Lazy::new(|
     );
 
     roles.insert(
-        consts::user_role::ROLE_ID_ORGANIZATION_ADMIN,
+        common_utils::consts::ROLE_ID_ORGANIZATION_ADMIN,
         RoleInfo {
             groups: vec![
                 PermissionGroup::OperationsView,
@@ -74,7 +74,7 @@ pub static PREDEFINED_ROLES: Lazy<HashMap<&'static str, RoleInfo>> = Lazy::new(|
                 PermissionGroup::MerchantDetailsManage,
                 PermissionGroup::OrganizationManage,
             ],
-            role_id: consts::user_role::ROLE_ID_ORGANIZATION_ADMIN.to_string(),
+            role_id: common_utils::consts::ROLE_ID_ORGANIZATION_ADMIN.to_string(),
             role_name: "organization_admin".to_string(),
             scope: RoleScope::Organization,
             entity_type: EntityType::Organization,
diff --git a/crates/router/src/types/domain/user.rs b/crates/router/src/types/domain/user.rs
index 3df0a11af0e..b252e618536 100644
--- a/crates/router/src/types/domain/user.rs
+++ b/crates/router/src/types/domain/user.rs
@@ -868,7 +868,7 @@ impl UserFromStorage {
     pub async fn get_roles_from_db(&self, state: &SessionState) -> UserResult<Vec<UserRole>> {
         state
             .store
-            .list_user_roles_by_user_id(&self.0.user_id, UserRoleVersion::V1)
+            .list_user_roles_by_user_id_and_version(&self.0.user_id, UserRoleVersion::V1)
             .await
             .change_context(UserErrors::InternalServerError)
     }
@@ -949,7 +949,7 @@ impl UserFromStorage {
         } else {
             state
                 .store
-                .list_user_roles_by_user_id(&self.0.user_id, UserRoleVersion::V1)
+                .list_user_roles_by_user_id_and_version(&self.0.user_id, UserRoleVersion::V1)
                 .await?
                 .into_iter()
                 .find(|role| role.status == UserStatus::Active)
diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs
index 88eec0f791a..538ae888cd5 100644
--- a/crates/router_env/src/logger/types.rs
+++ b/crates/router_env/src/logger/types.rs
@@ -466,6 +466,8 @@ pub enum Flow {
     ListMerchantsForUserInOrg,
     /// List Profile for user in org and merchant
     ListProfileForUserInOrgAndMerchant,
+    /// List Users in Org
+    ListUsersInLineage,
     /// List initial webhook delivery attempts
     WebhookEventInitialDeliveryAttemptList,
     /// List delivery attempts for a webhook event

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

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

## Description
<!-- Describe your changes in detail -->
- This PR adds a new API which will be used to list the users in the lineage.
- This PR also adds a db function for listing users in a particular org.

### Additional Changes

- [x] 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
<!--
Why is this change required? What problem does it solve?
If it fixes an open issue, please link to the issue here.

If you don't have an issue, we'd recommend starting with one first so the PR
can focus on the implementation (unless it is an obvious bug or documentation fix
that will have little conversation).
-->
Closes #5721.

## How did you test it?
<!--
Did you write an integration/unit/API test to verify the code changes?
Or did you test this change manually (provide relevant screenshots)?
-->
```
curl --location 'http://localhost:8080/user/user/v2/list' \
--header 'Authorization: ••••••' \
```

If the JWT is of `org_admin`, it will list all the users in the org, else it will list all the users in merchant.

## 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
