mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 12:27:28 +00:00
Allow server admins to grant server admin access to other users
This commit is contained in:
@@ -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({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/encryption-strategies",
|
url: "/encryption-strategies",
|
||||||
|
|||||||
@@ -291,6 +291,18 @@ export const superAdminServiceFactory = ({
|
|||||||
return user;
|
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 getAdminSlackConfig = async () => {
|
||||||
const serverCfg = await serverCfgDAL.findById(ADMIN_CONFIG_DB_UUID);
|
const serverCfg = await serverCfgDAL.findById(ADMIN_CONFIG_DB_UUID);
|
||||||
|
|
||||||
@@ -381,6 +393,7 @@ export const superAdminServiceFactory = ({
|
|||||||
deleteUser,
|
deleteUser,
|
||||||
getAdminSlackConfig,
|
getAdminSlackConfig,
|
||||||
updateRootEncryptionStrategy,
|
updateRootEncryptionStrategy,
|
||||||
getConfiguredEncryptionStrategies
|
getConfiguredEncryptionStrategies,
|
||||||
|
grantServerAdminAccessToUser
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ export {
|
|||||||
useCreateAdminUser,
|
useCreateAdminUser,
|
||||||
useUpdateAdminSlackConfig,
|
useUpdateAdminSlackConfig,
|
||||||
useUpdateServerConfig,
|
useUpdateServerConfig,
|
||||||
useUpdateServerEncryptionStrategy
|
useUpdateServerEncryptionStrategy,
|
||||||
|
useAdminGrantServerAdminAccess
|
||||||
} from "./mutation";
|
} from "./mutation";
|
||||||
export {
|
export {
|
||||||
useAdminGetUsers,
|
useAdminGetUsers,
|
||||||
|
|||||||
@@ -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 = () => {
|
export const useUpdateAdminSlackConfig = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
return useMutation<AdminSlackConfig, object, TUpdateAdminSlackConfigDTO>({
|
return useMutation<AdminSlackConfig, object, TUpdateAdminSlackConfigDTO>({
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useState } from "react";
|
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 { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
|
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
|
||||||
@@ -9,7 +9,6 @@ import {
|
|||||||
Button,
|
Button,
|
||||||
DeleteActionModal,
|
DeleteActionModal,
|
||||||
EmptyState,
|
EmptyState,
|
||||||
IconButton,
|
|
||||||
Input,
|
Input,
|
||||||
Table,
|
Table,
|
||||||
TableContainer,
|
TableContainer,
|
||||||
@@ -18,18 +17,26 @@ import {
|
|||||||
Td,
|
Td,
|
||||||
Th,
|
Th,
|
||||||
THead,
|
THead,
|
||||||
Tr
|
Tr,
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger
|
||||||
} from "@app/components/v2";
|
} from "@app/components/v2";
|
||||||
import { useSubscription, useUser } from "@app/context";
|
import { useSubscription, useUser } from "@app/context";
|
||||||
import { useDebounce, usePopUp } from "@app/hooks";
|
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";
|
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
const UserPanelTable = ({
|
const UserPanelTable = ({
|
||||||
handlePopUpOpen
|
handlePopUpOpen
|
||||||
}: {
|
}: {
|
||||||
handlePopUpOpen: (
|
handlePopUpOpen: (
|
||||||
popUpName: keyof UsePopUpState<["removeUser", "upgradePlan"]>,
|
popUpName: keyof UsePopUpState<["removeUser", "upgradePlan", "upgradeToServerAdmin"]>,
|
||||||
data?: {
|
data?: {
|
||||||
username: string;
|
username: string;
|
||||||
id: string;
|
id: string;
|
||||||
@@ -87,22 +94,43 @@ const UserPanelTable = ({
|
|||||||
<Td>
|
<Td>
|
||||||
{userId !== id && (
|
{userId !== id && (
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
<IconButton
|
<DropdownMenu>
|
||||||
size="lg"
|
<DropdownMenuTrigger asChild className="rounded-lg">
|
||||||
colorSchema="danger"
|
<div className="hover:text-primary-400 data-[state=open]:text-primary-400">
|
||||||
variant="plain"
|
<FontAwesomeIcon size="sm" icon={faEllipsis} />
|
||||||
ariaLabel="update"
|
</div>
|
||||||
isDisabled={userId === id}
|
</DropdownMenuTrigger>
|
||||||
onClick={() => {
|
<DropdownMenuContent align="start" className="p-1">
|
||||||
if (!subscription?.instanceUserManagement) {
|
<DropdownMenuItem
|
||||||
handlePopUpOpen("upgradePlan");
|
className="hover:!bg-red-500 hover:!text-white"
|
||||||
return;
|
onClick={(e) => {
|
||||||
}
|
e.stopPropagation();
|
||||||
handlePopUpOpen("removeUser", { username, id });
|
if (!subscription?.instanceUserManagement) {
|
||||||
}}
|
handlePopUpOpen("upgradePlan");
|
||||||
>
|
return;
|
||||||
<FontAwesomeIcon icon={faXmark} />
|
}
|
||||||
</IconButton>
|
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>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Td>
|
</Td>
|
||||||
@@ -134,10 +162,12 @@ const UserPanelTable = ({
|
|||||||
export const UserPanel = () => {
|
export const UserPanel = () => {
|
||||||
const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([
|
const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([
|
||||||
"removeUser",
|
"removeUser",
|
||||||
"upgradePlan"
|
"upgradePlan",
|
||||||
|
"upgradeToServerAdmin"
|
||||||
] as const);
|
] as const);
|
||||||
|
|
||||||
const { mutateAsync: deleteUser } = useAdminDeleteUser();
|
const { mutateAsync: deleteUser } = useAdminDeleteUser();
|
||||||
|
const { mutateAsync: grantAdminAccess } = useAdminGrantServerAdminAccess();
|
||||||
|
|
||||||
const handleRemoveUser = async () => {
|
const handleRemoveUser = async () => {
|
||||||
const { id } = popUp?.removeUser?.data as { id: string; username: string };
|
const { id } = popUp?.removeUser?.data as { id: string; username: string };
|
||||||
@@ -158,6 +188,25 @@ export const UserPanel = () => {
|
|||||||
handlePopUpClose("removeUser");
|
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 (
|
return (
|
||||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||||
<div className="mb-4">
|
<div className="mb-4">
|
||||||
@@ -173,6 +222,17 @@ export const UserPanel = () => {
|
|||||||
onChange={(isOpen) => handlePopUpToggle("removeUser", isOpen)}
|
onChange={(isOpen) => handlePopUpToggle("removeUser", isOpen)}
|
||||||
onDeleteApproved={handleRemoveUser}
|
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
|
<UpgradePlanModal
|
||||||
isOpen={popUp.upgradePlan.isOpen}
|
isOpen={popUp.upgradePlan.isOpen}
|
||||||
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||||
|
|||||||
Reference in New Issue
Block a user