Issue:
Bug: feat(roles): edge cases for multi-tenancy - I



Add tenant_id in custom roles
Add generic query to search across all tenants
Close https://github.com/juspay/hyperswitch-cloud/issues/7998
Close https://github.com/juspay/hyperswitch-cloud/issues/7998
Close https://github.com/juspay/hyperswitch-cloud/issues/8026

Pull request description:
## Type of Change
<!-- Put an `x` in the boxes that apply -->

- [x] Bugfix
- [x] New feature
- [ ] Enhancement
- [ ] Refactoring
- [ ] Dependency updates
- [ ] Documentation
- [ ] CI/CD

## Description
This PR
- add tenant_id in roles table
- add support for genenric query to serach user across all tenancies
- Fixes custom roles creates by tenant_level user
- List view for tenant level user
- Fix bug to search across envs for tenant admin

### 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 [#6999](https://github.com/juspay/hyperswitch/issues/6999)

## How did you test it?
Tenant admin able to create custom org level role
```
curl --location 'http://localhost:8080/user/role' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer JWT' \
--data '{
  "role_name": "custom_role_7",
  "groups": ["users_view"],
  "role_scope": "organization"
}'
```
Response
```
{
    "role_id": "role_KQxGGeCs6usRwxubnU2Y",
    "groups": [
        "users_view"
    ],
    "role_name": "custom_role_7",
    "role_scope": "organization"
}
```

For tenant level user, list api is showing that user in list, if we don't pass query, its coming with remaning org users
```
curl --location 'http://localhost:8080/user/user/list?entity_type=tenant' \
--header 'x-tenant-id: test' \
--header 'Authorization: Bearer JWT' \
```
Response
```
[
    {
        "email": "cd1@juspay.in",
        "roles": [
            {
                "role_id": "tenant_admin",
                "role_name": "tenant_admin"
            }
        ]
    }
]
```
For global search now, we are getting org_id in the query
<img width="1146" alt="Screenshot 2025-01-07 at 3 23 14 PM" src="https://github.com/user-attachments/assets/2cc0c6d9-1377-4fb6-883c-540845307d51" />


## 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


Testing:
Tenant admin able to create custom org level role
```
curl --location 'http://localhost:8080/user/role' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer JWT' \
--data '{
  "role_name": "custom_role_7",
  "groups": ["users_view"],
  "role_scope": "organization"
}'
```
Response
```
{
    "role_id": "role_KQxGGeCs6usRwxubnU2Y",
    "groups": [
        "users_view"
    ],
    "role_name": "custom_role_7",
    "role_scope": "organization"
}
```

For tenant level user, list api is showing that user in list, if we don't pass query, its coming with remaning org users
```
curl --location 'http://localhost:8080/user/user/list?entity_type=tenant' \
--header 'x-tenant-id: test' \
--header 'Authorization: Bearer JWT' \
```
Response
```
[
    {
        "email": "cd1@juspay.in",
        "roles": [
            {
                "role_id": "tenant_admin",
                "role_name": "tenant_admin"
            }
        ]
    }
]
```
For global search now, we are getting org_id in the query
<img width="1146" alt="Screenshot 2025-01-07 at 3 23 14 PM" src="https://github.com/user-attachments/assets/2cc0c6d9-1377-4fb6-883c-540845307d51" />

Patch:
diff --git a/crates/diesel_models/src/query/role.rs b/crates/diesel_models/src/query/role.rs
index 6f6a1404ee2..2ab58ec2382 100644
--- a/crates/diesel_models/src/query/role.rs
+++ b/crates/diesel_models/src/query/role.rs
@@ -32,14 +32,18 @@ impl Role {
         role_id: &str,
         merchant_id: &id_type::MerchantId,
         org_id: &id_type::OrganizationId,
+        tenant_id: &id_type::TenantId,
     ) -> StorageResult<Self> {
         generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
             conn,
-            dsl::role_id.eq(role_id.to_owned()).and(
-                dsl::merchant_id.eq(merchant_id.to_owned()).or(dsl::org_id
-                    .eq(org_id.to_owned())
-                    .and(dsl::scope.eq(RoleScope::Organization))),
-            ),
+            dsl::role_id
+                .eq(role_id.to_owned())
+                .and(dsl::tenant_id.eq(tenant_id.to_owned()))
+                .and(
+                    dsl::merchant_id.eq(merchant_id.to_owned()).or(dsl::org_id
+                        .eq(org_id.to_owned())
+                        .and(dsl::scope.eq(RoleScope::Organization))),
+                ),
         )
         .await
     }
