misc: added trigger for privilege upgrade

This commit is contained in:
Sheen Capadngan
2025-03-13 22:21:44 +08:00
parent cc2c4b16bf
commit fc651f6645
13 changed files with 417 additions and 13 deletions
@@ -17,6 +17,8 @@ export type CheckboxProps = Omit<
isError?: boolean;
isIndeterminate?: boolean;
containerClassName?: string;
indicatorClassName?: string;
allowMultilineLabel?: boolean;
};
export const Checkbox = ({
@@ -30,6 +32,8 @@ export const Checkbox = ({
isError,
isIndeterminate,
containerClassName,
indicatorClassName,
allowMultilineLabel,
...props
}: CheckboxProps): JSX.Element => {
return (
@@ -48,7 +52,9 @@ export const Checkbox = ({
{...props}
id={id}
>
<CheckboxPrimitive.Indicator className={`${checkIndicatorBg || "text-bunker-800"}`}>
<CheckboxPrimitive.Indicator
className={twMerge(`${checkIndicatorBg || "text-bunker-800"}`, indicatorClassName)}
>
{isIndeterminate ? (
<FontAwesomeIcon icon={faMinus} size="sm" />
) : (
@@ -57,7 +63,11 @@ export const Checkbox = ({
</CheckboxPrimitive.Indicator>
</CheckboxPrimitive.Root>
<label
className={twMerge("truncate whitespace-nowrap text-sm", isError && "text-red-400")}
className={twMerge(
"text-sm",
!allowMultilineLabel && "truncate whitespace-nowrap",
isError && "text-red-400"
)}
htmlFor={id}
>
{children}
+2 -1
View File
@@ -20,5 +20,6 @@ export {
useGetOrgTaxIds,
useGetOrgTrialUrl,
useUpdateOrg,
useUpdateOrgBillingDetails
useUpdateOrgBillingDetails,
useUpgradePrivilegeSystem
} from "./queries";
@@ -127,6 +127,18 @@ export const useUpdateOrg = () => {
});
};
export const useUpgradePrivilegeSystem = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: () => {
return apiRequest.post("/api/v2/organizations/privilege-system-upgrade");
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: organizationKeys.getUserOrganizations });
}
});
};
export const useGetOrgTrialUrl = () => {
return useMutation({
mutationFn: async ({ orgId, success_url }: { orgId: string; success_url: string }) => {
@@ -15,6 +15,7 @@ export type Organization = {
defaultMembershipRole: string;
enforceMfa: boolean;
selectedMfaMethod?: MfaMethod;
shouldUseNewPrivilegeSystem: boolean;
};
export type UpdateOrgDTO = {
@@ -2,22 +2,29 @@ import { Helmet } from "react-helmet";
import { useTranslation } from "react-i18next";
import { useNavigate, useSearch } from "@tanstack/react-router";
import { PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
import { Button, PageHeader, Tab, TabList, TabPanel, Tabs } from "@app/components/v2";
import { ROUTE_PATHS } from "@app/const/routes";
import {
OrgPermissionActions,
OrgPermissionGroupActions,
OrgPermissionIdentityActions,
OrgPermissionSubjects,
useOrganization,
useOrgPermission
} from "@app/context";
import { OrgAccessControlTabSections } from "@app/types/org";
import { OrgGroupsTab, OrgIdentityTab, OrgMembersTab, OrgRoleTabSection } from "./components";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faInfoCircle } from "@fortawesome/free-solid-svg-icons";
import { UpgradePrivilegeSystemModal } from "./components/UpgradePrivilegeSystemModal/UpgradePrivilegeSystemModal";
import { useState } from "react";
export const AccessManagementPage = () => {
const { t } = useTranslation();
const { permission } = useOrgPermission();
const { currentOrg } = useOrganization();
const navigate = useNavigate({
from: ROUTE_PATHS.Organization.AccessControlPage.path
});
@@ -27,6 +34,8 @@ export const AccessManagementPage = () => {
structuralSharing: true
});
const [isUpgradePrivilegeSystemModalOpen, setIsUpgradePrivilegeSystemModalOpen] = useState(false);
const updateSelectedTab = (tab: string) => {
navigate({
search: { selectedTab: tab }
@@ -73,6 +82,31 @@ export const AccessManagementPage = () => {
title="Organization Access Control"
description="Manage fine-grained access for users, groups, roles, and identities within your organization resources."
/>
{!currentOrg.shouldUseNewPrivilegeSystem && (
<div className="mb-4 mt-4 flex flex-col rounded-r border-l-2 border-l-primary bg-mineshaft-300/5 px-4 py-2.5">
<div className="mb-1 flex items-center text-sm">
<FontAwesomeIcon icon={faInfoCircle} size="sm" className="mr-1.5 text-primary" />
Your organization is using legacy privilege management
</div>
<p className="mb-2 mt-1 text-sm text-bunker-300">
We've developed an improved privilege management system to better serve your security
needs. Upgrade to our new permission-based approach that allows you to explicitly
designate who can modify specific access levels, rather than relying on traditional
hierarchy comparisons.
</p>
<Button
colorSchema="primary"
className="mt-2 w-fit text-xs"
onClick={() => setIsUpgradePrivilegeSystemModalOpen(true)}
>
Learn More & Upgrade
</Button>
</div>
)}
<UpgradePrivilegeSystemModal
isOpen={isUpgradePrivilegeSystemModalOpen}
onOpenChange={setIsUpgradePrivilegeSystemModalOpen}
/>
<Tabs value={selectedTab} onValueChange={updateSelectedTab}>
<TabList>
{tabSections
@@ -0,0 +1,242 @@
import { Controller, useForm } from "react-hook-form";
import {
faCheck,
faCircleInfo,
faExclamationTriangle,
faWarning
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { createNotification } from "@app/components/notifications";
import { Button, Checkbox, Modal, ModalContent } from "@app/components/v2";
import { useOrgPermission } from "@app/context";
import { useUpgradePrivilegeSystem } from "@app/hooks/api";
const formSchema = z.object({
isProjectPrivilegesUpdated: z.literal(true),
isOrgPrivilegesUpdated: z.literal(true),
isInfrastructureUpdated: z.literal(true),
acknowledgesPermanentChange: z.literal(true)
});
type Props = {
isOpen?: boolean;
onOpenChange: (isOpen: boolean) => void;
};
export const UpgradePrivilegeSystemModal = ({ isOpen, onOpenChange }: Props) => {
const { membership } = useOrgPermission();
const {
handleSubmit,
control,
formState: { isSubmitting }
} = useForm({ resolver: zodResolver(formSchema) });
const { mutateAsync: upgradePrivilegeSystem } = useUpgradePrivilegeSystem();
const handlePrivilegeSystemUpgrade = async () => {
try {
await upgradePrivilegeSystem();
createNotification({
text: "Privilege system upgrade completed",
type: "success"
});
onOpenChange(false);
} catch {
createNotification({
text: "Failed to upgrade privilege system",
type: "error"
});
}
};
const isAdmin = membership?.role === "admin";
return (
<Modal isOpen={isOpen} onOpenChange={onOpenChange}>
<ModalContent title="Privilege Management System Upgrade">
<div className="mb-4">
<h4 className="mb-2 text-lg font-semibold">
Introducing Permission-Based Privilege Management
</h4>
<p className="mb-4 leading-7 text-mineshaft-100">
We've developed an improved privilege management system that enhances how access
controls work in your organization.
</p>
<div className="mb-4 rounded-lg border border-mineshaft-600 bg-mineshaft-800 p-4">
<div className="mb-3">
<div className="mb-2 flex items-start gap-2">
<FontAwesomeIcon icon={faCircleInfo} className="mt-1 text-primary" />
<p className="font-medium">How it works:</p>
</div>
<div className="mb-3 ml-7">
<p className="mb-1">
<strong>Legacy system:</strong> Users with higher privilege levels could modify
access for anyone below them.
</p>
<p>
<strong>New system:</strong> Users need explicit permission to modify specific
access levels, providing targeted control. After upgrading, you'll need to grant
the new 'Manage Privileges' permission at organization or project level.
</p>
</div>
</div>
<div>
<div className="mb-2 flex items-start gap-2">
<FontAwesomeIcon icon={faCheck} className="mt-1 text-primary" />
<p className="font-medium">Benefits:</p>
</div>
<div className="ml-7">
<ul className="list-disc pl-5">
<li>More granular control over who can modify access levels</li>
<li>Improved security through precise permission checks</li>
</ul>
</div>
</div>
</div>
<p className="mb-4 leading-7 text-mineshaft-100">
This upgrade affects operations like updating roles, managing group memberships, and
modifying privileges across your organization and projects.
</p>
</div>
<div className="mt-6 flex max-w-2xl flex-col rounded-lg border border-primary/50 bg-primary/10 px-6 py-5">
<div className="mb-4 flex items-start gap-2">
<FontAwesomeIcon icon={faWarning} size="xl" className="mt-1 text-primary" />
<p className="text-xl font-semibold">Upgrade privilege system</p>
</div>
<p className="mx-1 mb-4 leading-7 text-mineshaft-100">
Your existing access control workflows will continue to function. However, actions that
involve changing privileges or permissions will now use the new permission-based system,
requiring users to have explicit permission to modify access levels.
</p>
<form onSubmit={handleSubmit(handlePrivilegeSystemUpgrade)}>
<div className="mb-4 rounded-lg border border-red-500 bg-red-500/10 p-4">
<div className="mb-3 flex items-start gap-2">
<FontAwesomeIcon
icon={faExclamationTriangle}
className="mt-1 text-red-500"
size="lg"
/>
<p className="font-bold text-red-400">IMPORTANT: THIS CHANGE IS PERMANENT</p>
</div>
<p className="mb-3 ml-7 text-mineshaft-100">
Once upgraded, your organization <span className="font-bold">cannot</span> revert to
the legacy privilege system. Please ensure you've completed all preparations before
proceeding.
</p>
</div>
<div className="mb-4">
<p className="mb-3 font-medium">Required preparation checklist:</p>
<div className="flex flex-col space-y-4">
<Controller
control={control}
name="isProjectPrivilegesUpdated"
defaultValue={false}
render={({ field: { onBlur, value, onChange }, fieldState: { error } }) => (
<Checkbox
containerClassName="items-start"
className="mt-0.5 items-start"
id="is-project-privileges-updated"
indicatorClassName="flex h-full w-full items-center justify-center"
allowMultilineLabel
isChecked={value}
onCheckedChange={onChange}
onBlur={onBlur}
isError={Boolean(error?.message)}
>
I have reviewed project-level privileges and updated them if necessary
</Checkbox>
)}
/>
<Controller
control={control}
name="isOrgPrivilegesUpdated"
defaultValue={false}
render={({ field: { onBlur, value, onChange }, fieldState: { error } }) => (
<Checkbox
containerClassName="items-start"
className="mt-0.5 items-start"
indicatorClassName="flex h-full w-full items-center justify-center"
allowMultilineLabel
id="is-org-privileges-updated"
isChecked={value}
onCheckedChange={onChange}
onBlur={onBlur}
isError={Boolean(error?.message)}
>
I have reviewed organization-level privileges and updated them if necessary
</Checkbox>
)}
/>
<Controller
control={control}
name="isInfrastructureUpdated"
defaultValue={false}
render={({ field: { onBlur, value, onChange }, fieldState: { error } }) => (
<Checkbox
containerClassName="items-start"
className="mt-0.5 items-start"
id="is-infrastructure-updated"
indicatorClassName="flex h-full w-full items-center justify-center"
allowMultilineLabel
isChecked={value}
onCheckedChange={onChange}
onBlur={onBlur}
isError={Boolean(error?.message)}
>
I have checked Terraform configurations and API integrations for compatibility
with the new system
</Checkbox>
)}
/>
<Controller
control={control}
name="acknowledgesPermanentChange"
defaultValue={false}
rules={{ required: true }}
render={({ field: { onBlur, value, onChange }, fieldState: { error } }) => (
<Checkbox
containerClassName="items-start"
className="mt-0.5 items-start"
id="acknowledges-permanent-change"
indicatorClassName="flex h-full w-full items-center justify-center"
allowMultilineLabel
isChecked={value}
onCheckedChange={onChange}
onBlur={onBlur}
isError={Boolean(error?.message)}
>
<span className="font-bold">
I understand that this upgrade is permanent and cannot be reversed
</span>
</Checkbox>
)}
/>
</div>
</div>
<Button
type="submit"
isDisabled={!isAdmin}
isLoading={isSubmitting}
className="mt-5 w-full"
>
{isAdmin ? "Upgrade Privilege System" : "Upgrade requires admin privilege"}
</Button>
</form>
</div>
</ModalContent>
</Modal>
);
};