Bug: feat: change role apis to support custom roles




diff --git a/crates/api_models/src/user_role.rs b/crates/api_models/src/user_role.rs
index 215227b91a3..1c4c28aa993 100644
--- a/crates/api_models/src/user_role.rs
+++ b/crates/api_models/src/user_role.rs
@@ -1,3 +1,4 @@
+use common_enums::RoleScope;
 use common_utils::pii;
 
 use crate::user::DashboardEntryResponse;
@@ -7,9 +8,10 @@ pub struct ListRolesResponse(pub Vec<RoleInfoResponse>);
 
 #[derive(Debug, serde::Serialize)]
 pub struct RoleInfoResponse {
-    pub role_id: &'static str,
+    pub role_id: String,
     pub permissions: Vec<Permission>,
-    pub role_name: &'static str,
+    pub role_name: String,
+    pub role_scope: RoleScope,
 }
 
 #[derive(Debug, serde::Deserialize, serde::Serialize)]
diff --git a/crates/router/src/analytics.rs b/crates/router/src/analytics.rs
index d126d23c8ae..51fb6ff822b 100644
--- a/crates/router/src/analytics.rs
+++ b/crates/router/src/analytics.rs
@@ -497,7 +497,7 @@ pub mod routes {
                 .await
                 .map(ApplicationResponse::Json)
             },
-            &auth::JWTAuth(Permission::Analytics),
+            &auth::JWTAuth(Permission::PaymentWrite),
             api_locking::LockAction::NotApplicable,
         ))
         .await
diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs
index e7639af3918..5b634920cf6 100644
--- a/crates/router/src/core/user.rs
+++ b/crates/router/src/core/user.rs
@@ -18,10 +18,8 @@ use crate::services::email::types as email_types;
 use crate::{
     consts,
     routes::AppState,
-    services::{
-        authentication as auth, authorization::predefined_permissions, ApplicationResponse,
-    },
-    types::domain,
+    services::{authentication as auth, authorization::roles, ApplicationResponse},
+    types::{domain, transformers::ForeignInto},
     utils,
 };
 pub mod dashboard_metadata;
@@ -444,7 +442,16 @@ pub async fn invite_user(
         .into());
     }
 
-    if !predefined_permissions::is_role_invitable(request.role_id.as_str())? {
+    let role_info = roles::get_role_info_from_role_id(
+        &state,
+        &request.role_id,
+        &user_from_token.merchant_id,
+        &user_from_token.org_id,
+    )
+    .await
+    .to_not_found_response(UserErrors::InvalidRoleId)?;
+
+    if !role_info.is_invitable() {
         return Err(UserErrors::InvalidRoleId.into())
             .attach_printable(format!("role_id = {} is not invitable", request.role_id));
     }
@@ -626,7 +633,16 @@ async fn handle_invitation(
         .into());
     }
 
-    if !predefined_permissions::is_role_invitable(request.role_id.as_str())? {
+    let role_info = roles::get_role_info_from_role_id(
+        state,
+        &request.role_id,
+        &user_from_token.merchant_id,
+        &user_from_token.org_id,
+    )
+    .await
+    .to_not_found_response(UserErrors::InvalidRoleId)?;
+
+    if !role_info.is_invitable() {
         return Err(UserErrors::InvalidRoleId.into())
             .attach_printable(format!("role_id = {} is not invitable", request.role_id));
     }
@@ -915,20 +931,18 @@ pub async fn switch_merchant_id(
         .into());
     }
 
-    let user_roles = state
-        .store
-        .list_user_roles_by_user_id(&user_from_token.user_id)
-        .await
-        .change_context(UserErrors::InternalServerError)?;
-
-    let active_user_roles = user_roles
-        .into_iter()
-        .filter(|role| role.status == UserStatus::Active)
-        .collect::<Vec<_>>();
-
     let user = user_from_token.get_user_from_db(&state).await?;
 