@@ -49,11 +53,13 @@ impl Role {
         role_id: &str,
         merchant_id: &id_type::MerchantId,
         org_id: &id_type::OrganizationId,
+        tenant_id: &id_type::TenantId,
     ) -> StorageResult<Self> {
         generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
             conn,
             dsl::role_id
                 .eq(role_id.to_owned())
+                .and(dsl::tenant_id.eq(tenant_id.to_owned()))
                 .and(dsl::org_id.eq(org_id.to_owned()))
                 .and(
                     dsl::scope.eq(RoleScope::Organization).or(dsl::merchant_id
@@ -64,15 +70,17 @@ impl Role {
         .await
     }
 
-    pub async fn find_by_role_id_and_org_id(
+    pub async fn find_by_role_id_org_id_tenant_id(
         conn: &PgPooledConn,
         role_id: &str,
         org_id: &id_type::OrganizationId,
+        tenant_id: &id_type::TenantId,
     ) -> StorageResult<Self> {
         generics::generic_find_one::<<Self as HasTable>::Table, _, _>(
             conn,
             dsl::role_id
                 .eq(role_id.to_owned())
+                .and(dsl::tenant_id.eq(tenant_id.to_owned()))
                 .and(dsl::org_id.eq(org_id.to_owned())),
         )
         .await
@@ -108,12 +116,16 @@ impl Role {
         conn: &PgPooledConn,
         merchant_id: &id_type::MerchantId,
         org_id: &id_type::OrganizationId,
+        tenant_id: &id_type::TenantId,
     ) -> StorageResult<Vec<Self>> {
-        let predicate = dsl::org_id.eq(org_id.to_owned()).and(
-            dsl::scope.eq(RoleScope::Organization).or(dsl::merchant_id
-                .eq(merchant_id.to_owned())
-                .and(dsl::scope.eq(RoleScope::Merchant))),
-        );
+        let predicate = dsl::tenant_id
+            .eq(tenant_id.to_owned())
+            .and(dsl::org_id.eq(org_id.to_owned()))
+            .and(
+                dsl::scope.eq(RoleScope::Organization).or(dsl::merchant_id
+                    .eq(merchant_id.to_owned())
+                    .and(dsl::scope.eq(RoleScope::Merchant))),
+            );
 
         generics::generic_filter::<<Self as HasTable>::Table, _, _, _>(
             conn,
@@ -127,13 +139,14 @@ impl Role {
 
     pub async fn generic_roles_list_for_org(
         conn: &PgPooledConn,
+        tenant_id: id_type::TenantId,
         org_id: id_type::OrganizationId,
         merchant_id: Option<id_type::MerchantId>,
         entity_type: Option<common_enums::EntityType>,
         limit: Option<u32>,
     ) -> StorageResult<Vec<Self>> {
         let mut query = <Self as HasTable>::table()
-            .filter(dsl::org_id.eq(org_id))
+            .filter(dsl::tenant_id.eq(tenant_id).and(dsl::org_id.eq(org_id)))
             .into_boxed();
 
         if let Some(merchant_id) = merchant_id {
diff --git a/crates/diesel_models/src/query/user_role.rs b/crates/diesel_models/src/query/user_role.rs
index bb07f671824..01fa05e925a 100644
--- a/crates/diesel_models/src/query/user_role.rs
+++ b/crates/diesel_models/src/query/user_role.rs
@@ -294,4 +294,34 @@ impl UserRole {
             },
         }
     }
+
+    pub async fn list_user_roles_by_user_id_across_tenants(
+        conn: &PgPooledConn,
+        user_id: String,
+        limit: Option<u32>,
+    ) -> StorageResult<Vec<Self>> {
+        let mut query = <Self as HasTable>::table()
+            .filter(dsl::user_id.eq(user_id))
+            .into_boxed();
+        if let Some(limit) = limit {
+            query = query.limit(limit.into());
+        }
+
+        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/role.rs b/crates/diesel_models/src/role.rs
index 8199bd3979c..16728801933 100644
--- a/crates/diesel_models/src/role.rs
+++ b/crates/diesel_models/src/role.rs
@@ -19,6 +19,7 @@ pub struct Role {
     pub last_modified_at: PrimitiveDateTime,
     pub last_modified_by: String,
     pub entity_type: enums::EntityType,
+    pub tenant_id: id_type::TenantId,
 }
 
 #[derive(router_derive::Setter, Clone, Debug, Insertable, router_derive::DebugAsDisplay)]
@@ -36,6 +37,7 @@ pub struct RoleNew {
     pub last_modified_at: PrimitiveDateTime,
     pub last_modified_by: String,
     pub entity_type: enums::EntityType,
+    pub tenant_id: id_type::TenantId,
 }
 
 #[derive(Clone, Debug, AsChangeset, router_derive::DebugAsDisplay)]
diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs
index 37a39cb731a..f5b10e091ce 100644
--- a/crates/diesel_models/src/schema.rs
+++ b/crates/diesel_models/src/schema.rs
@@ -1308,6 +1308,8 @@ diesel::table! {
         last_modified_by -> Varchar,
         #[max_length = 64]
         entity_type -> Varchar,
+        #[max_length = 64]
+        tenant_id -> Varchar,
     }
 }
 
diff --git a/crates/diesel_models/src/schema_v2.rs b/crates/diesel_models/src/schema_v2.rs
index 3e27f29427b..2de2309ba20 100644
--- a/crates/diesel_models/src/schema_v2.rs
+++ b/crates/diesel_models/src/schema_v2.rs
@@ -1255,6 +1255,8 @@ diesel::table! {
         last_modified_by -> Varchar,
         #[max_length = 64]
         entity_type -> Varchar,
+        #[max_length = 64]
+        tenant_id -> Varchar,
     }
 }
 
diff --git a/crates/router/src/analytics.rs b/crates/router/src/analytics.rs
index 8fe30aa59d4..8fb4f2ac2c1 100644
--- a/crates/router/src/analytics.rs
+++ b/crates/router/src/analytics.rs
@@ -1925,32 +1925,57 @@ pub mod routes {
             json_payload.into_inner(),
             |state, auth: UserFromToken, req, _| async move {
                 let role_id = auth.role_id;
-                let role_info = RoleInfo::from_role_id_and_org_id(&state, &role_id, &auth.org_id)
-                    .await
-                    .change_context(UserErrors::InternalServerError)
-                    .change_context(OpenSearchError::UnknownError)?;
+                let role_info = RoleInfo::from_role_id_org_id_tenant_id(
+                    &state,
+                    &role_id,
+                    &auth.org_id,
+                    auth.tenant_id.as_ref().unwrap_or(&state.tenant.tenant_id),
+                )
+                .await
+                .change_context(UserErrors::InternalServerError)
+                .change_context(OpenSearchError::UnknownError)?;
                 let permission_groups = role_info.get_permission_groups();
                 if !permission_groups.contains(&common_enums::PermissionGroup::OperationsView) {
                     return Err(OpenSearchError::AccessForbiddenError)?;
                 }
-                let user_roles: HashSet<UserRole> = state
-                    .global_store
-                    .list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
-                        user_id: &auth.user_id,
-                        tenant_id: auth.tenant_id.as_ref().unwrap_or(&state.tenant.tenant_id),
-                        org_id: Some(&auth.org_id),
-                        merchant_id: None,
-                        profile_id: None,
-                        entity_id: None,
-                        version: None,
-                        status: None,
-                        limit: None,
-                    })
-                    .await
-                    .change_context(UserErrors::InternalServerError)
-                    .change_context(OpenSearchError::UnknownError)?
-                    .into_iter()
-                    .collect();
+                let user_roles: HashSet<UserRole> = match role_info.get_entity_type() {
+                    EntityType::Tenant => state
+                        .global_store
+                        .list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
+                            user_id: &auth.user_id,
+                            tenant_id: auth.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: None,
+                            limit: None,
+                        })
+                        .await
+                        .change_context(UserErrors::InternalServerError)
+                        .change_context(OpenSearchError::UnknownError)?
+                        .into_iter()
+                        .collect(),
+                    EntityType::Organization | EntityType::Merchant | EntityType::Profile => state
+                        .global_store
+                        .list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
+                            user_id: &auth.user_id,
+                            tenant_id: auth.tenant_id.as_ref().unwrap_or(&state.tenant.tenant_id),
+                            org_id: Some(&auth.org_id),
+                            merchant_id: None,
+                            profile_id: None,
+                            entity_id: None,
+                            version: None,
+                            status: None,
+                            limit: None,
+                        })
+                        .await
+                        .change_context(UserErrors::InternalServerError)
+                        .change_context(OpenSearchError::UnknownError)?
+                        .into_iter()
+                        .collect(),
+                };
 
                 let state = Arc::new(state);
                 let role_info_map: HashMap<String, RoleInfo> = user_roles
@@ -1959,12 +1984,15 @@ pub mod routes {
                         let state = Arc::clone(&state);
                         let role_id = user_role.role_id.clone();
                         let org_id = user_role.org_id.clone().unwrap_or_default();
+                        let tenant_id = &user_role.tenant_id;
                         async move {
-                            RoleInfo::from_role_id_and_org_id(&state, &role_id, &org_id)
-                                .await
-                                .change_context(UserErrors::InternalServerError)
-                                .change_context(OpenSearchError::UnknownError)
-                                .map(|role_info| (role_id, role_info))
+                            RoleInfo::from_role_id_org_id_tenant_id(
+                                &state, &role_id, &org_id, tenant_id,
+                            )
+                            .await
+                            .change_context(UserErrors::InternalServerError)
+                            .change_context(OpenSearchError::UnknownError)
+                            .map(|role_info| (role_id, role_info))
                         }
                     })
                     .collect::<FuturesUnordered<_>>()
@@ -2047,32 +2075,57 @@ pub mod routes {
             indexed_req,
             |state, auth: UserFromToken, req, _| async move {
                 let role_id = auth.role_id;
-                let role_info = RoleInfo::from_role_id_and_org_id(&state, &role_id, &auth.org_id)
-                    .await
-                    .change_context(UserErrors::InternalServerError)
-                    .change_context(OpenSearchError::UnknownError)?;
+                let role_info = RoleInfo::from_role_id_org_id_tenant_id(
+                    &state,
+                    &role_id,
+                    &auth.org_id,
+                    auth.tenant_id.as_ref().unwrap_or(&state.tenant.tenant_id),
+                )
+                .await
+                .change_context(UserErrors::InternalServerError)
+                .change_context(OpenSearchError::UnknownError)?;
                 let permission_groups = role_info.get_permission_groups();
                 if !permission_groups.contains(&common_enums::PermissionGroup::OperationsView) {
                     return Err(OpenSearchError::AccessForbiddenError)?;
                 }
-                let user_roles: HashSet<UserRole> = state
-                    .global_store
-                    .list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
-                        user_id: &auth.user_id,
-                        tenant_id: auth.tenant_id.as_ref().unwrap_or(&state.tenant.tenant_id),
-                        org_id: Some(&auth.org_id),
-                        merchant_id: None,
-                        profile_id: None,
-                        entity_id: None,
-                        version: None,
-                        status: None,
-                        limit: None,
-                    })
-                    .await
-                    .change_context(UserErrors::InternalServerError)
-                    .change_context(OpenSearchError::UnknownError)?
-                    .into_iter()
-                    .collect();
+                let user_roles: HashSet<UserRole> = match role_info.get_entity_type() {
+                    EntityType::Tenant => state
+                        .global_store
+                        .list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
+                            user_id: &auth.user_id,
+                            tenant_id: auth.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: None,
+                            limit: None,
+                        })
+                        .await
+                        .change_context(UserErrors::InternalServerError)
+                        .change_context(OpenSearchError::UnknownError)?
+                        .into_iter()
+                        .collect(),
+                    EntityType::Organization | EntityType::Merchant | EntityType::Profile => state
+                        .global_store
+                        .list_user_roles_by_user_id(ListUserRolesByUserIdPayload {
+                            user_id: &auth.user_id,
+                            tenant_id: auth.tenant_id.as_ref().unwrap_or(&state.tenant.tenant_id),
+                            org_id: Some(&auth.org_id),
+                            merchant_id: None,
+                            profile_id: None,
+                            entity_id: None,
+                            version: None,
+                            status: None,
+                            limit: None,
+                        })
+                        .await
+                        .change_context(UserErrors::InternalServerError)
+                        .change_context(OpenSearchError::UnknownError)?
+                        .into_iter()
+                        .collect(),
+                };
                 let state = Arc::new(state);
                 let role_info_map: HashMap<String, RoleInfo> = user_roles
                     .iter()
