mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-26 19:04:36 +08:00
feat(user): create apis for custom role (#3763)
Co-authored-by: Mani Chandra Dulam <mani.dchandra@juspay.in> Co-authored-by: Mani Chandra <84711804+ThisIsMani@users.noreply.github.com>
This commit is contained in:
@ -1,8 +1,11 @@
|
||||
use common_utils::events::{ApiEventMetric, ApiEventsType};
|
||||
|
||||
use crate::user_role::{
|
||||
AcceptInvitationRequest, AuthorizationInfoResponse, DeleteUserRoleRequest, GetRoleRequest,
|
||||
ListRolesResponse, RoleInfoResponse, TransferOrgOwnershipRequest, UpdateUserRoleRequest,
|
||||
role::{
|
||||
CreateRoleRequest, GetRoleRequest, ListRolesResponse, RoleInfoResponse, UpdateRoleRequest,
|
||||
},
|
||||
AcceptInvitationRequest, AuthorizationInfoResponse, DeleteUserRoleRequest,
|
||||
TransferOrgOwnershipRequest, UpdateUserRoleRequest,
|
||||
};
|
||||
|
||||
common_utils::impl_misc_api_event_type!(
|
||||
@ -13,5 +16,7 @@ common_utils::impl_misc_api_event_type!(
|
||||
UpdateUserRoleRequest,
|
||||
AcceptInvitationRequest,
|
||||
DeleteUserRoleRequest,
|
||||
TransferOrgOwnershipRequest
|
||||
TransferOrgOwnershipRequest,
|
||||
CreateRoleRequest,
|
||||
UpdateRoleRequest
|
||||
);
|
||||
|
||||
@ -1,23 +1,8 @@
|
||||
use common_enums::RoleScope;
|
||||
use common_utils::pii;
|
||||
|
||||
use crate::user::DashboardEntryResponse;
|
||||
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
pub struct ListRolesResponse(pub Vec<RoleInfoResponse>);
|
||||
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
pub struct RoleInfoResponse {
|
||||
pub role_id: String,
|
||||
pub permissions: Vec<Permission>,
|
||||
pub role_name: String,
|
||||
pub role_scope: RoleScope,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct GetRoleRequest {
|
||||
pub role_id: String,
|
||||
}
|
||||
pub mod role;
|
||||
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
pub enum Permission {
|
||||
|
||||
30
crates/api_models/src/user_role/role.rs
Normal file
30
crates/api_models/src/user_role/role.rs
Normal file
@ -0,0 +1,30 @@
|
||||
use common_enums::{PermissionGroup, RoleScope};
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct CreateRoleRequest {
|
||||
pub role_name: String,
|
||||
pub groups: Vec<PermissionGroup>,
|
||||
pub role_scope: RoleScope,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct UpdateRoleRequest {
|
||||
pub groups: Option<Vec<PermissionGroup>>,
|
||||
pub role_name: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
pub struct ListRolesResponse(pub Vec<RoleInfoResponse>);
|
||||
|
||||
#[derive(Debug, serde::Serialize)]
|
||||
pub struct RoleInfoResponse {
|
||||
pub role_id: String,
|
||||
pub permissions: Vec<super::Permission>,
|
||||
pub role_name: String,
|
||||
pub role_scope: RoleScope,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct GetRoleRequest {
|
||||
pub role_id: String,
|
||||
}
|
||||
@ -46,35 +46,25 @@ pub struct RoleUpdateInternal {
|
||||
}
|
||||
|
||||
pub enum RoleUpdate {
|
||||
UpdateGroup {
|
||||
groups: Vec<enums::PermissionGroup>,
|
||||
last_modified_by: String,
|
||||
},
|
||||
UpdateRoleName {
|
||||
role_name: String,
|
||||
UpdateDetails {
|
||||
groups: Option<Vec<enums::PermissionGroup>>,
|
||||
role_name: Option<String>,
|
||||
last_modified_at: PrimitiveDateTime,
|
||||
last_modified_by: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<RoleUpdate> for RoleUpdateInternal {
|
||||
fn from(value: RoleUpdate) -> Self {
|
||||
let last_modified_at = common_utils::date_time::now();
|
||||
match value {
|
||||
RoleUpdate::UpdateGroup {
|
||||
RoleUpdate::UpdateDetails {
|
||||
groups,
|
||||
last_modified_by,
|
||||
} => Self {
|
||||
groups: Some(groups),
|
||||
role_name: None,
|
||||
last_modified_at,
|
||||
last_modified_by,
|
||||
},
|
||||
RoleUpdate::UpdateRoleName {
|
||||
role_name,
|
||||
last_modified_by,
|
||||
last_modified_at,
|
||||
} => Self {
|
||||
groups: None,
|
||||
role_name: Some(role_name),
|
||||
groups,
|
||||
role_name,
|
||||
last_modified_at,
|
||||
last_modified_by,
|
||||
},
|
||||
|
||||
@ -9,3 +9,4 @@ pub const ROLE_ID_MERCHANT_DEVELOPER: &str = "merchant_developer";
|
||||
pub const ROLE_ID_MERCHANT_OPERATOR: &str = "merchant_operator";
|
||||
pub const ROLE_ID_MERCHANT_CUSTOMER_SUPPORT: &str = "merchant_customer_support";
|
||||
pub const INTERNAL_USER_MERCHANT_ID: &str = "juspay000";
|
||||
pub const MAX_ROLE_NAME_LENGTH: usize = 64;
|
||||
|
||||
@ -62,6 +62,10 @@ pub enum UserErrors {
|
||||
RoleNotFound,
|
||||
#[error("InvalidRoleOperationWithMessage")]
|
||||
InvalidRoleOperationWithMessage(String),
|
||||
#[error("RoleNameParsingError")]
|
||||
RoleNameParsingError,
|
||||
#[error("RoleNameAlreadyExists")]
|
||||
RoleNameAlreadyExists,
|
||||
}
|
||||
|
||||
impl common_utils::errors::ErrorSwitch<api_models::errors::types::ApiErrorResponse> for UserErrors {
|
||||
@ -159,6 +163,12 @@ impl common_utils::errors::ErrorSwitch<api_models::errors::types::ApiErrorRespon
|
||||
Self::InvalidRoleOperationWithMessage(_) => {
|
||||
AER::BadRequest(ApiError::new(sub_code, 33, self.get_error_message(), None))
|
||||
}
|
||||
Self::RoleNameParsingError => {
|
||||
AER::BadRequest(ApiError::new(sub_code, 34, self.get_error_message(), None))
|
||||
}
|
||||
Self::RoleNameAlreadyExists => {
|
||||
AER::BadRequest(ApiError::new(sub_code, 35, self.get_error_message(), None))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -193,6 +203,8 @@ impl UserErrors {
|
||||
Self::MaxInvitationsError => "Maximum invite count per request exceeded",
|
||||
Self::RoleNotFound => "Role Not Found",
|
||||
Self::InvalidRoleOperationWithMessage(error_message) => error_message,
|
||||
Self::RoleNameParsingError => "Invalid Role Name",
|
||||
Self::RoleNameAlreadyExists => "Role name already exists",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,6 +17,8 @@ use crate::{
|
||||
utils,
|
||||
};
|
||||
|
||||
pub mod role;
|
||||
|
||||
pub async fn get_authorization_info(
|
||||
_state: AppState,
|
||||
) -> UserResponse<user_role_api::AuthorizationInfoResponse> {
|
||||
@ -30,98 +32,6 @@ pub async fn get_authorization_info(
|
||||
))
|
||||
}
|
||||
|
||||
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_roles_map.chain(custom_roles_map).collect(),
|
||||
)))
|
||||
}
|
||||
|
||||
pub async fn get_role(
|
||||
state: AppState,
|
||||
user_from_token: auth::UserFromToken,
|
||||
role: user_role_api::GetRoleRequest,
|
||||
) -> UserResponse<user_role_api::RoleInfoResponse> {
|
||||
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());
|
||||
}
|
||||
|
||||
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_from_token: auth::UserFromToken,
|
||||
) -> UserResponse<Vec<user_role_api::Permission>> {
|
||||
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(
|
||||
state: AppState,
|
||||
user_from_token: auth::UserFromToken,
|
||||
|
||||
223
crates/router/src/core/user_role/role.rs
Normal file
223
crates/router/src/core/user_role/role.rs
Normal file
@ -0,0 +1,223 @@
|
||||
use api_models::user_role::{
|
||||
role::{self as role_api},
|
||||
Permission,
|
||||
};
|
||||
use common_enums::RoleScope;
|
||||
use common_utils::generate_id_with_default_len;
|
||||
use diesel_models::role::{RoleNew, RoleUpdate};
|
||||
use error_stack::ResultExt;
|
||||
|
||||
use crate::{
|
||||
consts,
|
||||
core::errors::{StorageErrorExt, UserErrors, UserResponse},
|
||||
routes::AppState,
|
||||
services::{
|
||||
authentication::UserFromToken,
|
||||
authorization::roles::{self, predefined_roles::PREDEFINED_ROLES},
|
||||
ApplicationResponse,
|
||||
},
|
||||
types::domain::user::RoleName,
|
||||
utils,
|
||||
};
|
||||
|
||||
pub async fn get_role_from_token(
|
||||
state: AppState,
|
||||
user_from_token: UserFromToken,
|
||||
) -> UserResponse<Vec<Permission>> {
|
||||
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 create_role(
|
||||
state: AppState,
|
||||
user_from_token: UserFromToken,
|
||||
req: role_api::CreateRoleRequest,
|
||||
) -> UserResponse<()> {
|
||||
let now = common_utils::date_time::now();
|
||||
let role_name = RoleName::new(req.role_name)?.get_role_name();
|
||||
|
||||
if req.groups.is_empty() {
|
||||
return Err(UserErrors::InvalidRoleOperation.into())
|
||||
.attach_printable("Role groups cannot be empty");
|
||||
}
|
||||
|
||||
if matches!(req.role_scope, RoleScope::Organization)
|
||||
&& user_from_token.role_id != consts::user_role::ROLE_ID_ORGANIZATION_ADMIN
|
||||
{
|
||||
return Err(UserErrors::InvalidRoleOperation.into())
|
||||
.attach_printable("Non org admin user creating org level role");
|
||||
}
|
||||
|
||||
utils::user_role::is_role_name_already_present_for_merchant(
|
||||
&state,
|
||||
&role_name,
|
||||
&user_from_token.merchant_id,
|
||||
&user_from_token.org_id,
|
||||
)
|
||||
.await?;
|
||||
|
||||
state
|
||||
.store
|
||||
.insert_role(RoleNew {
|
||||
role_id: generate_id_with_default_len("role"),
|
||||
role_name,
|
||||
merchant_id: user_from_token.merchant_id,
|
||||
org_id: user_from_token.org_id,
|
||||
groups: req.groups,
|
||||
scope: req.role_scope,
|
||||
created_by: user_from_token.user_id.clone(),
|
||||
last_modified_by: user_from_token.user_id,
|
||||
created_at: now,
|
||||
last_modified_at: now,
|
||||
})
|
||||
.await
|
||||
.to_duplicate_response(UserErrors::RoleNameAlreadyExists)?;
|
||||
|
||||
Ok(ApplicationResponse::StatusOk)
|
||||
}
|
||||
|
||||
pub async fn list_invitable_roles(
|
||||
state: AppState,
|
||||
user_from_token: UserFromToken,
|
||||
) -> UserResponse<role_api::ListRolesResponse> {
|
||||
let predefined_roles_map = PREDEFINED_ROLES
|
||||
.iter()
|
||||
.filter(|(_, role_info)| role_info.is_invitable())
|
||||
.map(|(role_id, role_info)| 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| 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(role_api::ListRolesResponse(
|
||||
predefined_roles_map.chain(custom_roles_map).collect(),
|
||||
)))
|
||||
}
|
||||
|
||||
pub async fn get_role(
|
||||
state: AppState,
|
||||
user_from_token: UserFromToken,
|
||||
role: role_api::GetRoleRequest,
|
||||
) -> UserResponse<role_api::RoleInfoResponse> {
|
||||
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());
|
||||
}
|
||||
|
||||
let permissions = role_info
|
||||
.get_permissions_set()
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.collect();
|
||||
|
||||
Ok(ApplicationResponse::Json(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 update_role(
|
||||
state: AppState,
|
||||
user_from_token: UserFromToken,
|
||||
req: role_api::UpdateRoleRequest,
|
||||
role_id: &str,
|
||||
) -> UserResponse<()> {
|
||||
let role_name = req
|
||||
.role_name
|
||||
.map(RoleName::new)
|
||||
.transpose()?
|
||||
.map(RoleName::get_role_name);
|
||||
|
||||
if let Some(ref role_name) = role_name {
|
||||
utils::user_role::is_role_name_already_present_for_merchant(
|
||||
&state,
|
||||
role_name,
|
||||
&user_from_token.merchant_id,
|
||||
&user_from_token.org_id,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
let role_info = roles::get_role_info_from_role_id(
|
||||
&state,
|
||||
role_id,
|
||||
&user_from_token.merchant_id,
|
||||
&user_from_token.org_id,
|
||||
)
|
||||
.await
|
||||
.to_not_found_response(UserErrors::InvalidRoleOperation)?;
|
||||
|
||||
if matches!(role_info.get_scope(), RoleScope::Organization)
|
||||
&& user_from_token.role_id != consts::user_role::ROLE_ID_ORGANIZATION_ADMIN
|
||||
{
|
||||
return Err(UserErrors::InvalidRoleOperation.into())
|
||||
.attach_printable("Non org admin user creating org level role");
|
||||
}
|
||||
|
||||
if let Some(ref groups) = req.groups {
|
||||
if groups.is_empty() {
|
||||
return Err(UserErrors::InvalidRoleOperation.into())
|
||||
.attach_printable("role groups cannot be empty");
|
||||
}
|
||||
}
|
||||
|
||||
state
|
||||
.store
|
||||
.update_role_by_role_id(
|
||||
role_id,
|
||||
RoleUpdate::UpdateDetails {
|
||||
groups: req.groups,
|
||||
role_name,
|
||||
last_modified_at: common_utils::date_time::now(),
|
||||
last_modified_by: user_from_token.user_id,
|
||||
},
|
||||
)
|
||||
.await
|
||||
.to_duplicate_response(UserErrors::RoleNameAlreadyExists)?;
|
||||
|
||||
Ok(ApplicationResponse::StatusOk)
|
||||
}
|
||||
@ -200,29 +200,21 @@ impl RoleInterface for MockDb {
|
||||
role_update: storage::RoleUpdate,
|
||||
) -> CustomResult<storage::Role, errors::StorageError> {
|
||||
let mut roles = self.roles.lock().await;
|
||||
let last_modified_at = common_utils::date_time::now();
|
||||
|
||||
roles
|
||||
.iter_mut()
|
||||
.find(|role| role.role_id == role_id)
|
||||
.map(|role| {
|
||||
*role = match role_update {
|
||||
storage::RoleUpdate::UpdateGroup {
|
||||
storage::RoleUpdate::UpdateDetails {
|
||||
groups,
|
||||
last_modified_by,
|
||||
} => storage::Role {
|
||||
groups,
|
||||
last_modified_by,
|
||||
last_modified_at,
|
||||
..role.to_owned()
|
||||
},
|
||||
storage::RoleUpdate::UpdateRoleName {
|
||||
role_name,
|
||||
last_modified_by,
|
||||
} => storage::Role {
|
||||
role_name,
|
||||
last_modified_at,
|
||||
last_modified_by,
|
||||
} => storage::Role {
|
||||
groups: groups.unwrap_or(role.groups.to_owned()),
|
||||
role_name: role_name.unwrap_or(role.role_name.to_owned()),
|
||||
last_modified_by,
|
||||
last_modified_at,
|
||||
..role.to_owned()
|
||||
},
|
||||
};
|
||||
|
||||
@ -1052,9 +1052,17 @@ impl User {
|
||||
// Role information
|
||||
route = route.service(
|
||||
web::scope("/role")
|
||||
.service(web::resource("").route(web::get().to(get_role_from_token)))
|
||||
.service(
|
||||
web::resource("")
|
||||
.route(web::get().to(get_role_from_token))
|
||||
.route(web::post().to(create_role)),
|
||||
)
|
||||
.service(web::resource("/list").route(web::get().to(list_all_roles)))
|
||||
.service(web::resource("/{role_id}").route(web::get().to(get_role))),
|
||||
.service(
|
||||
web::resource("/{role_id}")
|
||||
.route(web::get().to(get_role))
|
||||
.route(web::put().to(update_role)),
|
||||
),
|
||||
);
|
||||
|
||||
#[cfg(feature = "dummy_connector")]
|
||||
|
||||
@ -29,6 +29,7 @@ pub enum ApiIdentifier {
|
||||
Forex,
|
||||
RustLockerMigration,
|
||||
Gsm,
|
||||
Role,
|
||||
User,
|
||||
UserRole,
|
||||
ConnectorOnboarding,
|
||||
@ -200,7 +201,9 @@ impl From<Flow> for ApiIdentifier {
|
||||
| Flow::GetAuthorizationInfo
|
||||
| Flow::AcceptInvitation
|
||||
| Flow::DeleteUserRole
|
||||
| Flow::TransferOrgOwnership => Self::UserRole,
|
||||
| Flow::TransferOrgOwnership
|
||||
| Flow::CreateRole
|
||||
| Flow::UpdateRole => Self::UserRole,
|
||||
|
||||
Flow::GetActionUrl | Flow::SyncOnboardingStatus | Flow::ResetTrackingId => {
|
||||
Self::ConnectorOnboarding
|
||||
|
||||
@ -7,7 +7,7 @@ use crate::{
|
||||
core::{api_locking, user_role as user_role_core},
|
||||
services::{
|
||||
api,
|
||||
authentication::{self as auth, UserFromToken},
|
||||
authentication::{self as auth},
|
||||
authorization::permissions::Permission,
|
||||
},
|
||||
};
|
||||
@ -29,6 +29,38 @@ pub async fn get_authorization_info(
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_role_from_token(state: web::Data<AppState>, req: HttpRequest) -> HttpResponse {
|
||||
let flow = Flow::GetRoleFromToken;
|
||||
Box::pin(api::server_wrap(
|
||||
flow,
|
||||
state.clone(),
|
||||
&req,
|
||||
(),
|
||||
|state, user, _| user_role_core::role::get_role_from_token(state, user),
|
||||
&auth::DashboardNoPermissionAuth,
|
||||
api_locking::LockAction::NotApplicable,
|
||||
))
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn create_role(
|
||||
state: web::Data<AppState>,
|
||||
req: HttpRequest,
|
||||
json_payload: web::Json<user_role_api::role::CreateRoleRequest>,
|
||||
) -> HttpResponse {
|
||||
let flow = Flow::CreateRole;
|
||||
Box::pin(api::server_wrap(
|
||||
flow,
|
||||
state.clone(),
|
||||
&req,
|
||||
json_payload.into_inner(),
|
||||
user_role_core::role::create_role,
|
||||
&auth::JWTAuth(Permission::UsersWrite),
|
||||
api_locking::LockAction::NotApplicable,
|
||||
))
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn list_all_roles(state: web::Data<AppState>, req: HttpRequest) -> HttpResponse {
|
||||
let flow = Flow::ListRoles;
|
||||
Box::pin(api::server_wrap(
|
||||
@ -36,7 +68,7 @@ pub async fn list_all_roles(state: web::Data<AppState>, req: HttpRequest) -> Htt
|
||||
state.clone(),
|
||||
&req,
|
||||
(),
|
||||
|state, user, _| user_role_core::list_invitable_roles(state, user),
|
||||
|state, user, _| user_role_core::role::list_invitable_roles(state, user),
|
||||
&auth::JWTAuth(Permission::UsersRead),
|
||||
api_locking::LockAction::NotApplicable,
|
||||
))
|
||||
@ -49,7 +81,7 @@ pub async fn get_role(
|
||||
path: web::Path<String>,
|
||||
) -> HttpResponse {
|
||||
let flow = Flow::GetRole;
|
||||
let request_payload = user_role_api::GetRoleRequest {
|
||||
let request_payload = user_role_api::role::GetRoleRequest {
|
||||
role_id: path.into_inner(),
|
||||
};
|
||||
Box::pin(api::server_wrap(
|
||||
@ -57,22 +89,29 @@ pub async fn get_role(
|
||||
state.clone(),
|
||||
&req,
|
||||
request_payload,
|
||||
user_role_core::get_role,
|
||||
user_role_core::role::get_role,
|
||||
&auth::JWTAuth(Permission::UsersRead),
|
||||
api_locking::LockAction::NotApplicable,
|
||||
))
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn get_role_from_token(state: web::Data<AppState>, req: HttpRequest) -> HttpResponse {
|
||||
let flow = Flow::GetRoleFromToken;
|
||||
pub async fn update_role(
|
||||
state: web::Data<AppState>,
|
||||
req: HttpRequest,
|
||||
json_payload: web::Json<user_role_api::role::UpdateRoleRequest>,
|
||||
path: web::Path<String>,
|
||||
) -> HttpResponse {
|
||||
let flow = Flow::UpdateRole;
|
||||
let role_id = path.into_inner();
|
||||
|
||||
Box::pin(api::server_wrap(
|
||||
flow,
|
||||
state.clone(),
|
||||
&req,
|
||||
(),
|
||||
|state, user: UserFromToken, _| user_role_core::get_role_from_token(state, user),
|
||||
&auth::DashboardNoPermissionAuth,
|
||||
json_payload.into_inner(),
|
||||
|state, user, req| user_role_core::role::update_role(state, user, req, &role_id),
|
||||
&auth::JWTAuth(Permission::UsersWrite),
|
||||
api_locking::LockAction::NotApplicable,
|
||||
))
|
||||
.await
|
||||
|
||||
@ -926,3 +926,23 @@ impl ForeignFrom<UserStatus> for user_role_api::UserStatus {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct RoleName(String);
|
||||
|
||||
impl RoleName {
|
||||
pub fn new(name: String) -> UserResult<Self> {
|
||||
let is_empty_or_whitespace = name.trim().is_empty();
|
||||
let is_too_long = name.graphemes(true).count() > consts::user_role::MAX_ROLE_NAME_LENGTH;
|
||||
|
||||
if is_empty_or_whitespace || is_too_long || name.contains(' ') {
|
||||
Err(UserErrors::RoleNameParsingError.into())
|
||||
} else {
|
||||
Ok(Self(name.to_lowercase()))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_role_name(self) -> String {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,11 @@
|
||||
use api_models::user_role as user_role_api;
|
||||
use error_stack::ResultExt;
|
||||
|
||||
use crate::services::authorization::permissions::Permission;
|
||||
use crate::{
|
||||
core::errors::{UserErrors, UserResult},
|
||||
routes::AppState,
|
||||
services::authorization::permissions::Permission,
|
||||
};
|
||||
|
||||
impl From<Permission> for user_role_api::Permission {
|
||||
fn from(value: Permission) -> Self {
|
||||
@ -34,3 +39,24 @@ impl From<Permission> for user_role_api::Permission {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn is_role_name_already_present_for_merchant(
|
||||
state: &AppState,
|
||||
role_name: &str,
|
||||
merchant_id: &str,
|
||||
org_id: &str,
|
||||
) -> UserResult<()> {
|
||||
let role_name_list: Vec<String> = state
|
||||
.store
|
||||
.list_all_roles(merchant_id, org_id)
|
||||
.await
|
||||
.change_context(UserErrors::InternalServerError)?
|
||||
.iter()
|
||||
.map(|role| role.role_name.to_owned())
|
||||
.collect();
|
||||
|
||||
if role_name_list.contains(&role_name.to_string()) {
|
||||
return Err(UserErrors::RoleNameAlreadyExists.into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -363,6 +363,10 @@ pub enum Flow {
|
||||
UpdateUserAccountDetails,
|
||||
/// Accept user invitation
|
||||
AcceptInvitation,
|
||||
/// Create Role
|
||||
CreateRole,
|
||||
/// Update Role
|
||||
UpdateRole,
|
||||
}
|
||||
|
||||
///
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
-- This file should undo anything in `up.sql`
|
||||
DROP INDEX role_name_org_id_org_scope_index;
|
||||
DROP INDEX role_name_merchant_id_merchant_scope_index;
|
||||
@ -0,0 +1,3 @@
|
||||
-- Your SQL goes here
|
||||
CREATE UNIQUE INDEX role_name_org_id_org_scope_index ON roles(org_id, role_name) WHERE scope='organization';
|
||||
CREATE UNIQUE INDEX role_name_merchant_id_merchant_scope_index ON roles(merchant_id, role_name) WHERE scope='merchant';
|
||||
Reference in New Issue
Block a user