-    let (token, role_id) = if utils::user_role::is_internal_role(&user_from_token.role_id) {
+    let role_info = roles::get_role_info_from_role_id(
+        &state,
+        &user_from_token.role_id,
+        &user_from_token.merchant_id,
+        &user_from_token.org_id,
+    )
+    .await
+    .to_not_found_response(UserErrors::InternalServerError)?;
+
+    let (token, role_id) = if role_info.is_internal() {
         let key_store = state
             .store
             .get_merchant_key_store_by_merchant_id(
@@ -967,6 +981,17 @@ pub async fn switch_merchant_id(
         .await?;
         (token, user_from_token.role_id)
     } else {
+        let user_roles = state
+            .store
+            .list_user_roles_by_user_id(&user_from_token.user_id)
+            .await
+            .change_context(UserErrors::InternalServerError)?;
+
+        let active_user_roles = user_roles
+            .into_iter()
+            .filter(|role| role.status == UserStatus::Active)
+            .collect::<Vec<_>>();
+
         let user_role = active_user_roles
             .iter()
             .find(|role| role.merchant_id == request.merchant_id)
@@ -1051,17 +1076,47 @@ pub async fn get_users_for_merchant_account(
     state: AppState,
     user_from_token: auth::UserFromToken,
 ) -> UserResponse<user_api::GetUsersResponse> {
-    let users = state
+    let users_and_user_roles = state
         .store
         .find_users_and_roles_by_merchant_id(user_from_token.merchant_id.as_str())
         .await
         .change_context(UserErrors::InternalServerError)
-        .attach_printable("No users for given merchant id")?
+        .attach_printable("No users for given merchant id")?;
+
+    let users_user_roles_and_roles =
+        futures::future::try_join_all(users_and_user_roles.into_iter().map(
+            |(user, user_role)| async {
+                roles::get_role_info_from_role_id(
+                    &state,
+                    &user_role.role_id,
+                    &user_role.merchant_id,
+                    &user_role.org_id,
+                )
+                .await
+                .map(|role_info| (user, user_role, role_info))
+                .to_not_found_response(UserErrors::InternalServerError)
+            },
+        ))
+        .await?;
+
+    let user_details_vec = users_user_roles_and_roles
         .into_iter()
-        .filter_map(|(user, role)| domain::UserAndRoleJoined(user, role).try_into().ok())
+        .map(|(user, user_role, role_info)| {
+            let user = domain::UserFromStorage::from(user);
+            user_api::UserDetails {
+                email: user.get_email(),
+                name: user.get_name(),
+                role_id: user_role.role_id,
+                role_name: role_info.get_role_name().to_string(),
+                status: user_role.status.foreign_into(),
+                last_modified_at: user_role.last_modified,
+            }
+        })
         .collect();
 
-    Ok(ApplicationResponse::Json(user_api::GetUsersResponse(users)))
+    Ok(ApplicationResponse::Json(user_api::GetUsersResponse(
+        user_details_vec,
+    )))
 }
 
 #[cfg(feature = "email")]
diff --git a/crates/router/src/core/user_role.rs b/crates/router/src/core/user_role.rs
index 16047bc3eb7..c2dfd34322c 100644
--- a/crates/router/src/core/user_role.rs
+++ b/crates/router/src/core/user_role.rs
@@ -10,7 +10,7 @@ use crate::{
     routes::AppState,
     services::{
         authentication::{self as auth},
-        authorization::{info, predefined_permissions},
+        authorization::{info, roles},
         ApplicationResponse,
     },
     types::domain,
@@ -30,58 +30,96 @@ pub async fn get_authorization_info(
     ))
 }
 
-pub async fn list_roles(_state: AppState) -> UserResponse<user_role_api::ListRolesResponse> {
+pub async fn list_invitable_roles(
+    state: AppState,
+    user_from_token: auth::UserFromToken,
+) -> UserResponse<user_role_api::ListRolesResponse> {
+    let predefined_roles_map = roles::predefined_roles::PREDEFINED_ROLES
+        .iter()
+        .filter(|(_, role_info)| role_info.is_invitable())
+        .map(|(role_id, role_info)| user_role_api::RoleInfoResponse {
+            permissions: role_info
+                .get_permissions_set()
+                .into_iter()
+                .map(Into::into)
+                .collect(),
+            role_id: role_id.to_string(),
+            role_name: role_info.get_role_name().to_string(),
+            role_scope: role_info.get_scope(),
+        });
+
+    let custom_roles_map = state
+        .store
+        .list_all_roles(&user_from_token.merchant_id, &user_from_token.org_id)
+        .await
+        .change_context(UserErrors::InternalServerError)?
+        .into_iter()
+        .map(roles::RoleInfo::from)
+        .filter(|role_info| role_info.is_invitable())
+        .map(|role_info| user_role_api::RoleInfoResponse {
+            permissions: role_info
+                .get_permissions_set()
+                .into_iter()
+                .map(Into::into)
+                .collect(),
+            role_id: role_info.get_role_id().to_string(),
+            role_name: role_info.get_role_name().to_string(),
+            role_scope: role_info.get_scope(),
+        });
+
     Ok(ApplicationResponse::Json(user_role_api::ListRolesResponse(
-        predefined_permissions::PREDEFINED_PERMISSIONS
-            .iter()
-            .filter(|(_, role_info)| role_info.is_invitable())
-            .filter_map(|(role_id, role_info)| {
-                utils::user_role::get_role_name_and_permission_response(role_info).map(
-                    |(permissions, role_name)| user_role_api::RoleInfoResponse {
-                        permissions,
-                        role_id,
-                        role_name,
-                    },
-                )
-            })
-            .collect(),
+        predefined_roles_map.chain(custom_roles_map).collect(),
     )))
 }
 
 pub async fn get_role(
-    _state: AppState,
+    state: AppState,
+    user_from_token: auth::UserFromToken,
     role: user_role_api::GetRoleRequest,
 ) -> UserResponse<user_role_api::RoleInfoResponse> {
-    let info = predefined_permissions::PREDEFINED_PERMISSIONS
-        .get_key_value(role.role_id.as_str())
-        .and_then(|(role_id, role_info)| {
-            utils::user_role::get_role_name_and_permission_response(role_info).map(
-                |(permissions, role_name)| user_role_api::RoleInfoResponse {
-                    permissions,
-                    role_id,
-                    role_name,
-                },
-            )
-        })
-        .ok_or(UserErrors::InvalidRoleId)?;
+    let role_info = roles::get_role_info_from_role_id(
+        &state,
+        &role.role_id,
+        &user_from_token.merchant_id,
+        &user_from_token.org_id,
+    )
+    .await
+    .to_not_found_response(UserErrors::InvalidRoleId)?;
+
+    if role_info.is_internal() {
+        return Err(UserErrors::InvalidRoleId.into());
+    }
 
-    Ok(ApplicationResponse::Json(info))
+    let permissions = role_info
+        .get_permissions_set()
+        .into_iter()
+        .map(Into::into)
+        .collect();
+
+    Ok(ApplicationResponse::Json(user_role_api::RoleInfoResponse {
+        permissions,
+        role_id: role.role_id,
+        role_name: role_info.get_role_name().to_string(),
+        role_scope: role_info.get_scope(),
+    }))
 }
 
 pub async fn get_role_from_token(
-    _state: AppState,
-    user: auth::UserFromToken,
+    state: AppState,
+    user_from_token: auth::UserFromToken,
 ) -> UserResponse<Vec<user_role_api::Permission>> {
-    Ok(ApplicationResponse::Json(
-        predefined_permissions::PREDEFINED_PERMISSIONS
-            .get(user.role_id.as_str())
-            .ok_or(UserErrors::InternalServerError.into())
-            .attach_printable("Invalid Role Id in JWT")?
-            .get_permissions()
-            .iter()
-            .map(|&per| per.into())
-            .collect(),
-    ))
+    let role_info = user_from_token
+        .get_role_info_from_db(&state)
+        .await
+        .attach_printable("Invalid role_id in JWT")?;
+
+    let permissions = role_info
+        .get_permissions_set()
+        .into_iter()
+        .map(Into::into)
+        .collect();
+
+    Ok(ApplicationResponse::Json(permissions))
 }
 
 pub async fn update_user_role(
@@ -89,7 +127,16 @@ pub async fn update_user_role(
     user_from_token: auth::UserFromToken,
     req: user_role_api::UpdateUserRoleRequest,
 ) -> UserResponse<()> {
-    if !predefined_permissions::is_role_updatable(&req.role_id)? {
+    let role_info = roles::get_role_info_from_role_id(
+        &state,
+        &req.role_id,
+        &user_from_token.merchant_id,
+        &user_from_token.org_id,
+    )
+    .await
+    .to_not_found_response(UserErrors::InvalidRoleId)?;
+
+    if !role_info.is_updatable() {
         return Err(UserErrors::InvalidRoleOperation.into())
             .attach_printable(format!("User role cannot be updated to {}", req.role_id));
     }
@@ -110,10 +157,19 @@ pub async fn update_user_role(
         .await
         .to_not_found_response(UserErrors::InvalidRoleOperation)?;
 
-    if !predefined_permissions::is_role_updatable(&user_role_to_be_updated.role_id)? {
+    let role_to_be_updated = roles::get_role_info_from_role_id(
+        &state,
+        &user_role_to_be_updated.role_id,
+        &user_from_token.merchant_id,
+        &user_from_token.org_id,
+    )
+    .await
+    .change_context(UserErrors::InternalServerError)?;
+
+    if !role_to_be_updated.is_updatable() {
         return Err(UserErrors::InvalidRoleOperation.into()).attach_printable(format!(
             "User role cannot be updated from {}",
-            user_role_to_be_updated.role_id
+            role_to_be_updated.get_role_id()
         ));
     }
 
@@ -270,7 +326,15 @@ pub async fn delete_user_role(
         .find(|&role| role.merchant_id == user_from_token.merchant_id.as_str())
     {
         Some(user_role) => {
-            if !predefined_permissions::is_role_deletable(&user_role.role_id)? {
+            let role_info = roles::get_role_info_from_role_id(
+                &state,
+                &user_role.role_id,
+                &user_from_token.merchant_id,
+                &user_from_token.org_id,
+            )
+            .await
+            .change_context(UserErrors::InternalServerError)?;
+            if !role_info.is_deletable() {
                 return Err(UserErrors::InvalidDeleteOperation.into())
                     .attach_printable(format!("role_id = {} is not deletable", user_role.role_id));
             }
diff --git a/crates/router/src/routes/user.rs b/crates/router/src/routes/user.rs
index a863bc2b662..ea51383c94b 100644
--- a/crates/router/src/routes/user.rs
+++ b/crates/router/src/routes/user.rs
@@ -291,7 +291,7 @@ pub async fn delete_sample_data(
         &http_req,
         payload.into_inner(),
         sample_data::delete_sample_data_for_user,
-        &auth::JWTAuth(Permission::PaymentWrite),
+        &auth::JWTAuth(Permission::MerchantAccountWrite),
         api_locking::LockAction::NotApplicable,
     ))
     .await
diff --git a/crates/router/src/routes/user_role.rs b/crates/router/src/routes/user_role.rs
index f84c158332b..63e2ce37269 100644
--- a/crates/router/src/routes/user_role.rs
+++ b/crates/router/src/routes/user_role.rs
@@ -36,7 +36,7 @@ pub async fn list_all_roles(state: web::Data<AppState>, req: HttpRequest) -> Htt
         state.clone(),
         &req,
         (),
-        |state, _: (), _| user_role_core::list_roles(state),
+        |state, user, _| user_role_core::list_invitable_roles(state, user),
         &auth::JWTAuth(Permission::UsersRead),
         api_locking::LockAction::NotApplicable,
     ))
@@ -57,7 +57,7 @@ pub async fn get_role(
         state.clone(),
         &req,
         request_payload,
-        |state, _: (), req| user_role_core::get_role(state, req),
+        user_role_core::get_role,
         &auth::JWTAuth(Permission::UsersRead),
         api_locking::LockAction::NotApplicable,
     ))
diff --git a/crates/router/src/services/authentication.rs b/crates/router/src/services/authentication.rs
index 1004982d292..34153ef6e8f 100644
--- a/crates/router/src/services/authentication.rs
+++ b/crates/router/src/services/authentication.rs
@@ -503,8 +503,8 @@ where
             return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
         }
 
-        let permissions = authorization::get_permissions(&payload.role_id)?;
-        authorization::check_authorization(&self.0, permissions)?;
+        let permissions = authorization::get_permissions(state, &payload).await?;
+        authorization::check_authorization(&self.0, &permissions)?;
 
         Ok((
             (),
@@ -532,8 +532,8 @@ where
             return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
         }
 
-        let permissions = authorization::get_permissions(&payload.role_id)?;
-        authorization::check_authorization(&self.0, permissions)?;
+        let permissions = authorization::get_permissions(state, &payload).await?;
+        authorization::check_authorization(&self.0, &permissions)?;
 
         Ok((
             UserFromToken {
@@ -570,8 +570,8 @@ where
             return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
         }
 
-        let permissions = authorization::get_permissions(&payload.role_id)?;
-        authorization::check_authorization(&self.required_permission, permissions)?;
+        let permissions = authorization::get_permissions(state, &payload).await?;
+        authorization::check_authorization(&self.required_permission, &permissions)?;
 
         // Check if token has access to MerchantId that has been requested through query param
         if payload.merchant_id != self.merchant_id {
@@ -613,8 +613,8 @@ where
             return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
         }
 
-        let permissions = authorization::get_permissions(&payload.role_id)?;
-        authorization::check_authorization(&self.0, permissions)?;
+        let permissions = authorization::get_permissions(state, &payload).await?;
+        authorization::check_authorization(&self.0, &permissions)?;
 
         let key_store = state
             .store()
@@ -663,8 +663,8 @@ where
             return Err(errors::ApiErrorResponse::InvalidJwtToken.into());
         }
 
-        let permissions = authorization::get_permissions(&payload.role_id)?;
-        authorization::check_authorization(&self.0, permissions)?;
+        let permissions = authorization::get_permissions(state, &payload).await?;
+        authorization::check_authorization(&self.0, &permissions)?;
 
         let key_store = state
             .store()
diff --git a/crates/router/src/services/authorization.rs b/crates/router/src/services/authorization.rs
index cad9b1ece62..034773a07ea 100644
--- a/crates/router/src/services/authorization.rs
+++ b/crates/router/src/services/authorization.rs
@@ -1,14 +1,50 @@
-use crate::core::errors::{ApiErrorResponse, RouterResult};
+use common_enums::PermissionGroup;
+
+use super::authentication::AuthToken;
+use crate::{
+    core::errors::{ApiErrorResponse, RouterResult, StorageErrorExt},
+    routes::app::AppStateInfo,
+};
 
 pub mod info;
+pub mod permission_groups;
 pub mod permissions;
-pub mod predefined_permissions;
+pub mod roles;
+
+pub async fn get_permissions<A>(
+    state: &A,
+    token: &AuthToken,
+) -> RouterResult<Vec<permissions::Permission>>
+where
+    A: AppStateInfo + Sync,
+{
+    if let Some(role_info) = roles::predefined_roles::PREDEFINED_ROLES.get(token.role_id.as_str()) {
+        Ok(get_permissions_from_groups(
+            role_info.get_permission_groups(),
+        ))
+    } else {
+        state
+            .store()
+            .find_role_by_role_id_in_merchant_scope(
+                &token.role_id,
+                &token.merchant_id,
+                &token.org_id,
+            )
+            .await
+            .map(|role| get_permissions_from_groups(&role.groups))
+            .to_not_found_response(ApiErrorResponse::InvalidJwtToken)
+    }
+}
 
-pub fn get_permissions(role: &str) -> RouterResult<&Vec<permissions::Permission>> {
-    predefined_permissions::PREDEFINED_PERMISSIONS
-        .get(role)
-        .map(|role_info| role_info.get_permissions())
-        .ok_or(ApiErrorResponse::InvalidJwtToken.into())
+pub fn get_permissions_from_groups(groups: &[PermissionGroup]) -> Vec<permissions::Permission> {
+    groups
+        .iter()
+        .flat_map(|group| {
+            permission_groups::get_permissions_vec(group)
+                .iter()
+                .cloned()
+        })
+        .collect()
 }
 
 pub fn check_authorization(
diff --git a/crates/router/src/services/authorization/permission_groups.rs b/crates/router/src/services/authorization/permission_groups.rs
new file mode 100644
index 00000000000..246a4ce91dc
--- /dev/null
+++ b/crates/router/src/services/authorization/permission_groups.rs
@@ -0,0 +1,86 @@
+use common_enums::PermissionGroup;
+
+use super::permissions::Permission;
+
+pub fn get_permissions_vec(permission_group: &PermissionGroup) -> &[Permission] {
+    match permission_group {
+        PermissionGroup::OperationsView => &OPERATIONS_VIEW,
+        PermissionGroup::OperationsManage => &OPERATIONS_MANAGE,
+        PermissionGroup::ConnectorsView => &CONNECTORS_VIEW,
+        PermissionGroup::ConnectorsManage => &CONNECTORS_MANAGE,
+        PermissionGroup::WorkflowsView => &WORKFLOWS_VIEW,
+        PermissionGroup::WorkflowsManage => &WORKFLOWS_MANAGE,
+        PermissionGroup::AnalyticsView => &ANALYTICS_VIEW,
+        PermissionGroup::UsersView => &USERS_VIEW,
+        PermissionGroup::UsersManage => &USERS_MANAGE,
+        PermissionGroup::MerchantDetailsView => &MERCHANT_DETAILS_VIEW,
+        PermissionGroup::MerchantDetailsManage => &MERCHANT_DETAILS_MANAGE,
+        PermissionGroup::OrganizationManage => &ORGANIZATION_MANAGE,
+    }
+}
+
+pub static OPERATIONS_VIEW: [Permission; 6] = [
+    Permission::PaymentRead,
+    Permission::RefundRead,
+    Permission::MandateRead,
+    Permission::DisputeRead,
+    Permission::CustomerRead,
+    Permission::MerchantAccountRead,
+];
+
+pub static OPERATIONS_MANAGE: [Permission; 6] = [
+    Permission::PaymentWrite,
+    Permission::RefundWrite,
+    Permission::MandateWrite,
+    Permission::DisputeWrite,
+    Permission::CustomerWrite,
+    Permission::MerchantAccountRead,
+];
+
+pub static CONNECTORS_VIEW: [Permission; 2] = [
+    Permission::MerchantConnectorAccountRead,
+    Permission::MerchantAccountRead,
+];
+
+pub static CONNECTORS_MANAGE: [Permission; 2] = [
+    Permission::MerchantConnectorAccountWrite,
+    Permission::MerchantAccountRead,
+];
+
+pub static WORKFLOWS_VIEW: [Permission; 5] = [
+    Permission::RoutingRead,
+    Permission::ThreeDsDecisionManagerRead,
+    Permission::SurchargeDecisionManagerRead,
+    Permission::MerchantConnectorAccountRead,
+    Permission::MerchantAccountRead,
+];
+
+pub static WORKFLOWS_MANAGE: [Permission; 5] = [
+    Permission::RoutingWrite,
+    Permission::ThreeDsDecisionManagerWrite,
+    Permission::SurchargeDecisionManagerWrite,
+    Permission::MerchantConnectorAccountRead,
+    Permission::MerchantAccountRead,
+];
+
+pub static ANALYTICS_VIEW: [Permission; 2] =
+    [Permission::Analytics, Permission::MerchantAccountRead];
+
+pub static USERS_VIEW: [Permission; 2] = [Permission::UsersRead, Permission::MerchantAccountRead];
+
+pub static USERS_MANAGE: [Permission; 2] =
+    [Permission::UsersWrite, Permission::MerchantAccountRead];
+
+pub static MERCHANT_DETAILS_VIEW: [Permission; 1] = [Permission::MerchantAccountRead];
+
+pub static MERCHANT_DETAILS_MANAGE: [Permission; 4] = [
+    Permission::MerchantAccountWrite,
+    Permission::ApiKeyRead,
+    Permission::ApiKeyWrite,
+    Permission::MerchantAccountRead,
+];
+
+pub static ORGANIZATION_MANAGE: [Permission; 2] = [
+    Permission::MerchantAccountCreate,
+    Permission::MerchantAccountRead,
+];
diff --git a/crates/router/src/services/authorization/permissions.rs b/crates/router/src/services/authorization/permissions.rs
index 3e022e8f666..8d436618b32 100644
--- a/crates/router/src/services/authorization/permissions.rs
+++ b/crates/router/src/services/authorization/permissions.rs
@@ -1,6 +1,6 @@
 use strum::Display;
 
-#[derive(PartialEq, Display, Clone, Debug, Copy)]
+#[derive(PartialEq, Display, Clone, Debug, Copy, Eq, Hash)]
 pub enum Permission {
     PaymentRead,
     PaymentWrite,
diff --git a/crates/router/src/services/authorization/roles.rs b/crates/router/src/services/authorization/roles.rs
new file mode 100644
index 00000000000..6372799f98c
--- /dev/null
+++ b/crates/router/src/services/authorization/roles.rs
@@ -0,0 +1,100 @@
+use std::collections::HashSet;
+
+use common_enums::{PermissionGroup, RoleScope};
+use common_utils::errors::CustomResult;
+
+use super::{permission_groups::get_permissions_vec, permissions::Permission};
+use crate::{core::errors, routes::AppState};
+
+pub mod predefined_roles;
+
+#[derive(Clone)]
+pub struct RoleInfo {
+    role_id: String,
+    role_name: String,
+    groups: Vec<PermissionGroup>,
+    scope: RoleScope,
+    is_invitable: bool,
+    is_deletable: bool,
+    is_updatable: bool,
+    is_internal: bool,
+}
+
+impl RoleInfo {
+    pub fn get_role_id(&self) -> &str {
+        &self.role_id
+    }
+
+    pub fn get_role_name(&self) -> &str {
+        &self.role_name
+    }
+
+    pub fn get_permission_groups(&self) -> &Vec<PermissionGroup> {
+        &self.groups
+    }
+
+    pub fn get_scope(&self) -> RoleScope {
+        self.scope
+    }
+
+    pub fn is_invitable(&self) -> bool {
+        self.is_invitable
+    }
+
+    pub fn is_deletable(&self) -> bool {
+        self.is_deletable
+    }
+
+    pub fn is_internal(&self) -> bool {
+        self.is_internal
+    }
+
+    pub fn is_updatable(&self) -> bool {
+        self.is_updatable
+    }
+
+    pub fn get_permissions_set(&self) -> HashSet<Permission> {
+        self.groups
+            .iter()
+            .flat_map(|group| get_permissions_vec(group).iter().copied())
+            .collect()
+    }
+
+    pub fn check_permission_exists(&self, required_permission: &Permission) -> bool {
+        self.groups
+            .iter()
+            .any(|group| get_permissions_vec(group).contains(required_permission))
+    }
+}
+
+pub async fn get_role_info_from_role_id(
+    state: &AppState,
+    role_id: &str,
+    merchant_id: &str,
+    org_id: &str,
+) -> CustomResult<RoleInfo, errors::StorageError> {
+    if let Some(role) = predefined_roles::PREDEFINED_ROLES.get(role_id) {
+        Ok(role.clone())
+    } else {
+        state
+            .store
+            .find_role_by_role_id_in_merchant_scope(role_id, merchant_id, org_id)
+            .await
+            .map(RoleInfo::from)
+    }
+}
+
+impl From<diesel_models::role::Role> for RoleInfo {
+    fn from(role: diesel_models::role::Role) -> Self {
+        Self {
+            role_id: role.role_id,
+            role_name: role.role_name,
+            groups: role.groups.into_iter().map(Into::into).collect(),
+            scope: role.scope,
+            is_invitable: true,
+            is_deletable: true,
+            is_updatable: true,
+            is_internal: false,
+        }
+    }
+}
diff --git a/crates/router/src/services/authorization/roles/predefined_roles.rs b/crates/router/src/services/authorization/roles/predefined_roles.rs
new file mode 100644
index 00000000000..9accf094ea4
--- /dev/null
+++ b/crates/router/src/services/authorization/roles/predefined_roles.rs
@@ -0,0 +1,210 @@
+use std::collections::HashMap;
+
+use common_enums::{PermissionGroup, RoleScope};
+use once_cell::sync::Lazy;
+
+use super::RoleInfo;
+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,
+        RoleInfo {
+            groups: vec![
+                PermissionGroup::OperationsView,
+                PermissionGroup::OperationsManage,
+                PermissionGroup::ConnectorsView,
+                PermissionGroup::ConnectorsManage,
+                PermissionGroup::WorkflowsView,
+                PermissionGroup::WorkflowsManage,
+                PermissionGroup::AnalyticsView,
+                PermissionGroup::UsersView,
+                PermissionGroup::UsersManage,
+                PermissionGroup::MerchantDetailsView,
+                PermissionGroup::MerchantDetailsManage,
+                PermissionGroup::OrganizationManage,
+            ],
+            role_id: consts::user_role::ROLE_ID_INTERNAL_ADMIN.to_string(),
+            role_name: "Internal Admin".to_string(),
+            scope: RoleScope::Organization,
+            is_invitable: false,
+            is_deletable: false,
+            is_updatable: false,
+            is_internal: true,
+        },
+    );
+    roles.insert(
+        consts::user_role::ROLE_ID_INTERNAL_VIEW_ONLY_USER,
+        RoleInfo {
+            groups: vec![
+                PermissionGroup::OperationsView,
+                PermissionGroup::ConnectorsView,
+                PermissionGroup::WorkflowsView,
+                PermissionGroup::AnalyticsView,
+                PermissionGroup::UsersView,
+                PermissionGroup::MerchantDetailsView,
+            ],
+            role_id: consts::user_role::ROLE_ID_INTERNAL_VIEW_ONLY_USER.to_string(),
+            role_name: "Internal View Only".to_string(),
+            scope: RoleScope::Organization,
+            is_invitable: false,
+            is_deletable: false,
+            is_updatable: false,
+            is_internal: true,
+        },
+    );
+
+    roles.insert(
+        consts::user_role::ROLE_ID_ORGANIZATION_ADMIN,
+        RoleInfo {
+            groups: vec![
+                PermissionGroup::OperationsView,
+                PermissionGroup::OperationsManage,
+                PermissionGroup::ConnectorsView,
+                PermissionGroup::ConnectorsManage,
+                PermissionGroup::WorkflowsView,
+                PermissionGroup::WorkflowsManage,
+                PermissionGroup::AnalyticsView,
+                PermissionGroup::UsersView,
+                PermissionGroup::UsersManage,
+                PermissionGroup::MerchantDetailsView,
+                PermissionGroup::MerchantDetailsManage,
+                PermissionGroup::OrganizationManage,
+            ],
+            role_id: consts::user_role::ROLE_ID_ORGANIZATION_ADMIN.to_string(),
+            role_name: "Organization Admin".to_string(),
+            scope: RoleScope::Organization,
+            is_invitable: false,
+            is_deletable: false,
+            is_updatable: false,
+            is_internal: false,
+        },
+    );
+
+    // MERCHANT ROLES
+    roles.insert(
+        consts::user_role::ROLE_ID_MERCHANT_ADMIN,
+        RoleInfo {
+            groups: vec![
+                PermissionGroup::OperationsView,
+                PermissionGroup::OperationsManage,
+                PermissionGroup::ConnectorsView,
+                PermissionGroup::ConnectorsManage,
+                PermissionGroup::WorkflowsView,
+                PermissionGroup::WorkflowsManage,
+                PermissionGroup::AnalyticsView,
+                PermissionGroup::UsersView,
+                PermissionGroup::UsersManage,
+                PermissionGroup::MerchantDetailsView,
+                PermissionGroup::MerchantDetailsManage,
+            ],
+            role_id: consts::user_role::ROLE_ID_MERCHANT_ADMIN.to_string(),
+            role_name: "Admin".to_string(),
+            scope: RoleScope::Organization,
+            is_invitable: true,
+            is_deletable: true,
+            is_updatable: true,
+            is_internal: false,
+        },
+    );
+    roles.insert(
+        consts::user_role::ROLE_ID_MERCHANT_VIEW_ONLY,
+        RoleInfo {
+            groups: vec![
+                PermissionGroup::OperationsView,
+                PermissionGroup::ConnectorsView,
+                PermissionGroup::WorkflowsView,
+                PermissionGroup::AnalyticsView,
+                PermissionGroup::UsersView,
+                PermissionGroup::MerchantDetailsView,
+            ],
+            role_id: consts::user_role::ROLE_ID_MERCHANT_VIEW_ONLY.to_string(),
+            role_name: "View Only".to_string(),
+            scope: RoleScope::Organization,
+            is_invitable: true,
+            is_deletable: true,
+            is_updatable: true,
+            is_internal: false,
+        },
+    );
+    roles.insert(
+        consts::user_role::ROLE_ID_MERCHANT_IAM_ADMIN,
+        RoleInfo {
+            groups: vec![
+                PermissionGroup::OperationsView,
+                PermissionGroup::AnalyticsView,
+                PermissionGroup::UsersView,
+                PermissionGroup::UsersManage,
+                PermissionGroup::MerchantDetailsView,
+            ],
+            role_id: consts::user_role::ROLE_ID_MERCHANT_IAM_ADMIN.to_string(),
+            role_name: "IAM".to_string(),
+            scope: RoleScope::Organization,
+            is_invitable: true,
+            is_deletable: true,
+            is_updatable: true,
+            is_internal: false,
+        },
+    );
+    roles.insert(
+        consts::user_role::ROLE_ID_MERCHANT_DEVELOPER,
+        RoleInfo {
+            groups: vec![
+                PermissionGroup::OperationsView,
+                PermissionGroup::ConnectorsView,
+                PermissionGroup::AnalyticsView,
+                PermissionGroup::UsersView,
+                PermissionGroup::MerchantDetailsView,
+                PermissionGroup::MerchantDetailsManage,
+            ],
+            role_id: consts::user_role::ROLE_ID_MERCHANT_DEVELOPER.to_string(),
+            role_name: "Developer".to_string(),
+            scope: RoleScope::Organization,
+            is_invitable: true,
+            is_deletable: true,
+            is_updatable: true,
+            is_internal: false,
+        },
+    );
+    roles.insert(
+        consts::user_role::ROLE_ID_MERCHANT_OPERATOR,
+        RoleInfo {
+            groups: vec![
+                PermissionGroup::OperationsView,
+                PermissionGroup::OperationsManage,
+                PermissionGroup::ConnectorsView,
+                PermissionGroup::WorkflowsView,
+                PermissionGroup::AnalyticsView,
+                PermissionGroup::UsersView,
+                PermissionGroup::MerchantDetailsView,
+            ],
+            role_id: consts::user_role::ROLE_ID_MERCHANT_OPERATOR.to_string(),
+            role_name: "Operator".to_string(),
+            scope: RoleScope::Organization,
+            is_invitable: true,
+            is_deletable: true,
+            is_updatable: true,
+            is_internal: false,
+        },
+    );
+    roles.insert(
+        consts::user_role::ROLE_ID_MERCHANT_CUSTOMER_SUPPORT,
+        RoleInfo {
+            groups: vec![
+                PermissionGroup::OperationsView,
+                PermissionGroup::AnalyticsView,
+                PermissionGroup::UsersView,
+                PermissionGroup::MerchantDetailsView,
+            ],
+            role_id: consts::user_role::ROLE_ID_MERCHANT_CUSTOMER_SUPPORT.to_string(),
+            role_name: "Customer Support".to_string(),
+            scope: RoleScope::Organization,
+            is_invitable: true,
+            is_deletable: true,
+            is_updatable: true,
+            is_internal: false,
+        },
+    );
+    roles
+});
diff --git a/crates/router/src/types/domain/user.rs b/crates/router/src/types/domain/user.rs
index ab32febf9b4..263b0e52b8a 100644
--- a/crates/router/src/types/domain/user.rs
+++ b/crates/router/src/types/domain/user.rs
@@ -25,11 +25,7 @@ use crate::{
     },
     db::StorageInterface,
     routes::AppState,
-    services::{
-        authentication as auth,
-        authentication::UserFromToken,
-        authorization::{info, predefined_permissions},
-    },
+    services::{authentication as auth, authentication::UserFromToken, authorization::info},
     types::transformers::ForeignFrom,
     utils::{self, user::password},
 };
@@ -824,32 +820,6 @@ impl From<info::PermissionInfo> for user_role_api::PermissionInfo {
     }
 }
 
-pub struct UserAndRoleJoined(pub storage_user::User, pub UserRole);
-
-impl TryFrom<UserAndRoleJoined> for user_api::UserDetails {
-    type Error = ();
-    fn try_from(user_and_role: UserAndRoleJoined) -> Result<Self, Self::Error> {
-        let status = match user_and_role.1.status {
-            UserStatus::Active => user_role_api::UserStatus::Active,
-            UserStatus::InvitationSent => user_role_api::UserStatus::InvitationSent,
-        };
-
-        let role_id = user_and_role.1.role_id;
-        let role_name = predefined_permissions::get_role_name_from_id(role_id.as_str())
-            .ok_or(())?
-            .to_string();
-
-        Ok(Self {
-            email: user_and_role.0.email,
-            name: user_and_role.0.name,
-            role_id,
-            status,
-            role_name,
-            last_modified_at: user_and_role.0.last_modified_at,
-        })
-    }
-}
-
 pub enum SignInWithRoleStrategyType {
     SingleRole(SignInWithSingleRoleStrategy),
     MultipleRoles(SignInWithMultipleRolesStrategy),
@@ -947,3 +917,12 @@ impl SignInWithMultipleRolesStrategy {
         ))
     }
 }
+
+impl ForeignFrom<UserStatus> for user_role_api::UserStatus {
+    fn foreign_from(value: UserStatus) -> Self {
+        match value {
+            UserStatus::Active => Self::Active,
+            UserStatus::InvitationSent => Self::InvitationSent,
+        }
+    }
+}
diff --git a/crates/router/src/utils/user.rs b/crates/router/src/utils/user.rs
index 86b298822ac..e9b7143a26e 100644
--- a/crates/router/src/utils/user.rs
+++ b/crates/router/src/utils/user.rs
@@ -9,7 +9,10 @@ use masking::{ExposeInterface, Secret};
 use crate::{
     core::errors::{StorageError, UserErrors, UserResult},
     routes::AppState,
-    services::authentication::{AuthToken, UserFromToken},
+    services::{
+        authentication::{AuthToken, UserFromToken},
+        authorization::roles::{self, RoleInfo},
+    },
     types::domain::{self, MerchantAccount, UserFromStorage},
 };
 
@@ -19,7 +22,10 @@ pub mod password;
 pub mod sample_data;
 
 impl UserFromToken {
-    pub async fn get_merchant_account(&self, state: AppState) -> UserResult<MerchantAccount> {
+    pub async fn get_merchant_account_from_db(
+        &self,
+        state: AppState,
+    ) -> UserResult<MerchantAccount> {
         let key_store = state
             .store
             .get_merchant_key_store_by_merchant_id(
@@ -56,6 +62,12 @@ impl UserFromToken {
             .change_context(UserErrors::InternalServerError)?;
         Ok(user.into())
     }
+
+    pub async fn get_role_info_from_db(&self, state: &AppState) -> UserResult<RoleInfo> {
+        roles::get_role_info_from_role_id(state, &self.role_id, &self.merchant_id, &self.org_id)
+            .await
+            .change_context(UserErrors::InternalServerError)
+    }
 }
 
 pub async fn generate_jwt_auth_token(
diff --git a/crates/router/src/utils/user_role.rs b/crates/router/src/utils/user_role.rs
index b677e89269e..ef69219b4c9 100644
--- a/crates/router/src/utils/user_role.rs
+++ b/crates/router/src/utils/user_role.rs
@@ -1,29 +1,6 @@
 use api_models::user_role as user_role_api;
 
-use crate::{
-    consts,
-    services::authorization::{permissions::Permission, predefined_permissions::RoleInfo},
-};
-
-pub fn is_internal_role(role_id: &str) -> bool {
-    role_id == consts::user_role::ROLE_ID_INTERNAL_ADMIN
-        || role_id == consts::user_role::ROLE_ID_INTERNAL_VIEW_ONLY_USER
-}
-
-pub fn get_role_name_and_permission_response(
-    role_info: &RoleInfo,
-) -> Option<(Vec<user_role_api::Permission>, &'static str)> {
-    role_info.get_name().map(|name| {
-        (
-            role_info
-                .get_permissions()
-                .iter()
-                .map(|&per| per.into())
-                .collect::<Vec<user_role_api::Permission>>(),
-            name,
-        )
-    })
-}
+use crate::services::authorization::permissions::Permission;
 
 impl From<Permission> for user_role_api::Permission {
     fn from(value: Permission) -> Self {

## 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 will add checks in authorization to check for custom roles.
This PR also add custom roles functionality to apis which were dealing with roles.

### 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).
-->
To support custom roles.
Closes #3717 
Closes #3718 

## 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)?
-->
### Roles
| Group | Org Admin | Merchant Admin | Merchant View Only | Merchant IAM Admin | Merchant Developer | Merchant Operator | Merchant Customer Support |
| ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- |
| Operations View | &check; | &check; | &check; | &check; | &check; | &check; | &check; |
| Operations Manage | &check; | &check; |  |  |  | &check; |  |
| Connectors View | &check; | &check; | &check; |  | &check; | &check; |  |
| Connectors Manage | &check; | &check; |  |  |  |  |  |
| Workflows View | &check; | &check; | &check; |  |  | &check; |  |
| Workflows Manage | &check; | &check; |  |  |  |  |  |
| Analytics View | &check; | &check; | &check; | &check; | &check; | &check; | &check; |
| Users View | &check; | &check; | &check; | &check; | &check; | &check; | &check; |
| Users Manage | &check; | &check; |  | &check; |  |  |  |
| Merchant Details View | &check; | &check; | &check; | &check; | &check; | &check; | &check; |
| MerchantDetails Manage | &check; | &check; |  |  | &check; |  |  |
| Organization Manage | &check; |  |  |  |  |  |  |

### Groups
| Permission | Operations View | Operations Manage | Connectors View | Connectors Manage | Workflows View | Workflows Manage | Analytics View | Users View | Users Manage | Merchant Details View | Merchant Details Manage | Organization Manage |
| ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- | ---- |
| Payment Read | &check; |  |  |  |  |  |  |  |  |  |  |  |
| Payment Write |  | &check; |  |  |  |  |  |  |  |  |  |  |
| Refund Read | &check; |  |  |  |  |  |  |  |  |  |  |  |
| Refund Write |  | &check; |  |  |  |  |  |  |  |  |  |  |
| Dispute Read | &check; |  |  |  |  |  |  |  |  |  |  |  |
| Dispute Write |  | &check; |  |  |  |  |  |  |  |  |  |  |
| Mandate Read | &check; |  |  |  |  |  |  |  |  |  |  |  |
| Mandate Write |  | &check; |  |  |  |  |  |  |  |  |  |  |
| Customer Read | &check; |  |  |  |  |  |  |  |  |  |  |  |
| Customer Write |  | &check; |  |  |  |  |  |  |  |  |  |  |
| Api Key Read |  |  |  |  |  |  |  |  |  |  | &check; |  |
| Api Key Write |  |  |  |  |  |  |  |  |  |  | &check; |  |
| Merchant Account Read | &check; | &check; | &check; | &check; | &check; | &check; | &check; | &check; | &check; | &check; | &check; | &check; |
| Merchant Account Write |  |  |  |  |  |  |  |  |  |  | &check; |  |
| Merchant Connector Account Read |  |  | &check; |  | &check; | &check; |  |  |  |  |  |  |
| Merchant Connector Account Write |  |  |  | &check; |  |  |  |  |  |  |  |  |
| Routing Read |  |  |  |  | &check; |  |  |  |  |  |  |  |
| Routing Write |  |  |  |  |  | &check; |  |  |  |  |  |  |
| Analytics |  |  |  |  |  |  | &check; |  |  |  |  |  |
| 3DS Manager Read |  |  |  |  | &check; |  |  |  |  |  |  |  |
| 3DS Manger Write |  |  |  |  |  | &check; |  |  |  |  |  |  |
| Surcharge Decision Manager Read |  |  |  |  | &check; |  |  |  |  |  |  |  |
| Surcharge Decision Manager Write |  |  |  |  |  | &check; |  |  |  |  |  |  |
| Users Read |  |  |  |  |  |  |  | &check; |  |  |  |  |
| Users Write |  |  |  |  |  |  |  |  | &check; |  |  |  |
| Merchant Account Create |  |  |  |  |  |  |  |  |  |  |  | &check; |

All the APIs will perform according to these roles from now on.
----
```
curl --location 'http://localhost:8080/user/role/list' \
--header 'Authorization: Bearer JWT'
```
```
[
    {
        "role_id": "merchant_developer",
        "permissions": [
            "CustomerRead",
            "MerchantAccountRead",
            "Analytics",
            "UsersRead",
            "PaymentRead",
            "MerchantAccountWrite",
            "MerchantConnectorAccountRead",
            "ApiKeyRead",
            "MandateRead",
            "RefundRead",
            "DisputeRead",
            "ApiKeyWrite"
        ],
        "role_name": "Developer",
        "role_scope": "organization"
    },
    {
        "role_id": "merchant_operator",
        "permissions": [
            "DisputeWrite",
            "RefundWrite",
            "SurchargeDecisionManagerRead",
            "CustomerWrite",
            "DisputeRead",
            "MandateWrite",
            "MerchantConnectorAccountRead",
            "MerchantAccountRead",
            "PaymentWrite",
            "MandateRead",
            "RefundRead",
            "PaymentRead",
            "RoutingRead",
            "ThreeDsDecisionManagerRead",
            "Analytics",
            "UsersRead",
            "CustomerRead"
        ],
        "role_name": "Operator",
        "role_scope": "organization"
    },
    {
        "role_id": "merchant_iam_admin",
        "permissions": [
            "PaymentRead",
            "MerchantAccountRead",
            "Analytics",
            "DisputeRead",
            "UsersRead",
            "MandateRead",
            "UsersWrite",
            "RefundRead",
            "CustomerRead"
        ],
        "role_name": "IAM",
        "role_scope": "organization"
    },
    {
        "role_id": "merchant_view_only",
        "permissions": [
            "MerchantConnectorAccountRead",
            "SurchargeDecisionManagerRead",
            "UsersRead",
            "Analytics",
            "ThreeDsDecisionManagerRead",
            "MandateRead",
            "DisputeRead",
            "MerchantAccountRead",
            "RoutingRead",
            "CustomerRead",
            "RefundRead",
            "PaymentRead"
        ],
        "role_name": "View Only",
        "role_scope": "organization"
    },
    {
        "role_id": "merchant_customer_support",
        "permissions": [
            "RefundRead",
            "UsersRead",
            "Analytics",
            "CustomerRead",
            "MandateRead",
            "DisputeRead",
            "MerchantAccountRead",
            "PaymentRead"
        ],
        "role_name": "Customer Support",
        "role_scope": "organization"
    },
    {
        "role_id": "merchant_admin",
        "permissions": [
            "Analytics",
            "RoutingRead",
            "ApiKeyRead",
            "MerchantAccountRead",
            "MandateWrite",
            "RefundWrite",
            "SurchargeDecisionManagerRead",
            "ThreeDsDecisionManagerWrite",
            "CustomerRead",
            "MerchantAccountWrite",
            "ApiKeyWrite",
            "ThreeDsDecisionManagerRead",
            "PaymentWrite",
            "MerchantConnectorAccountRead",
            "PaymentRead",
            "MerchantConnectorAccountWrite",
            "UsersRead",
            "UsersWrite",
            "DisputeRead",
            "MandateRead",
            "DisputeWrite",
            "CustomerWrite",
            "RoutingWrite",
            "RefundRead",
            "SurchargeDecisionManagerWrite"
        ],
        "role_name": "Admin",
        "role_scope": "organization"
    }
]
```
---

```
curl --location 'http://localhost:8080/user/role/role_id' \
--header 'Authorization: Bearer JWT'
```

```
{
    "role_id": "rold_id",
    "permissions": [ permissions as mentioned above ],
    "role_name": "name",
    "role_scope": "organization"
}
```
## 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
- [ ] I added a [CHANGELOG](/CHANGELOG.md) entry if applicable
