diff --git a/dashboard/src/pages/Dashboard/UserManagment.tsx b/dashboard/src/pages/Dashboard/UserManagment.tsx
index 524daa6ebf..a05a96a805 100644
--- a/dashboard/src/pages/Dashboard/UserManagment.tsx
+++ b/dashboard/src/pages/Dashboard/UserManagment.tsx
@@ -9,6 +9,7 @@ import {
import { InviteUserModal } from "../../components/InviteUserModal";
import { EditUserModal } from "../../components/EditUserModal";
import { createToast } from "../../components/ShowToasts";
+import { FaRegularTrashCan } from "solid-icons/fa";
export const UserManagement = () => {
const apiHost = import.meta.env.VITE_API_HOST as unknown as string;
@@ -96,6 +97,46 @@ export const UserManagement = () => {
});
};
+ const removeUser = (id: string) => {
+ const confirm = window.confirm(
+ "Are you sure you want to remove this user?",
+ );
+ if (!confirm) {
+ return;
+ }
+
+ fetch(
+ `${apiHost}/organization/${
+ userContext.selectedOrganizationId?.() as string
+ }/user/${id}`,
+ {
+ method: "DELETE",
+ headers: {
+ "TR-Organization": userContext.selectedOrganizationId?.() as string,
+ },
+ credentials: "include",
+ },
+ )
+ .then((res) => {
+ if (res.ok) {
+ getUsers();
+ createToast({
+ title: "Success",
+ type: "success",
+ message: "User removed successfully!",
+ });
+ }
+ })
+ .catch((err) => {
+ console.error(err);
+ createToast({
+ title: "Error",
+ type: "error",
+ message: "Error removing user!",
+ });
+ });
+ };
+
const deleteInvitation = (id: string) => {
fetch(`${apiHost}/invitation/${id}`, {
method: "DELETE",
@@ -228,10 +269,16 @@ export const UserManagement = () => {
Edit
|
+
+ Delete
+ |
@@ -247,7 +294,7 @@ export const UserManagement = () => {
{fromI32ToUserRole(user.user_orgs[0].role) as string}
|
-
+ |
|
+
+
+ |
)}
diff --git a/server/src/handlers/organization_handler.rs b/server/src/handlers/organization_handler.rs
index 91d37a637c..12a6fe2881 100644
--- a/server/src/handlers/organization_handler.rs
+++ b/server/src/handlers/organization_handler.rs
@@ -6,7 +6,7 @@ use crate::{
create_organization_query, delete_organization_query, get_org_from_id_query,
get_org_usage_by_id_query, get_org_users_by_id_query, update_organization_query,
},
- user_operator::add_user_to_organization,
+ user_operator::{add_user_to_organization, remove_user_from_org_query},
},
};
use actix_web::{web, HttpRequest, HttpResponse};
@@ -255,3 +255,42 @@ pub async fn get_organization_users(
Ok(HttpResponse::Ok().json(usage))
}
+
+#[derive(Serialize, Deserialize, Debug, Clone, ToSchema)]
+pub struct RemoveUserFromOrgData {
+ /// The id of the organization to remove the user from.
+ organization_id: uuid::Uuid,
+ /// The id of the user to remove from the organization.
+ user_id: uuid::Uuid,
+}
+
+/// Remove User From Organization
+///
+/// Remove a user from an organization. The auth'ed user must be an admin or owner of the organization to remove a user.
+#[utoipa::path(
+ delete,
+ path = "/organization/{organization_id}/user/{user_id}",
+ context_path = "/api",
+ tag = "organization",
+ responses(
+ (status = 204, description = "Confirmation that the user was removed from the organization"),
+ (status = 400, description = "Service error relating to removing the user from the organization", body = ErrorResponseBody),
+ ),
+ params(
+ ("TR-Organization" = String, Header, description = "The organization id to use for the request"),
+ ("user_id" = Option, Path, description = "The id of the user you want to remove from the organization."),
+ ),
+ security(
+ ("ApiKey" = ["readonly"]),
+ )
+)]
+pub async fn remove_user_from_org(
+ data: web::Path,
+ pool: web::Data,
+ redis_pool: web::Data,
+ _admin: OwnerOnly,
+) -> Result {
+ remove_user_from_org_query(data.user_id, data.organization_id, pool, redis_pool).await?;
+
+ Ok(HttpResponse::NoContent().finish())
+}
diff --git a/server/src/lib.rs b/server/src/lib.rs
index 94f3d0fc0b..67af399aab 100644
--- a/server/src/lib.rs
+++ b/server/src/lib.rs
@@ -782,6 +782,10 @@ pub fn main() -> std::io::Result<()> {
handlers::organization_handler::get_organization_users,
)),
)
+ .service(
+ web::resource("/{organization_id}/user/{user_id}")
+ .route(web::delete().to(handlers::organization_handler::remove_user_from_org)),
+ )
.service(
web::resource("/{organization_id}")
.route(
diff --git a/server/src/operators/user_operator.rs b/server/src/operators/user_operator.rs
index 5f47633a0b..e9c02553e5 100644
--- a/server/src/operators/user_operator.rs
+++ b/server/src/operators/user_operator.rs
@@ -559,3 +559,33 @@ pub async fn create_default_user(api_key: &str, pool: web::Data) -> Result
Ok(())
}
+
+pub async fn remove_user_from_org_query(
+ user_id: uuid::Uuid,
+ organization_id: uuid::Uuid,
+ pool: web::Data,
+ redis_pool: web::Data,
+) -> Result<(), ServiceError> {
+ use crate::data::schema::user_organizations::dsl as user_organizations_columns;
+
+ let mut conn = pool.get().await.unwrap();
+
+ diesel::delete(
+ user_organizations_columns::user_organizations
+ .filter(user_organizations_columns::user_id.eq(user_id))
+ .filter(user_organizations_columns::organization_id.eq(organization_id)),
+ )
+ .execute(&mut conn)
+ .await
+ .map_err(|_| ServiceError::BadRequest("Error removing user from org".to_string()))?;
+
+ let mut redis_conn = redis_pool.get().await.map_err(|_| {
+ ServiceError::InternalServerError("Failed to get redis connection".to_string())
+ })?;
+
+ redis_conn.del(user_id.to_string()).await.map_err(|_| {
+ ServiceError::InternalServerError("Failed to delete user from redis".to_string())
+ })?;
+
+ Ok(())
+}