From ed6306747ab43b22fbdb094012c71be57e93021a Mon Sep 17 00:00:00 2001 From: Sheen Capadngan Date: Tue, 18 Mar 2025 03:00:54 +0800 Subject: [PATCH] feat: add support for removing instance admin permission from identity --- backend/src/server/routes/v1/admin-router.ts | 36 +++++++ .../super-admin/super-admin-service.ts | 19 +++- frontend/src/hooks/api/admin/index.ts | 1 + frontend/src/hooks/api/admin/mutation.ts | 18 ++++ .../OverviewPage/components/IdentityPanel.tsx | 102 ++++++++++++++++-- 5 files changed, 165 insertions(+), 11 deletions(-) diff --git a/backend/src/server/routes/v1/admin-router.ts b/backend/src/server/routes/v1/admin-router.ts index b0d474be9..2935e2655 100644 --- a/backend/src/server/routes/v1/admin-router.ts +++ b/backend/src/server/routes/v1/admin-router.ts @@ -399,6 +399,42 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => { } }); + server.route({ + method: "DELETE", + url: "/identity-management/identities/:identityId/super-admin-access", + config: { + rateLimit: writeLimit + }, + schema: { + params: z.object({ + identityId: z.string() + }), + response: { + 200: z.object({ + identity: IdentitiesSchema.pick({ + name: true, + id: true + }) + }) + } + }, + onRequest: (req, res, done) => { + verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN])(req, res, () => { + verifySuperAdmin(req, res, done); + }); + }, + handler: async (req) => { + const identity = await server.services.superAdmin.deleteIdentitySuperAdminAccess( + req.params.identityId, + req.permission.id + ); + + return { + identity + }; + } + }); + server.route({ method: "POST", url: "/bootstrap", diff --git a/backend/src/services/super-admin/super-admin-service.ts b/backend/src/services/super-admin/super-admin-service.ts index 1f1ed592d..b8fb5a3e3 100644 --- a/backend/src/services/super-admin/super-admin-service.ts +++ b/backend/src/services/super-admin/super-admin-service.ts @@ -437,6 +437,22 @@ export const superAdminServiceFactory = ({ return user; }; + const deleteIdentitySuperAdminAccess = async (identityId: string, actorId: string) => { + const identity = await identityDAL.findById(identityId); + if (!identity) { + throw new NotFoundError({ name: "Identity", message: "Identity not found" }); + } + + const currentAdminIdentityIds = (await getServerCfg()).adminIdentityIds ?? []; + if (!currentAdminIdentityIds?.includes(identityId)) { + throw new BadRequestError({ name: "Identity", message: "Identity does not have super admin access" }); + } + + await updateServerCfg({ adminIdentityIds: currentAdminIdentityIds.filter((id) => id !== identityId) }, actorId); + + return identity; + }; + const getIdentities = async ({ offset, limit, searchTerm }: TAdminGetIdentitiesDTO) => { const identities = await identityDAL.getIdentitiesByFilter({ limit, @@ -554,6 +570,7 @@ export const superAdminServiceFactory = ({ getAdminSlackConfig, updateRootEncryptionStrategy, getConfiguredEncryptionStrategies, - grantServerAdminAccessToUser + grantServerAdminAccessToUser, + deleteIdentitySuperAdminAccess }; }; diff --git a/frontend/src/hooks/api/admin/index.ts b/frontend/src/hooks/api/admin/index.ts index 5eb6c6732..f4982dc14 100644 --- a/frontend/src/hooks/api/admin/index.ts +++ b/frontend/src/hooks/api/admin/index.ts @@ -1,6 +1,7 @@ export { useAdminDeleteUser, useAdminGrantServerAdminAccess, + useAdminRemoveIdentitySuperAdminAccess, useCreateAdminUser, useUpdateAdminSlackConfig, useUpdateServerConfig, diff --git a/frontend/src/hooks/api/admin/mutation.ts b/frontend/src/hooks/api/admin/mutation.ts index 901c079a0..754aca5e9 100644 --- a/frontend/src/hooks/api/admin/mutation.ts +++ b/frontend/src/hooks/api/admin/mutation.ts @@ -70,6 +70,24 @@ export const useAdminDeleteUser = () => { }); }; +export const useAdminRemoveIdentitySuperAdminAccess = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: async (identityId: string) => { + await apiRequest.delete( + `/api/v1/admin/identity-management/identities/${identityId}/super-admin-access` + ); + + return {}; + }, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: [adminStandaloneKeys.getIdentities] + }); + } + }); +}; + export const useAdminGrantServerAdminAccess = () => { const queryClient = useQueryClient(); return useMutation({ diff --git a/frontend/src/pages/admin/OverviewPage/components/IdentityPanel.tsx b/frontend/src/pages/admin/OverviewPage/components/IdentityPanel.tsx index 81ae19d4f..1df9a7cd3 100644 --- a/frontend/src/pages/admin/OverviewPage/components/IdentityPanel.tsx +++ b/frontend/src/pages/admin/OverviewPage/components/IdentityPanel.tsx @@ -1,10 +1,16 @@ import { useState } from "react"; -import { faMagnifyingGlass, faServer } from "@fortawesome/free-solid-svg-icons"; +import { faEllipsis, faMagnifyingGlass, faServer } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { createNotification } from "@app/components/notifications"; import { Badge, Button, + DeleteActionModal, + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, EmptyState, Input, Table, @@ -16,10 +22,22 @@ import { THead, Tr } from "@app/components/v2"; -import { useDebounce } from "@app/hooks"; +import { useDebounce, usePopUp } from "@app/hooks"; +import { useAdminRemoveIdentitySuperAdminAccess } from "@app/hooks/api/admin"; import { useAdminGetIdentities } from "@app/hooks/api/admin/queries"; +import { UsePopUpState } from "@app/hooks/usePopUp"; -const IdentityPanelTable = () => { +const IdentityPanelTable = ({ + handlePopUpOpen +}: { + handlePopUpOpen: ( + popUpName: keyof UsePopUpState<["removeServerAdmin"]>, + data?: { + name: string; + id: string; + } + ) => void; +}) => { const [searchIdentityFilter, setSearchIdentityFilter] = useState(""); const [debouncedSearchTerm] = useDebounce(searchIdentityFilter, 500); @@ -49,6 +67,7 @@ const IdentityPanelTable = () => { Name + @@ -65,6 +84,31 @@ const IdentityPanelTable = () => { )} + + {isInstanceAdmin && ( +
+ + +
+ +
+
+ + {isInstanceAdmin && ( + { + e.stopPropagation(); + handlePopUpOpen("removeServerAdmin", { name, id }); + }} + > + Remove Server Admin + + )} + +
+
+ )} + )) )} @@ -89,11 +133,49 @@ const IdentityPanelTable = () => { ); }; -export const IdentityPanel = () => ( -
-
-

Identities

+export const IdentityPanel = () => { + const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([ + "removeServerAdmin" + ] as const); + + const { mutate: deleteIdentitySuperAdminAccess } = useAdminRemoveIdentitySuperAdminAccess(); + + const handleRemoveServerAdmin = async () => { + const { id } = popUp?.removeServerAdmin?.data as { id: string; name: string }; + + try { + await deleteIdentitySuperAdminAccess(id); + createNotification({ + type: "success", + text: "Successfully removed server admin permissions" + }); + } catch { + createNotification({ + type: "error", + text: "Error removing server admin permissions" + }); + } + + handlePopUpClose("removeServerAdmin"); + }; + + return ( +
+
+

Identities

+
+ + handlePopUpToggle("removeServerAdmin", isOpen)} + deleteKey="confirm" + onDeleteApproved={handleRemoveServerAdmin} + buttonText="Remove Access" + />
- -
-); + ); +};