misc: added support for removing instance admin access of users

This commit is contained in:
Sheen Capadngan
2025-03-19 02:13:24 +08:00
parent d91add2e7b
commit 015a193330
5 changed files with 155 additions and 40 deletions

View File

@ -435,6 +435,42 @@ export const registerAdminRouter = async (server: FastifyZodProvider) => {
}
});
server.route({
method: "DELETE",
url: "/user-management/users/:userId/admin-access",
config: {
rateLimit: writeLimit
},
schema: {
params: z.object({
userId: z.string()
}),
response: {
200: z.object({
user: UsersSchema.pick({
username: true,
firstName: true,
lastName: true,
email: true,
id: true
})
})
}
},
onRequest: (req, res, done) => {
verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN])(req, res, () => {
verifySuperAdmin(req, res, done);
});
},
handler: async (req) => {
const user = await server.services.superAdmin.deleteUserSuperAdminAccess(req.params.userId);
return {
user
};
}
});
server.route({
method: "POST",
url: "/bootstrap",

View File

@ -453,6 +453,17 @@ export const superAdminServiceFactory = ({
return identity;
};
const deleteUserSuperAdminAccess = async (userId: string) => {
const user = await userDAL.findById(userId);
if (!user) {
throw new NotFoundError({ name: "User", message: "User not found" });
}
const updatedUser = userDAL.updateById(userId, { superAdmin: false });
return updatedUser;
};
const getIdentities = async ({ offset, limit, searchTerm }: TAdminGetIdentitiesDTO) => {
const identities = await identityDAL.getIdentitiesByFilter({
limit,
@ -571,6 +582,7 @@ export const superAdminServiceFactory = ({
updateRootEncryptionStrategy,
getConfiguredEncryptionStrategies,
grantServerAdminAccessToUser,
deleteIdentitySuperAdminAccess
deleteIdentitySuperAdminAccess,
deleteUserSuperAdminAccess
};
};

View File

@ -3,6 +3,7 @@ export {
useAdminGrantServerAdminAccess,
useAdminRemoveIdentitySuperAdminAccess,
useCreateAdminUser,
useRemoveUserServerAdminAccess,
useUpdateAdminSlackConfig,
useUpdateServerConfig,
useUpdateServerEncryptionStrategy

View File

@ -88,6 +88,22 @@ export const useAdminRemoveIdentitySuperAdminAccess = () => {
});
};
export const useRemoveUserServerAdminAccess = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async (userId: string) => {
await apiRequest.delete(`/api/v1/admin/user-management/users/${userId}/admin-access`);
return {};
},
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: [adminStandaloneKeys.getUsers]
});
}
});
};
export const useAdminGrantServerAdminAccess = () => {
const queryClient = useQueryClient();
return useMutation({

View File

@ -33,22 +33,26 @@ import {
THead,
Tr
} from "@app/components/v2";
import { useSubscription, useUser } from "@app/context";
import { useSubscription } from "@app/context";
import { useDebounce, usePopUp } from "@app/hooks";
import {
useAdminDeleteUser,
useAdminGetUsers,
useAdminGrantServerAdminAccess
useAdminGrantServerAdminAccess,
useRemoveUserServerAdminAccess
} from "@app/hooks/api";
import { UsePopUpState } from "@app/hooks/usePopUp";
const addServerAdminUpgradePlanMessage = "Granting another user Server Admin permissions";
const removeServerAdminUpgradePlanMessage = "Removing Server Admin permissions from user";
const UserPanelTable = ({
handlePopUpOpen
}: {
handlePopUpOpen: (
popUpName: keyof UsePopUpState<["removeUser", "upgradePlan", "upgradeToServerAdmin"]>,
popUpName: keyof UsePopUpState<
["removeUser", "upgradePlan", "upgradeToServerAdmin", "removeServerAdmin"]
>,
data?: {
username: string;
id: string;
@ -58,8 +62,6 @@ const UserPanelTable = ({
}) => {
const [searchUserFilter, setSearchUserFilter] = useState("");
const [adminsOnly, setAdminsOnly] = useState(false);
const { user } = useUser();
const userId = user?.id || "";
const [debouncedSearchTerm] = useDebounce(searchUserFilter, 500);
const { subscription } = useSubscription();
@ -143,45 +145,61 @@ const UserPanelTable = ({
</Td>
<Td className="w-5/12">{email}</Td>
<Td>
{userId !== id && (
<div className="flex justify-end">
<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">
<div className="flex justify-end">
<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
onClick={(e) => {
e.stopPropagation();
handlePopUpOpen("removeUser", { username, id });
}}
>
Remove User
</DropdownMenuItem>
{!superAdmin && (
<DropdownMenuItem
onClick={(e) => {
e.stopPropagation();
handlePopUpOpen("removeUser", { username, id });
if (!subscription?.instanceUserManagement) {
handlePopUpOpen("upgradePlan", {
username,
id,
message: addServerAdminUpgradePlanMessage
});
return;
}
handlePopUpOpen("upgradeToServerAdmin", { username, id });
}}
>
Remove User
Make User Server Admin
</DropdownMenuItem>
{!superAdmin && (
<DropdownMenuItem
onClick={(e) => {
e.stopPropagation();
if (!subscription?.instanceUserManagement) {
handlePopUpOpen("upgradePlan", {
username,
id,
message: addServerAdminUpgradePlanMessage
});
return;
}
handlePopUpOpen("upgradeToServerAdmin", { username, id });
}}
>
Make User Server Admin
</DropdownMenuItem>
)}
</DropdownMenuContent>
</DropdownMenu>
</div>
)}
)}
{superAdmin && (
<DropdownMenuItem
onClick={(e) => {
e.stopPropagation();
if (!subscription?.instanceUserManagement) {
handlePopUpOpen("upgradePlan", {
username,
id,
message: removeServerAdminUpgradePlanMessage
});
return;
}
handlePopUpOpen("removeServerAdmin", { username, id });
}}
>
Remove Server Admin
</DropdownMenuItem>
)}
</DropdownMenuContent>
</DropdownMenu>
</div>
</Td>
</Tr>
);
@ -212,11 +230,13 @@ export const UserPanel = () => {
const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([
"removeUser",
"upgradePlan",
"upgradeToServerAdmin"
"upgradeToServerAdmin",
"removeServerAdmin"
] as const);
const { mutateAsync: deleteUser } = useAdminDeleteUser();
const { mutateAsync: grantAdminAccess } = useAdminGrantServerAdminAccess();
const { mutateAsync: removeAdminAccess } = useRemoveUserServerAdminAccess();
const handleRemoveUser = async () => {
const { id } = popUp?.removeUser?.data as { id: string; username: string };
@ -256,6 +276,25 @@ export const UserPanel = () => {
handlePopUpClose("upgradeToServerAdmin");
};
const handleRemoveServerAdminAccess = async () => {
const { id } = popUp?.removeServerAdmin?.data as { id: string; username: string };
try {
await removeAdminAccess(id);
createNotification({
type: "success",
text: "Successfully removed server admin access from user"
});
} catch {
createNotification({
type: "error",
text: "Error removing server admin access from user"
});
}
handlePopUpClose("removeServerAdmin");
};
return (
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="mb-4">
@ -282,6 +321,17 @@ export const UserPanel = () => {
onDeleteApproved={handleGrantServerAdminAccess}
buttonText="Grant Access"
/>
<DeleteActionModal
isOpen={popUp.removeServerAdmin.isOpen}
title={`Are you sure want to remove Server Admin permissions from ${
(popUp?.removeServerAdmin?.data as { id: string; username: string })?.username || ""
}?`}
subTitle=""
onChange={(isOpen) => handlePopUpToggle("removeServerAdmin", isOpen)}
deleteKey="confirm"
onDeleteApproved={handleRemoveServerAdminAccess}
buttonText="Remove Access"
/>
<UpgradePlanModal
isOpen={popUp.upgradePlan.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}