diff --git a/backend/src/server/routes/v1/admin-router.ts b/backend/src/server/routes/v1/admin-router.ts index c13351c13..cb91e7c86 100644 --- a/backend/src/server/routes/v1/admin-router.ts +++ b/backend/src/server/routes/v1/admin-router.ts @@ -211,6 +211,27 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => { } }); + server.route({ + method: "PATCH", + url: "/user-management/users/grant-admin-access", + config: { + rateLimit: writeLimit + }, + schema: { + body: z.object({ + userId: z.string() + }) + }, + onRequest: (req, res, done) => { + verifyAuth([AuthMode.JWT])(req, res, () => { + verifySuperAdmin(req, res, done); + }); + }, + handler: async (req) => { + await server.services.superAdmin.grantServerAdminAccessToUser(req.body.userId); + } + }); + server.route({ method: "GET", url: "/encryption-strategies", diff --git a/backend/src/services/super-admin/super-admin-service.ts b/backend/src/services/super-admin/super-admin-service.ts index 9a6075423..57162c2e9 100644 --- a/backend/src/services/super-admin/super-admin-service.ts +++ b/backend/src/services/super-admin/super-admin-service.ts @@ -291,6 +291,18 @@ export const superAdminServiceFactory = ({ return user; }; + const grantServerAdminAccessToUser = async (userId: string) => { + if (!licenseService.onPremFeatures?.instanceUserManagement) { + throw new BadRequestError({ + message: "Failed to grant server admin access to user due to plan restriction. Upgrade to Infisical's Pro plan." + }); + } + + await userDAL.transaction(async (tx) => { + await userDAL.updateById(userId, { superAdmin: true }, tx); + }); + }; + const getAdminSlackConfig = async () => { const serverCfg = await serverCfgDAL.findById(ADMIN_CONFIG_DB_UUID); @@ -381,6 +393,7 @@ export const superAdminServiceFactory = ({ deleteUser, getAdminSlackConfig, updateRootEncryptionStrategy, - getConfiguredEncryptionStrategies + getConfiguredEncryptionStrategies, + grantServerAdminAccessToUser }; }; diff --git a/frontend/src/hooks/api/admin/index.ts b/frontend/src/hooks/api/admin/index.ts index 5405878c7..4d9f05bb1 100644 --- a/frontend/src/hooks/api/admin/index.ts +++ b/frontend/src/hooks/api/admin/index.ts @@ -3,7 +3,8 @@ export { useCreateAdminUser, useUpdateAdminSlackConfig, useUpdateServerConfig, - useUpdateServerEncryptionStrategy + useUpdateServerEncryptionStrategy, + useAdminGrantServerAdminAccess } from "./mutation"; export { useAdminGetUsers, diff --git a/frontend/src/hooks/api/admin/mutation.ts b/frontend/src/hooks/api/admin/mutation.ts index c68760fa4..23e25ea60 100644 --- a/frontend/src/hooks/api/admin/mutation.ts +++ b/frontend/src/hooks/api/admin/mutation.ts @@ -70,6 +70,21 @@ export const useAdminDeleteUser = () => { }); }; +export const useAdminGrantServerAdminAccess = () => { + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: async (userId: string) => { + await apiRequest.patch("/api/v1/admin/user-management/users/grant-admin-access", { userId }); + return {}; + }, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: [adminStandaloneKeys.getUsers] + }); + } + }); +}; + export const useUpdateAdminSlackConfig = () => { const queryClient = useQueryClient(); return useMutation({ diff --git a/frontend/src/pages/admin/OverviewPage/components/UserPanel.tsx b/frontend/src/pages/admin/OverviewPage/components/UserPanel.tsx index 889420a73..5d48044dc 100644 --- a/frontend/src/pages/admin/OverviewPage/components/UserPanel.tsx +++ b/frontend/src/pages/admin/OverviewPage/components/UserPanel.tsx @@ -1,5 +1,5 @@ import { useState } from "react"; -import { faMagnifyingGlass, faUsers, faXmark } from "@fortawesome/free-solid-svg-icons"; +import { faMagnifyingGlass, faUsers, faEllipsis } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; @@ -9,7 +9,6 @@ import { Button, DeleteActionModal, EmptyState, - IconButton, Input, Table, TableContainer, @@ -18,18 +17,26 @@ import { Td, Th, THead, - Tr + Tr, + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger } from "@app/components/v2"; import { useSubscription, useUser } from "@app/context"; import { useDebounce, usePopUp } from "@app/hooks"; -import { useAdminDeleteUser, useAdminGetUsers } from "@app/hooks/api"; +import { + useAdminDeleteUser, + useAdminGetUsers, + useAdminGrantServerAdminAccess +} from "@app/hooks/api"; import { UsePopUpState } from "@app/hooks/usePopUp"; const UserPanelTable = ({ handlePopUpOpen }: { handlePopUpOpen: ( - popUpName: keyof UsePopUpState<["removeUser", "upgradePlan"]>, + popUpName: keyof UsePopUpState<["removeUser", "upgradePlan", "upgradeToServerAdmin"]>, data?: { username: string; id: string; @@ -87,22 +94,43 @@ const UserPanelTable = ({ {userId !== id && (
- { - if (!subscription?.instanceUserManagement) { - handlePopUpOpen("upgradePlan"); - return; - } - handlePopUpOpen("removeUser", { username, id }); - }} - > - - + + +
+ +
+
+ + { + e.stopPropagation(); + if (!subscription?.instanceUserManagement) { + handlePopUpOpen("upgradePlan"); + return; + } + handlePopUpOpen("removeUser", { username, id }); + }} + > + Remove User + + {(!superAdmin && + { + e.stopPropagation(); + if (!subscription?.instanceUserManagement) { + handlePopUpOpen("upgradePlan"); + return; + } + handlePopUpOpen("upgradeToServerAdmin", { username, id }); + }} + > + Make User Server Admin + + )} + +
)} @@ -134,10 +162,12 @@ const UserPanelTable = ({ export const UserPanel = () => { const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([ "removeUser", - "upgradePlan" + "upgradePlan", + "upgradeToServerAdmin" ] as const); const { mutateAsync: deleteUser } = useAdminDeleteUser(); + const { mutateAsync: grantAdminAccess } = useAdminGrantServerAdminAccess(); const handleRemoveUser = async () => { const { id } = popUp?.removeUser?.data as { id: string; username: string }; @@ -158,6 +188,25 @@ export const UserPanel = () => { handlePopUpClose("removeUser"); }; + const handleGrantServerAdminAccess = async () => { + const { id } = popUp?.upgradeToServerAdmin?.data as { id: string; username: string }; + + try { + await grantAdminAccess(id); + createNotification({ + type: "success", + text: "Successfully granted server admin access to user" + }); + } catch { + createNotification({ + type: "error", + text: "Error granting server admin access to user" + }); + } + + handlePopUpClose("upgradeToServerAdmin"); + }; + return (
@@ -173,6 +222,17 @@ export const UserPanel = () => { onChange={(isOpen) => handlePopUpToggle("removeUser", isOpen)} onDeleteApproved={handleRemoveUser} /> + handlePopUpToggle("upgradeToServerAdmin", isOpen)} + deleteKey="confirm" + onDeleteApproved={handleGrantServerAdminAccess} + buttonText="Grant Access" + /> handlePopUpToggle("upgradePlan", isOpen)}