@@ -2080,12 +2133,15 @@ pub mod routes {
                         let state = Arc::clone(&state);
                         let role_id = user_role.role_id.clone();
                         let org_id = user_role.org_id.clone().unwrap_or_default();
+                        let tenant_id = &user_role.tenant_id;
                         async move {
-                            RoleInfo::from_role_id_and_org_id(&state, &role_id, &org_id)
-                                .await
-                                .change_context(UserErrors::InternalServerError)
-                                .change_context(OpenSearchError::UnknownError)
-                                .map(|role_info| (role_id, role_info))
+                            RoleInfo::from_role_id_org_id_tenant_id(
+                                &state, &role_id, &org_id, tenant_id,
+                            )
+                            .await
+                            .change_context(UserErrors::InternalServerError)
+                            .change_context(OpenSearchError::UnknownError)
+                            .map(|role_info| (role_id, role_info))
                         }
                     })
                     .collect::<FuturesUnordered<_>>()
diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs
index a7c60f33ff7..52d7d6a252e 100644
--- a/crates/router/src/core/user.rs
+++ b/crates/router/src/core/user.rs
@@ -116,10 +116,14 @@ pub async fn get_user_details(
 ) -> UserResponse<user_api::GetUserDetailsResponse> {
     let user = user_from_token.get_user_from_db(&state).await?;
     let verification_days_left = utils::user::get_verification_days_left(&state, &user)?;
-    let role_info = roles::RoleInfo::from_role_id_and_org_id(
+    let role_info = roles::RoleInfo::from_role_id_org_id_tenant_id(
         &state,
         &user_from_token.role_id,
         &user_from_token.org_id,
+        user_from_token
+            .tenant_id
+            .as_ref()
+            .unwrap_or(&state.tenant.tenant_id),
     )
     .await
     .change_context(UserErrors::InternalServerError)?;
@@ -633,6 +637,10 @@ async fn handle_invitation(
         &request.role_id,
         &user_from_token.merchant_id,
         &user_from_token.org_id,
+        user_from_token
+            .tenant_id
+            .as_ref()
+            .unwrap_or(&state.tenant.tenant_id),
     )
     .await
     .to_not_found_response(UserErrors::InvalidRoleId)?;
@@ -1155,10 +1163,14 @@ pub async fn resend_invite(
         .get_entity_id_and_type()
         .ok_or(UserErrors::InternalServerError)?;
 
-    let invitee_role_info = roles::RoleInfo::from_role_id_and_org_id(
+    let invitee_role_info = roles::RoleInfo::from_role_id_org_id_tenant_id(
         &state,
         &user_role.role_id,
         &user_from_token.org_id,
+        user_from_token
+            .tenant_id
+            .as_ref()
+            .unwrap_or(&state.tenant.tenant_id),
     )
     .await
     .change_context(UserErrors::InternalServerError)?;
@@ -1401,7 +1413,7 @@ pub async fn create_tenant_user(
         .change_context(UserErrors::InternalServerError)
         .attach_printable("Failed to get merchants list for org")?
         .pop()
-        .ok_or(UserErrors::InternalServerError)
+        .ok_or(UserErrors::InvalidRoleOperation)
         .attach_printable("No merchants found in the tenancy")?;
 
     let new_user = domain::NewUser::try_from((
@@ -1490,10 +1502,14 @@ pub async fn list_user_roles_details(
         .await
         .to_not_found_response(UserErrors::InvalidRoleOperation)?;
 
-    let requestor_role_info = roles::RoleInfo::from_role_id_and_org_id(
+    let requestor_role_info = roles::RoleInfo::from_role_id_org_id_tenant_id(
         &state,
         &user_from_token.role_id,
         &user_from_token.org_id,
+        user_from_token
+            .tenant_id
+            .as_ref()
+            .unwrap_or(&state.tenant.tenant_id),
     )
     .await
     .to_not_found_response(UserErrors::InternalServerError)
@@ -1644,10 +1660,14 @@ pub async fn list_user_roles_details(
             .collect::<HashSet<_>>()
             .into_iter()
             .map(|role_id| async {
-                let role_info = roles::RoleInfo::from_role_id_and_org_id(
+                let role_info = roles::RoleInfo::from_role_id_org_id_tenant_id(
                     &state,
                     &role_id,
                     &user_from_token.org_id,
+                    user_from_token
+                        .tenant_id
+                        .as_ref()
+                        .unwrap_or(&state.tenant.tenant_id),
                 )
                 .await
                 .change_context(UserErrors::InternalServerError)?;
@@ -2757,10 +2777,14 @@ pub async fn list_orgs_for_user(
     state: SessionState,
     user_from_token: auth::UserFromToken,
 ) -> UserResponse<Vec<user_api::ListOrgsForUserResponse>> {
-    let role_info = roles::RoleInfo::from_role_id_and_org_id(
+    let role_info = roles::RoleInfo::from_role_id_org_id_tenant_id(
         &state,
         &user_from_token.role_id,
         &user_from_token.org_id,
+        user_from_token
+            .tenant_id
+            .as_ref()
+            .unwrap_or(&state.tenant.tenant_id),
     )
     .await
     .change_context(UserErrors::InternalServerError)?;
@@ -2834,10 +2858,14 @@ pub async fn list_merchants_for_user_in_org(
     state: SessionState,
     user_from_token: auth::UserFromToken,
 ) -> UserResponse<Vec<user_api::ListMerchantsForUserInOrgResponse>> {
-    let role_info = roles::RoleInfo::from_role_id_and_org_id(
+    let role_info = roles::RoleInfo::from_role_id_org_id_tenant_id(
         &state,
         &user_from_token.role_id,
         &user_from_token.org_id,
+        user_from_token
+            .tenant_id
+            .as_ref()
+            .unwrap_or(&state.tenant.tenant_id),
     )
     .await
     .change_context(UserErrors::InternalServerError)?;
@@ -2909,10 +2937,14 @@ pub async fn list_profiles_for_user_in_org_and_merchant_account(
     state: SessionState,
     user_from_token: auth::UserFromToken,
 ) -> UserResponse<Vec<user_api::ListProfilesForUserInOrgAndMerchantAccountResponse>> {
-    let role_info = roles::RoleInfo::from_role_id_and_org_id(
+    let role_info = roles::RoleInfo::from_role_id_org_id_tenant_id(
         &state,
         &user_from_token.role_id,
         &user_from_token.org_id,
+        user_from_token
+            .tenant_id
+            .as_ref()
+            .unwrap_or(&state.tenant.tenant_id),
     )
     .await
     .change_context(UserErrors::InternalServerError)?;
@@ -3001,10 +3033,14 @@ pub async fn switch_org_for_user(
         .into());
     }
 
-    let role_info = roles::RoleInfo::from_role_id_and_org_id(
+    let role_info = roles::RoleInfo::from_role_id_org_id_tenant_id(
         &state,
         &user_from_token.role_id,
         &user_from_token.org_id,
+        user_from_token
+            .tenant_id
+            .as_ref()
+            .unwrap_or(&state.tenant.tenant_id),
     )
     .await
     .change_context(UserErrors::InternalServerError)
@@ -3092,12 +3128,20 @@ pub async fn switch_org_for_user(
         request.org_id.clone(),
         role_id.clone(),
         profile_id.clone(),
-        user_from_token.tenant_id,
+        user_from_token.tenant_id.clone(),
     )
     .await?;
 
-    utils::user_role::set_role_info_in_cache_by_role_id_org_id(&state, &role_id, &request.org_id)
-        .await;
+    utils::user_role::set_role_info_in_cache_by_role_id_org_id(
+        &state,
+        &role_id,
+        &request.org_id,
+        user_from_token
+            .tenant_id
+            .as_ref()
+            .unwrap_or(&state.tenant.tenant_id),
+    )
+    .await;
 
     let response = user_api::TokenResponse {
         token: token.clone(),
@@ -3120,10 +3164,14 @@ pub async fn switch_merchant_for_user_in_org(
     }
 
     let key_manager_state = &(&state).into();
-    let role_info = roles::RoleInfo::from_role_id_and_org_id(
+    let role_info = roles::RoleInfo::from_role_id_org_id_tenant_id(
         &state,
         &user_from_token.role_id,
         &user_from_token.org_id,
+        user_from_token
+            .tenant_id
+            .as_ref()
+            .unwrap_or(&state.tenant.tenant_id),
     )
     .await
     .change_context(UserErrors::InternalServerError)
@@ -3275,11 +3323,20 @@ pub async fn switch_merchant_for_user_in_org(
         org_id.clone(),
         role_id.clone(),
         profile_id,
-        user_from_token.tenant_id,
+        user_from_token.tenant_id.clone(),
     )
     .await?;
 
-    utils::user_role::set_role_info_in_cache_by_role_id_org_id(&state, &role_id, &org_id).await;
+    utils::user_role::set_role_info_in_cache_by_role_id_org_id(
+        &state,
+        &role_id,
+        &org_id,
+        user_from_token
+            .tenant_id
+            .as_ref()
+            .unwrap_or(&state.tenant.tenant_id),
+    )
+    .await;
 
     let response = user_api::TokenResponse {
         token: token.clone(),
@@ -3302,10 +3359,14 @@ pub async fn switch_profile_for_user_in_org_and_merchant(
     }
 
     let key_manager_state = &(&state).into();
-    let role_info = roles::RoleInfo::from_role_id_and_org_id(
+    let role_info = roles::RoleInfo::from_role_id_org_id_tenant_id(
         &state,
         &user_from_token.role_id,
         &user_from_token.org_id,
+        user_from_token
+            .tenant_id
+            .as_ref()
+            .unwrap_or(&state.tenant.tenant_id),
     )
     .await
     .change_context(UserErrors::InternalServerError)
@@ -3378,7 +3439,7 @@ pub async fn switch_profile_for_user_in_org_and_merchant(
         user_from_token.org_id.clone(),
         role_id.clone(),
         profile_id,
-        user_from_token.tenant_id,
+        user_from_token.tenant_id.clone(),
     )
     .await?;
 
@@ -3386,6 +3447,10 @@ pub async fn switch_profile_for_user_in_org_and_merchant(
         &state,
         &role_id,
         &user_from_token.org_id,
+        user_from_token
+            .tenant_id
+            .as_ref()
+            .unwrap_or(&state.tenant.tenant_id),
     )
     .await;
 
diff --git a/crates/router/src/core/user_role.rs b/crates/router/src/core/user_role.rs
index d8fdff0e623..19d91b14f01 100644
--- a/crates/router/src/core/user_role.rs
+++ b/crates/router/src/core/user_role.rs
@@ -83,10 +83,14 @@ pub async fn get_parent_group_info(
     state: SessionState,
     user_from_token: auth::UserFromToken,
 ) -> UserResponse<Vec<role_api::ParentGroupInfo>> {
-    let role_info = roles::RoleInfo::from_role_id_and_org_id(
+    let role_info = roles::RoleInfo::from_role_id_org_id_tenant_id(
         &state,
         &user_from_token.role_id,
         &user_from_token.org_id,
+        user_from_token
+            .tenant_id
+            .as_ref()
+            .unwrap_or(&state.tenant.tenant_id),
     )
     .await
     .to_not_found_response(UserErrors::InvalidRoleId)?;
@@ -123,6 +127,10 @@ pub async fn update_user_role(
         &req.role_id,
         &user_from_token.merchant_id,
         &user_from_token.org_id,
+        user_from_token
+            .tenant_id
+            .as_ref()
+            .unwrap_or(&state.tenant.tenant_id),
     )
     .await
     .to_not_found_response(UserErrors::InvalidRoleId)?;
@@ -143,10 +151,14 @@ pub async fn update_user_role(
             .attach_printable("User Changing their own role");
     }
 
-    let updator_role = roles::RoleInfo::from_role_id_and_org_id(
+    let updator_role = roles::RoleInfo::from_role_id_org_id_tenant_id(
         &state,
         &user_from_token.role_id,
         &user_from_token.org_id,
+        user_from_token
+            .tenant_id
+            .as_ref()
+            .unwrap_or(&state.tenant.tenant_id),
     )
     .await
     .change_context(UserErrors::InternalServerError)?;
@@ -179,10 +191,14 @@ pub async fn update_user_role(
     };
 
     if let Some(user_role) = v2_user_role_to_be_updated {
-        let role_to_be_updated = roles::RoleInfo::from_role_id_and_org_id(
+        let role_to_be_updated = roles::RoleInfo::from_role_id_org_id_tenant_id(
             &state,
             &user_role.role_id,
             &user_from_token.org_id,
+            user_from_token
+                .tenant_id
+                .as_ref()
+                .unwrap_or(&state.tenant.tenant_id),
         )
         .await
         .change_context(UserErrors::InternalServerError)?;
@@ -259,10 +275,14 @@ pub async fn update_user_role(
     };
 
     if let Some(user_role) = v1_user_role_to_be_updated {
-        let role_to_be_updated = roles::RoleInfo::from_role_id_and_org_id(
+        let role_to_be_updated = roles::RoleInfo::from_role_id_org_id_tenant_id(
             &state,
             &user_role.role_id,
             &user_from_token.org_id,
+            user_from_token
+                .tenant_id
+                .as_ref()
+                .unwrap_or(&state.tenant.tenant_id),
         )
         .await
         .change_context(UserErrors::InternalServerError)?;
@@ -485,10 +505,14 @@ pub async fn delete_user_role(
             .attach_printable("User deleting himself");
     }
 
-    let deletion_requestor_role_info = roles::RoleInfo::from_role_id_and_org_id(
+    let deletion_requestor_role_info = roles::RoleInfo::from_role_id_org_id_tenant_id(
         &state,
         &user_from_token.role_id,
         &user_from_token.org_id,
+        user_from_token
+            .tenant_id
+            .as_ref()
+            .unwrap_or(&state.tenant.tenant_id),
     )
     .await
     .change_context(UserErrors::InternalServerError)?;
@@ -527,6 +551,10 @@ pub async fn delete_user_role(
             &role_to_be_deleted.role_id,
             &user_from_token.merchant_id,
             &user_from_token.org_id,
+            user_from_token
+                .tenant_id
+                .as_ref()
+                .unwrap_or(&state.tenant.tenant_id),
         )
         .await
         .change_context(UserErrors::InternalServerError)?;
@@ -597,6 +625,10 @@ pub async fn delete_user_role(
             &role_to_be_deleted.role_id,
             &user_from_token.merchant_id,
             &user_from_token.org_id,
+            user_from_token
+                .tenant_id
+                .as_ref()
+                .unwrap_or(&state.tenant.tenant_id),
         )
         .await
         .change_context(UserErrors::InternalServerError)?;
@@ -680,10 +712,14 @@ pub async fn list_users_in_lineage(
     user_from_token: auth::UserFromToken,
     request: user_role_api::ListUsersInEntityRequest,
 ) -> UserResponse<Vec<user_role_api::ListUsersInEntityResponse>> {
-    let requestor_role_info = roles::RoleInfo::from_role_id_and_org_id(
+    let requestor_role_info = roles::RoleInfo::from_role_id_org_id_tenant_id(
         &state,
         &user_from_token.role_id,
         &user_from_token.org_id,
+        user_from_token
+            .tenant_id
+            .as_ref()
+            .unwrap_or(&state.tenant.tenant_id),
     )
     .await
     .change_context(UserErrors::InternalServerError)?;
@@ -692,7 +728,49 @@ pub async fn list_users_in_lineage(
         requestor_role_info.get_entity_type(),
         request.entity_type,
     )? {
-        EntityType::Tenant | EntityType::Organization => {
+        EntityType::Tenant => {
+            let mut org_users = utils::user_role::fetch_user_roles_by_payload(
+                &state,
+                ListUserRolesByOrgIdPayload {
+                    user_id: None,
+                    tenant_id: user_from_token
+                        .tenant_id
+                        .as_ref()
+                        .unwrap_or(&state.tenant.tenant_id),
+                    org_id: &user_from_token.org_id,
+                    merchant_id: None,
+                    profile_id: None,
+                    version: None,
+                    limit: None,
+                },
+                request.entity_type,
+            )
+            .await?;
+
+            // Fetch tenant user
+            let tenant_user = 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: None,
+                    merchant_id: None,
+                    profile_id: None,
+                    entity_id: None,
+                    version: None,
+                    status: None,
+                    limit: None,
+                })
+                .await
+                .change_context(UserErrors::InternalServerError)?;
+
+            org_users.extend(tenant_user);
+            org_users
+        }
+        EntityType::Organization => {
             utils::user_role::fetch_user_roles_by_payload(
                 &state,
                 ListUserRolesByOrgIdPayload {
@@ -777,10 +855,14 @@ pub async fn list_users_in_lineage(
 
     let role_info_map =
         futures::future::try_join_all(user_roles_set.iter().map(|user_role| async {
-            roles::RoleInfo::from_role_id_and_org_id(
+            roles::RoleInfo::from_role_id_org_id_tenant_id(
                 &state,
                 &user_role.role_id,
                 &user_from_token.org_id,
+                user_from_token
+                    .tenant_id
+                    .as_ref()
+                    .unwrap_or(&state.tenant.tenant_id),
             )
             .await
             .map(|role_info| {
diff --git a/crates/router/src/core/user_role/role.rs b/crates/router/src/core/user_role/role.rs
index e897e1b336a..ca4c062244c 100644
--- a/crates/router/src/core/user_role/role.rs
+++ b/crates/router/src/core/user_role/role.rs
@@ -73,16 +73,21 @@ pub async fn create_role(
         &role_name,
         &user_from_token.merchant_id,
         &user_from_token.org_id,
+        user_from_token
+            .tenant_id
+            .as_ref()
+            .unwrap_or(&state.tenant.tenant_id),
     )
     .await?;
 
     let user_role_info = user_from_token.get_role_info_from_db(&state).await?;
 
     if matches!(req.role_scope, RoleScope::Organization)
-        && user_role_info.get_entity_type() != EntityType::Organization
+        && user_role_info.get_entity_type() < EntityType::Organization
     {
-        return Err(report!(UserErrors::InvalidRoleOperation))
-            .attach_printable("Non org admin user creating org level role");
+        return Err(report!(UserErrors::InvalidRoleOperation)).attach_printable(
+            "User does not have sufficient privileges to perform organization-level role operation",
+        );
     }
 
     let role = state
@@ -99,6 +104,7 @@ pub async fn create_role(
             last_modified_by: user_from_token.user_id,
             created_at: now,
             last_modified_at: now,
+            tenant_id: user_from_token.tenant_id.unwrap_or(state.tenant.tenant_id),
         })
         .await
         .to_duplicate_response(UserErrors::RoleNameAlreadyExists)?;
@@ -118,10 +124,17 @@ pub async fn get_role_with_groups(
     user_from_token: UserFromToken,
     role: role_api::GetRoleRequest,
 ) -> UserResponse<role_api::RoleInfoWithGroupsResponse> {
-    let role_info =
-        roles::RoleInfo::from_role_id_and_org_id(&state, &role.role_id, &user_from_token.org_id)
-            .await
-            .to_not_found_response(UserErrors::InvalidRoleId)?;
+    let role_info = roles::RoleInfo::from_role_id_org_id_tenant_id(
+        &state,
+        &role.role_id,
+        &user_from_token.org_id,
+        user_from_token
+            .tenant_id
+            .as_ref()
+            .unwrap_or(&state.tenant.tenant_id),
+    )
+    .await
+    .to_not_found_response(UserErrors::InvalidRoleId)?;
 
     if role_info.is_internal() {
         return Err(UserErrors::InvalidRoleId.into());
@@ -142,10 +155,17 @@ pub async fn get_parent_info_for_role(
     user_from_token: UserFromToken,
     role: role_api::GetRoleRequest,
 ) -> UserResponse<role_api::RoleInfoWithParents> {
-    let role_info =
-        roles::RoleInfo::from_role_id_and_org_id(&state, &role.role_id, &user_from_token.org_id)
-            .await
-            .to_not_found_response(UserErrors::InvalidRoleId)?;
+    let role_info = roles::RoleInfo::from_role_id_org_id_tenant_id(
+        &state,
+        &role.role_id,
+        &user_from_token.org_id,
+        user_from_token
+            .tenant_id
+            .as_ref()
+            .unwrap_or(&state.tenant.tenant_id),
+    )
+    .await
+    .to_not_found_response(UserErrors::InvalidRoleId)?;
 
     if role_info.is_internal() {
         return Err(UserErrors::InvalidRoleId.into());
@@ -193,6 +213,10 @@ pub async fn update_role(
             role_name,
             &user_from_token.merchant_id,
             &user_from_token.org_id,
+            user_from_token
+                .tenant_id
+                .as_ref()
+                .unwrap_or(&state.tenant.tenant_id),
         )
         .await?;
     }
@@ -206,6 +230,10 @@ pub async fn update_role(
         role_id,
         &user_from_token.merchant_id,
         &user_from_token.org_id,
+        user_from_token
+            .tenant_id
+            .as_ref()
+            .unwrap_or(&state.tenant.tenant_id),
     )
     .await
     .to_not_found_response(UserErrors::InvalidRoleOperation)?;
@@ -273,6 +301,10 @@ pub async fn list_roles_with_info(
             EntityType::Tenant | EntityType::Organization => state
                 .global_store
                 .list_roles_for_org_by_parameters(
+                    user_from_token
+                        .tenant_id
+                        .as_ref()
+                        .unwrap_or(&state.tenant.tenant_id),
                     &user_from_token.org_id,
                     None,
                     request.entity_type,
@@ -284,6 +316,10 @@ pub async fn list_roles_with_info(
             EntityType::Merchant => state
                 .global_store
                 .list_roles_for_org_by_parameters(
+                    user_from_token
+                        .tenant_id
+                        .as_ref()
+                        .unwrap_or(&state.tenant.tenant_id),
                     &user_from_token.org_id,
                     Some(&user_from_token.merchant_id),
                     request.entity_type,
@@ -346,6 +382,10 @@ pub async fn list_roles_at_entity_level(
         EntityType::Tenant | EntityType::Organization => state
             .global_store
             .list_roles_for_org_by_parameters(
+                user_from_token
+                    .tenant_id
+                    .as_ref()
+                    .unwrap_or(&state.tenant.tenant_id),
                 &user_from_token.org_id,
                 None,
                 Some(req.entity_type),
@@ -358,6 +398,10 @@ pub async fn list_roles_at_entity_level(
         EntityType::Merchant => state
             .global_store
             .list_roles_for_org_by_parameters(
+                user_from_token
+                    .tenant_id
+                    .as_ref()
+                    .unwrap_or(&state.tenant.tenant_id),
                 &user_from_token.org_id,
                 Some(&user_from_token.merchant_id),
                 Some(req.entity_type),
diff --git a/crates/router/src/db/kafka_store.rs b/crates/router/src/db/kafka_store.rs
index 8eec7f04169..cbced16bdcd 100644
--- a/crates/router/src/db/kafka_store.rs
+++ b/crates/router/src/db/kafka_store.rs
@@ -3209,6 +3209,16 @@ impl UserRoleInterface for KafkaStore {
         self.diesel_store.list_user_roles_by_user_id(payload).await
     }
 
+    async fn list_user_roles_by_user_id_across_tenants(
+        &self,
+        user_id: &str,
+        limit: Option<u32>,
+    ) -> CustomResult<Vec<storage::UserRole>, errors::StorageError> {
+        self.diesel_store
+            .list_user_roles_by_user_id_across_tenants(user_id, limit)
+            .await
+    }
+
     async fn list_user_roles_by_org_id<'a>(
         &self,
         payload: ListUserRolesByOrgIdPayload<'a>,
@@ -3606,9 +3616,10 @@ impl RoleInterface for KafkaStore {
         role_id: &str,
         merchant_id: &id_type::MerchantId,
         org_id: &id_type::OrganizationId,
+        tenant_id: &id_type::TenantId,
     ) -> CustomResult<storage::Role, errors::StorageError> {
         self.diesel_store
-            .find_role_by_role_id_in_merchant_scope(role_id, merchant_id, org_id)
+            .find_role_by_role_id_in_merchant_scope(role_id, merchant_id, org_id, tenant_id)
             .await
     }
 
@@ -3617,19 +3628,21 @@ impl RoleInterface for KafkaStore {
         role_id: &str,
         merchant_id: &id_type::MerchantId,
         org_id: &id_type::OrganizationId,
+        tenant_id: &id_type::TenantId,
     ) -> CustomResult<storage::Role, errors::StorageError> {
         self.diesel_store
-            .find_role_by_role_id_in_lineage(role_id, merchant_id, org_id)
+            .find_role_by_role_id_in_lineage(role_id, merchant_id, org_id, tenant_id)
             .await
     }
 
-    async fn find_by_role_id_and_org_id(
+    async fn find_by_role_id_org_id_tenant_id(
         &self,
         role_id: &str,
         org_id: &id_type::OrganizationId,
+        tenant_id: &id_type::TenantId,
     ) -> CustomResult<storage::Role, errors::StorageError> {
         self.diesel_store
-            .find_by_role_id_and_org_id(role_id, org_id)
+            .find_by_role_id_org_id_tenant_id(role_id, org_id, tenant_id)
             .await
     }
 
@@ -3654,19 +3667,23 @@ impl RoleInterface for KafkaStore {
         &self,
         merchant_id: &id_type::MerchantId,
         org_id: &id_type::OrganizationId,
+        tenant_id: &id_type::TenantId,
     ) -> CustomResult<Vec<storage::Role>, errors::StorageError> {
-        self.diesel_store.list_all_roles(merchant_id, org_id).await
+        self.diesel_store
+            .list_all_roles(merchant_id, org_id, tenant_id)
+            .await
     }
 
     async fn list_roles_for_org_by_parameters(
         &self,
+        tenant_id: &id_type::TenantId,
         org_id: &id_type::OrganizationId,
         merchant_id: Option<&id_type::MerchantId>,
         entity_type: Option<enums::EntityType>,
         limit: Option<u32>,
     ) -> CustomResult<Vec<storage::Role>, errors::StorageError> {
         self.diesel_store
-            .list_roles_for_org_by_parameters(org_id, merchant_id, entity_type, limit)
+            .list_roles_for_org_by_parameters(tenant_id, org_id, merchant_id, entity_type, limit)
             .await
     }
 }
diff --git a/crates/router/src/db/role.rs b/crates/router/src/db/role.rs
index 877a4c54077..1006c33aaa0 100644
--- a/crates/router/src/db/role.rs
+++ b/crates/router/src/db/role.rs
@@ -29,6 +29,7 @@ pub trait RoleInterface {
         role_id: &str,
         merchant_id: &id_type::MerchantId,
         org_id: &id_type::OrganizationId,
+        tenant_id: &id_type::TenantId,
     ) -> CustomResult<storage::Role, errors::StorageError>;
 
     async fn find_role_by_role_id_in_lineage(
@@ -36,12 +37,14 @@ pub trait RoleInterface {
         role_id: &str,
         merchant_id: &id_type::MerchantId,
         org_id: &id_type::OrganizationId,
+        tenant_id: &id_type::TenantId,
     ) -> CustomResult<storage::Role, errors::StorageError>;
 
-    async fn find_by_role_id_and_org_id(
+    async fn find_by_role_id_org_id_tenant_id(
         &self,
         role_id: &str,
         org_id: &id_type::OrganizationId,
+        tenant_id: &id_type::TenantId,
     ) -> CustomResult<storage::Role, errors::StorageError>;
 
     async fn update_role_by_role_id(
@@ -59,10 +62,12 @@ pub trait RoleInterface {
         &self,
         merchant_id: &id_type::MerchantId,
         org_id: &id_type::OrganizationId,
+        tenant_id: &id_type::TenantId,
     ) -> CustomResult<Vec<storage::Role>, errors::StorageError>;
 
     async fn list_roles_for_org_by_parameters(
         &self,
+        tenant_id: &id_type::TenantId,
         org_id: &id_type::OrganizationId,
         merchant_id: Option<&id_type::MerchantId>,
         entity_type: Option<enums::EntityType>,
@@ -101,11 +106,18 @@ impl RoleInterface for Store {
         role_id: &str,
         merchant_id: &id_type::MerchantId,
         org_id: &id_type::OrganizationId,
+        tenant_id: &id_type::TenantId,
     ) -> CustomResult<storage::Role, errors::StorageError> {
         let conn = connection::pg_connection_read(self).await?;
-        storage::Role::find_by_role_id_in_merchant_scope(&conn, role_id, merchant_id, org_id)
-            .await
-            .map_err(|error| report!(errors::StorageError::from(error)))
+        storage::Role::find_by_role_id_in_merchant_scope(
+            &conn,
+            role_id,
+            merchant_id,
+            org_id,
+            tenant_id,
+        )
+        .await
+        .map_err(|error| report!(errors::StorageError::from(error)))
     }
 
     #[instrument(skip_all)]
@@ -114,21 +126,23 @@ impl RoleInterface for Store {
         role_id: &str,
         merchant_id: &id_type::MerchantId,
         org_id: &id_type::OrganizationId,
+        tenant_id: &id_type::TenantId,
     ) -> CustomResult<storage::Role, errors::StorageError> {
         let conn = connection::pg_connection_read(self).await?;
-        storage::Role::find_by_role_id_in_lineage(&conn, role_id, merchant_id, org_id)
+        storage::Role::find_by_role_id_in_lineage(&conn, role_id, merchant_id, org_id, tenant_id)
             .await
             .map_err(|error| report!(errors::StorageError::from(error)))
     }
 
     #[instrument(skip_all)]
-    async fn find_by_role_id_and_org_id(
+    async fn find_by_role_id_org_id_tenant_id(
         &self,
         role_id: &str,
         org_id: &id_type::OrganizationId,
+        tenant_id: &id_type::TenantId,
     ) -> CustomResult<storage::Role, errors::StorageError> {
         let conn = connection::pg_connection_read(self).await?;
-        storage::Role::find_by_role_id_and_org_id(&conn, role_id, org_id)
+        storage::Role::find_by_role_id_org_id_tenant_id(&conn, role_id, org_id, tenant_id)
             .await
             .map_err(|error| report!(errors::StorageError::from(error)))
     }
@@ -161,9 +175,10 @@ impl RoleInterface for Store {
         &self,
         merchant_id: &id_type::MerchantId,
         org_id: &id_type::OrganizationId,
+        tenant_id: &id_type::TenantId,
     ) -> CustomResult<Vec<storage::Role>, errors::StorageError> {
         let conn = connection::pg_connection_read(self).await?;
-        storage::Role::list_roles(&conn, merchant_id, org_id)
+        storage::Role::list_roles(&conn, merchant_id, org_id, tenant_id)
             .await
             .map_err(|error| report!(errors::StorageError::from(error)))
     }
@@ -171,6 +186,7 @@ impl RoleInterface for Store {
     #[instrument(skip_all)]
     async fn list_roles_for_org_by_parameters(
         &self,
+        tenant_id: &id_type::TenantId,
         org_id: &id_type::OrganizationId,
         merchant_id: Option<&id_type::MerchantId>,
         entity_type: Option<enums::EntityType>,
@@ -179,6 +195,7 @@ impl RoleInterface for Store {
         let conn = connection::pg_connection_read(self).await?;
         storage::Role::generic_roles_list_for_org(
             &conn,
+            tenant_id.to_owned(),
             org_id.to_owned(),
             merchant_id.cloned(),
             entity_type,
@@ -217,6 +234,7 @@ impl RoleInterface for MockDb {
             created_at: role.created_at,
             last_modified_at: role.last_modified_at,
             last_modified_by: role.last_modified_by,
+            tenant_id: role.tenant_id,
         };
         roles.push(role.clone());
         Ok(role)
@@ -245,12 +263,14 @@ impl RoleInterface for MockDb {
         role_id: &str,
         merchant_id: &id_type::MerchantId,
         org_id: &id_type::OrganizationId,
+        tenant_id: &id_type::TenantId,
     ) -> CustomResult<storage::Role, errors::StorageError> {
         let roles = self.roles.lock().await;
         roles
             .iter()
             .find(|role| {
                 role.role_id == role_id
+                    && (role.tenant_id == *tenant_id)
                     && (role.merchant_id == *merchant_id
                         || (role.org_id == *org_id && role.scope == enums::RoleScope::Organization))
             })
@@ -269,12 +289,14 @@ impl RoleInterface for MockDb {
         role_id: &str,
         merchant_id: &id_type::MerchantId,
         org_id: &id_type::OrganizationId,
+        tenant_id: &id_type::TenantId,
     ) -> CustomResult<storage::Role, errors::StorageError> {
         let roles = self.roles.lock().await;
         roles
             .iter()
             .find(|role| {
                 role.role_id == role_id
+                    && (role.tenant_id == *tenant_id)
                     && role.org_id == *org_id
                     && ((role.scope == enums::RoleScope::Organization)
                         || (role.merchant_id == *merchant_id
@@ -290,15 +312,18 @@ impl RoleInterface for MockDb {
             )
     }
 
-    async fn find_by_role_id_and_org_id(
+    async fn find_by_role_id_org_id_tenant_id(
         &self,
         role_id: &str,
         org_id: &id_type::OrganizationId,
+        tenant_id: &id_type::TenantId,
     ) -> CustomResult<storage::Role, errors::StorageError> {
         let roles = self.roles.lock().await;
         roles
             .iter()
-            .find(|role| role.role_id == role_id && role.org_id == *org_id)
+            .find(|role| {
+                role.role_id == role_id && role.org_id == *org_id && role.tenant_id == *tenant_id
+            })
             .cloned()
             .ok_or(
                 errors::StorageError::ValueNotFound(format!(
@@ -361,15 +386,17 @@ impl RoleInterface for MockDb {
         &self,
         merchant_id: &id_type::MerchantId,
         org_id: &id_type::OrganizationId,
+        tenant_id: &id_type::TenantId,
     ) -> CustomResult<Vec<storage::Role>, errors::StorageError> {
         let roles = self.roles.lock().await;
 
         let roles_list: Vec<_> = roles
             .iter()
             .filter(|role| {
-                role.merchant_id == *merchant_id
-                    || (role.org_id == *org_id
-                        && role.scope == diesel_models::enums::RoleScope::Organization)
+                role.tenant_id == *tenant_id
+                    && (role.merchant_id == *merchant_id
+                        || (role.org_id == *org_id
+                            && role.scope == diesel_models::enums::RoleScope::Organization))
             })
             .cloned()
             .collect();
@@ -388,6 +415,7 @@ impl RoleInterface for MockDb {
     #[instrument(skip_all)]
     async fn list_roles_for_org_by_parameters(
         &self,
+        tenant_id: &id_type::TenantId,
         org_id: &id_type::OrganizationId,
         merchant_id: Option<&id_type::MerchantId>,
         entity_type: Option<enums::EntityType>,
@@ -403,7 +431,10 @@ impl RoleInterface for MockDb {
                     None => true,
                 };
 
-                matches_merchant && role.org_id == *org_id && Some(role.entity_type) == entity_type
+                matches_merchant
+                    && role.org_id == *org_id
+                    && role.tenant_id == *tenant_id
+                    && Some(role.entity_type) == entity_type
             })
             .take(limit_usize)
             .cloned()
diff --git a/crates/router/src/db/user_role.rs b/crates/router/src/db/user_role.rs
index 0da51898326..1df6160f81e 100644
--- a/crates/router/src/db/user_role.rs
+++ b/crates/router/src/db/user_role.rs
@@ -79,6 +79,12 @@ pub trait UserRoleInterface {
         payload: ListUserRolesByUserIdPayload<'a>,
     ) -> CustomResult<Vec<storage::UserRole>, errors::StorageError>;
 
+    async fn list_user_roles_by_user_id_across_tenants(
+        &self,
+        user_id: &str,
+        limit: Option<u32>,
+    ) -> CustomResult<Vec<storage::UserRole>, errors::StorageError>;
+
     async fn list_user_roles_by_org_id<'a>(
         &self,
         payload: ListUserRolesByOrgIdPayload<'a>,
@@ -195,6 +201,21 @@ impl UserRoleInterface for Store {
         .map_err(|error| report!(errors::StorageError::from(error)))
     }
 
+    async fn list_user_roles_by_user_id_across_tenants(
+        &self,
+        user_id: &str,
+        limit: Option<u32>,
+    ) -> CustomResult<Vec<storage::UserRole>, errors::StorageError> {
+        let conn = connection::pg_connection_read(self).await?;
+        storage::UserRole::list_user_roles_by_user_id_across_tenants(
+            &conn,
+            user_id.to_owned(),
+            limit,
+        )
+        .await
+        .map_err(|error| report!(errors::StorageError::from(error)))
+    }
+
     async fn list_user_roles_by_org_id<'a>(
         &self,
         payload: ListUserRolesByOrgIdPayload<'a>,
@@ -472,6 +493,26 @@ impl UserRoleInterface for MockDb {
         Ok(filtered_roles)
     }
 
+    async fn list_user_roles_by_user_id_across_tenants(
+        &self,
+        user_id: &str,
+        limit: Option<u32>,
+    ) -> CustomResult<Vec<storage::UserRole>, errors::StorageError> {
+        let user_roles = self.user_roles.lock().await;
+
+        let filtered_roles: Vec<_> = user_roles
+            .iter()
+            .filter(|role| role.user_id == user_id)
+            .cloned()
+            .collect();
+
+        if let Some(Ok(limit)) = limit.map(|val| val.try_into()) {
+            return Ok(filtered_roles.into_iter().take(limit).collect());
+        }
+
+        Ok(filtered_roles)
+    }
+
     async fn list_user_roles_by_org_id<'a>(
         &self,
         payload: ListUserRolesByOrgIdPayload<'a>,
diff --git a/crates/router/src/services/authorization.rs b/crates/router/src/services/authorization.rs
index da296373d80..db3483f8164 100644
--- a/crates/router/src/services/authorization.rs
+++ b/crates/router/src/services/authorization.rs
@@ -33,7 +33,16 @@ where
         return Ok(role_info.clone());
     }
 
-    let role_info = get_role_info_from_db(state, &token.role_id, &token.org_id).await?;
+    let role_info = get_role_info_from_db(
+        state,
+        &token.role_id,
+        &token.org_id,
+        token
+            .tenant_id
+            .as_ref()
+            .unwrap_or(&state.session_state().tenant.tenant_id),
+    )
+    .await?;
 
     let token_expiry =
         i64::try_from(token.exp).change_context(ApiErrorResponse::InternalServerError)?;
@@ -68,6 +77,7 @@ async fn get_role_info_from_db<A>(
     state: &A,
     role_id: &str,
     org_id: &id_type::OrganizationId,
+    tenant_id: &id_type::TenantId,
 ) -> RouterResult<roles::RoleInfo>
 where
     A: SessionStateInfo + Sync,
@@ -75,7 +85,7 @@ where
     state
         .session_state()
         .global_store
-        .find_by_role_id_and_org_id(role_id, org_id)
+        .find_by_role_id_org_id_tenant_id(role_id, org_id, tenant_id)
         .await
         .map(roles::RoleInfo::from)
         .to_not_found_response(ApiErrorResponse::InvalidJwtToken)
diff --git a/crates/router/src/services/authorization/roles.rs b/crates/router/src/services/authorization/roles.rs
index c9c64b76143..df2a14a1a1a 100644
--- a/crates/router/src/services/authorization/roles.rs
+++ b/crates/router/src/services/authorization/roles.rs
@@ -121,29 +121,32 @@ impl RoleInfo {
         role_id: &str,
         merchant_id: &id_type::MerchantId,
         org_id: &id_type::OrganizationId,
+        tenant_id: &id_type::TenantId,
     ) -> CustomResult<Self, errors::StorageError> {
         if let Some(role) = predefined_roles::PREDEFINED_ROLES.get(role_id) {
             Ok(role.clone())
         } else {
             state
                 .global_store
-                .find_role_by_role_id_in_lineage(role_id, merchant_id, org_id)
+                .find_role_by_role_id_in_lineage(role_id, merchant_id, org_id, tenant_id)
                 .await
                 .map(Self::from)
         }
     }
 
-    pub async fn from_role_id_and_org_id(
+    // TODO: To evaluate whether we can omit org_id and tenant_id for this function
+    pub async fn from_role_id_org_id_tenant_id(
         state: &SessionState,
         role_id: &str,
         org_id: &id_type::OrganizationId,
+        tenant_id: &id_type::TenantId,
     ) -> CustomResult<Self, errors::StorageError> {
         if let Some(role) = predefined_roles::PREDEFINED_ROLES.get(role_id) {
             Ok(role.clone())
         } else {
             state
                 .global_store
-                .find_by_role_id_and_org_id(role_id, org_id)
+                .find_by_role_id_org_id_tenant_id(role_id, org_id, tenant_id)
                 .await
                 .map(Self::from)
         }
diff --git a/crates/router/src/utils/user.rs b/crates/router/src/utils/user.rs
index b8ffcf836d7..ef06531b421 100644
--- a/crates/router/src/utils/user.rs
+++ b/crates/router/src/utils/user.rs
@@ -77,9 +77,14 @@ impl UserFromToken {
     }
 
     pub async fn get_role_info_from_db(&self, state: &SessionState) -> UserResult<RoleInfo> {
-        RoleInfo::from_role_id_and_org_id(state, &self.role_id, &self.org_id)
-            .await
-            .change_context(UserErrors::InternalServerError)
+        RoleInfo::from_role_id_org_id_tenant_id(
+            state,
+            &self.role_id,
+            &self.org_id,
+            self.tenant_id.as_ref().unwrap_or(&state.tenant.tenant_id),
+        )
+        .await
+        .change_context(UserErrors::InternalServerError)
     }
 }
 
diff --git a/crates/router/src/utils/user_role.rs b/crates/router/src/utils/user_role.rs
index ac8ee11fc6a..7413e66070f 100644
--- a/crates/router/src/utils/user_role.rs
+++ b/crates/router/src/utils/user_role.rs
@@ -48,6 +48,7 @@ pub async fn validate_role_name(
     role_name: &domain::RoleName,
     merchant_id: &id_type::MerchantId,
     org_id: &id_type::OrganizationId,
+    tenant_id: &id_type::TenantId,
 ) -> UserResult<()> {
     let role_name_str = role_name.clone().get_role_name();
 
@@ -58,7 +59,7 @@ pub async fn validate_role_name(
     // TODO: Create and use find_by_role_name to make this efficient
     let is_present_in_custom_roles = state
         .global_store
-        .list_all_roles(merchant_id, org_id)
+        .list_all_roles(merchant_id, org_id, tenant_id)
         .await
         .change_context(UserErrors::InternalServerError)?
         .iter()
@@ -78,18 +79,24 @@ pub async fn set_role_info_in_cache_by_user_role(
     let Some(ref org_id) = user_role.org_id else {
         return false;
     };
-    set_role_info_in_cache_if_required(state, user_role.role_id.as_str(), org_id)
-        .await
-        .map_err(|e| logger::error!("Error setting permissions in cache {:?}", e))
-        .is_ok()
+    set_role_info_in_cache_if_required(
+        state,
+        user_role.role_id.as_str(),
+        org_id,
+        &user_role.tenant_id,
+    )
+    .await
+    .map_err(|e| logger::error!("Error setting permissions in cache {:?}", e))
+    .is_ok()
 }
 
 pub async fn set_role_info_in_cache_by_role_id_org_id(
     state: &SessionState,
     role_id: &str,
     org_id: &id_type::OrganizationId,
+    tenant_id: &id_type::TenantId,
 ) -> bool {
-    set_role_info_in_cache_if_required(state, role_id, org_id)
+    set_role_info_in_cache_if_required(state, role_id, org_id, tenant_id)
         .await
         .map_err(|e| logger::error!("Error setting permissions in cache {:?}", e))
         .is_ok()
@@ -99,15 +106,17 @@ pub async fn set_role_info_in_cache_if_required(
     state: &SessionState,
     role_id: &str,
     org_id: &id_type::OrganizationId,
+    tenant_id: &id_type::TenantId,
 ) -> UserResult<()> {
     if roles::predefined_roles::PREDEFINED_ROLES.contains_key(role_id) {
         return Ok(());
     }
 
-    let role_info = roles::RoleInfo::from_role_id_and_org_id(state, role_id, org_id)
-        .await
-        .change_context(UserErrors::InternalServerError)
-        .attach_printable("Error getting role_info from role_id")?;
+    let role_info =
+        roles::RoleInfo::from_role_id_org_id_tenant_id(state, role_id, org_id, tenant_id)
+            .await
+            .change_context(UserErrors::InternalServerError)
+            .attach_printable("Error getting role_info from role_id")?;
 
     authz::set_role_info_in_cache(
         state,
diff --git a/migrations/2024-12-28-121104_add_column_tenant_id_to_roles/down.sql b/migrations/2024-12-28-121104_add_column_tenant_id_to_roles/down.sql
new file mode 100644
index 00000000000..58d6b900e2d
--- /dev/null
+++ b/migrations/2024-12-28-121104_add_column_tenant_id_to_roles/down.sql
@@ -0,0 +1,2 @@
+-- This file should undo anything in `up.sql`
+ALTER TABLE roles DROP COLUMN IF EXISTS tenant_id;
\ No newline at end of file
diff --git a/migrations/2024-12-28-121104_add_column_tenant_id_to_roles/up.sql b/migrations/2024-12-28-121104_add_column_tenant_id_to_roles/up.sql
new file mode 100644
index 00000000000..13ec3cf48b7
--- /dev/null
+++ b/migrations/2024-12-28-121104_add_column_tenant_id_to_roles/up.sql
@@ -0,0 +1,2 @@
+-- Your SQL goes here
+ALTER TABLE roles ADD COLUMN IF NOT EXISTS tenant_id VARCHAR(64) NOT NULL DEFAULT 'public';
\ No newline at end of file