Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions crates/api_models/src/events/user_role.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use common_utils::events::{ApiEventMetric, ApiEventsType};

use crate::user_role::{
AcceptInvitationRequest, AuthorizationInfoResponse, DeleteUserRoleRequest, GetRoleRequest,
ListRolesResponse, RoleInfoResponse, UpdateUserRoleRequest,
ListRolesResponse, RoleInfoResponse, TransferOrgOwnershipRequest, UpdateUserRoleRequest,
};

common_utils::impl_misc_api_event_type!(
Expand All @@ -12,5 +12,6 @@ common_utils::impl_misc_api_event_type!(
AuthorizationInfoResponse,
UpdateUserRoleRequest,
AcceptInvitationRequest,
DeleteUserRoleRequest
DeleteUserRoleRequest,
TransferOrgOwnershipRequest
);
5 changes: 5 additions & 0 deletions crates/api_models/src/user_role.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,8 @@ pub type AcceptInvitationResponse = DashboardEntryResponse;
pub struct DeleteUserRoleRequest {
pub email: pii::Email,
}

#[derive(Debug, serde::Deserialize, serde::Serialize)]
pub struct TransferOrgOwnershipRequest {
pub email: pii::Email,
}
14 changes: 14 additions & 0 deletions crates/diesel_models/src/query/user_role.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,20 @@ impl UserRole {
.await
}

pub async fn update_by_user_id_org_id(
conn: &PgPooledConn,
user_id: String,
org_id: String,
update: UserRoleUpdate,
) -> StorageResult<Vec<Self>> {
generics::generic_update_with_results::<<Self as HasTable>::Table, _, _, _>(
conn,
dsl::user_id.eq(user_id).and(dsl::org_id.eq(org_id)),
UserRoleUpdateInternal::from(update),
)
.await
}

pub async fn delete_by_user_id_merchant_id(
conn: &PgPooledConn,
user_id: String,
Expand Down
52 changes: 51 additions & 1 deletion crates/router/src/core/user_role.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use api_models::user_role as user_role_api;
use api_models::{user as user_api, user_role as user_role_api};
use diesel_models::{enums::UserStatus, user_role::UserRoleUpdate};
use error_stack::ResultExt;
use masking::ExposeInterface;
use router_env::logger;

use crate::{
consts,
core::errors::{StorageErrorExt, UserErrors, UserResponse},
routes::AppState,
services::{
Expand Down Expand Up @@ -135,6 +136,55 @@ pub async fn update_user_role(
Ok(ApplicationResponse::StatusOk)
}

pub async fn transfer_org_ownership(
state: AppState,
user_from_token: auth::UserFromToken,
req: user_role_api::TransferOrgOwnershipRequest,
) -> UserResponse<user_api::DashboardEntryResponse> {
if user_from_token.role_id != consts::user_role::ROLE_ID_ORGANIZATION_ADMIN {
return Err(UserErrors::InvalidRoleOperation.into()).attach_printable(format!(
"role_id = {} is not org_admin",
user_from_token.role_id
));
}

let user_to_be_updated =
utils::user::get_user_from_db_by_email(&state, domain::UserEmail::try_from(req.email)?)
.await
.to_not_found_response(UserErrors::InvalidRoleOperation)
.attach_printable("User not found in our records".to_string())?;

if user_from_token.user_id == user_to_be_updated.get_user_id() {
return Err(UserErrors::InvalidRoleOperation.into())
.attach_printable("User transferring ownership to themselves".to_string());
}

state
.store
.transfer_org_ownership_between_users(
&user_from_token.user_id,
user_to_be_updated.get_user_id(),
&user_from_token.org_id,
)
.await
.change_context(UserErrors::InternalServerError)?;

auth::blacklist::insert_user_in_blacklist(&state, user_to_be_updated.get_user_id()).await?;
auth::blacklist::insert_user_in_blacklist(&state, &user_from_token.user_id).await?;

let user_from_db = domain::UserFromStorage::from(user_from_token.get_user(&state).await?);
let user_role = user_from_db
.get_role_from_db_by_merchant_id(&state, &user_from_token.merchant_id)
.await
.to_not_found_response(UserErrors::InvalidRoleOperation)?;

let token = utils::user::generate_jwt_auth_token(&state, &user_from_db, &user_role).await?;

Ok(ApplicationResponse::Json(
utils::user::get_dashboard_entry_response(&state, user_from_db, user_role, token)?,
))
}

pub async fn accept_invitation(
state: AppState,
user_token: auth::UserWithoutMerchantFromToken,
Expand Down
22 changes: 22 additions & 0 deletions crates/router/src/db/kafka_store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1965,6 +1965,17 @@ impl UserRoleInterface for KafkaStore {
.await
}

async fn update_user_roles_by_user_id_org_id(
&self,
user_id: &str,
org_id: &str,
update: user_storage::UserRoleUpdate,
) -> CustomResult<Vec<user_storage::UserRole>, errors::StorageError> {
self.diesel_store
.update_user_roles_by_user_id_org_id(user_id, org_id, update)
.await
}

async fn delete_user_role_by_user_id_merchant_id(
&self,
user_id: &str,
Expand All @@ -1981,6 +1992,17 @@ impl UserRoleInterface for KafkaStore {
) -> CustomResult<Vec<user_storage::UserRole>, errors::StorageError> {
self.diesel_store.list_user_roles_by_user_id(user_id).await
}

async fn transfer_org_ownership_between_users(
&self,
from_user_id: &str,
to_user_id: &str,
org_id: &str,
) -> CustomResult<(), errors::StorageError> {
self.diesel_store
.transfer_org_ownership_between_users(from_user_id, to_user_id, org_id)
.await
}
}

#[async_trait::async_trait]
Expand Down
Loading