Allow server admins to grant server admin access to other users

This commit is contained in:
carlosmonastyrski
2025-03-04 12:38:27 -03:00
parent e5bca5b5df
commit be4adc2759
5 changed files with 134 additions and 24 deletions

View File

@@ -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",

View File

@@ -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
};
};

View File

@@ -3,7 +3,8 @@ export {
useCreateAdminUser,
useUpdateAdminSlackConfig,
useUpdateServerConfig,
useUpdateServerEncryptionStrategy
useUpdateServerEncryptionStrategy,
useAdminGrantServerAdminAccess
} from "./mutation";
export {
useAdminGetUsers,

View File

@@ -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<AdminSlackConfig, object, TUpdateAdminSlackConfigDTO>({

View File

@@ -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 = ({
<Td>
{userId !== id && (
<div className="flex justify-end">
<IconButton
size="lg"
colorSchema="danger"
variant="plain"
ariaLabel="update"
isDisabled={userId === id}
onClick={() => {
if (!subscription?.instanceUserManagement) {
handlePopUpOpen("upgradePlan");
return;
}
handlePopUpOpen("removeUser", { username, id });
}}
>
<FontAwesomeIcon icon={faXmark} />
</IconButton>
<DropdownMenu>
<DropdownMenuTrigger asChild className="rounded-lg">
<div className="hover:text-primary-400 data-[state=open]:text-primary-400">
<FontAwesomeIcon size="sm" icon={faEllipsis} />
</div>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="p-1">
<DropdownMenuItem
className="hover:!bg-red-500 hover:!text-white"
onClick={(e) => {
e.stopPropagation();
if (!subscription?.instanceUserManagement) {
handlePopUpOpen("upgradePlan");
return;
}
handlePopUpOpen("removeUser", { username, id });
}}
>
Remove User
</DropdownMenuItem>
{(!superAdmin &&
<DropdownMenuItem
className="hover:!bg-yellow/20 hover:!text-yellow"
onClick={(e) => {
e.stopPropagation();
if (!subscription?.instanceUserManagement) {
handlePopUpOpen("upgradePlan");
return;
}
handlePopUpOpen("upgradeToServerAdmin", { username, id });
}}
>
Make User Server Admin
</DropdownMenuItem>
)}
</DropdownMenuContent>
</DropdownMenu>
</div>
)}
</Td>
@@ -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 (
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="mb-4">
@@ -173,6 +222,17 @@ export const UserPanel = () => {
onChange={(isOpen) => handlePopUpToggle("removeUser", isOpen)}
onDeleteApproved={handleRemoveUser}
/>
<DeleteActionModal
isOpen={popUp.upgradeToServerAdmin.isOpen}
title={`Are you sure want to grant Server Admin permissions to ${
(popUp?.upgradeToServerAdmin?.data as { id: string; username: string })?.username || ""
}?`}
subTitle=""
onChange={(isOpen) => handlePopUpToggle("upgradeToServerAdmin", isOpen)}
deleteKey="confirm"
onDeleteApproved={handleGrantServerAdminAccess}
buttonText="Grant Access"
/>
<UpgradePlanModal
isOpen={popUp.upgradePlan.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}