mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 22:27:22 +00:00
misc: added trigger for privilege upgrade
This commit is contained in:
@@ -0,0 +1,30 @@
|
|||||||
|
import { Knex } from "knex";
|
||||||
|
import { TableName } from "../schemas";
|
||||||
|
|
||||||
|
export async function up(knex: Knex): Promise<void> {
|
||||||
|
if (!(await knex.schema.hasColumn(TableName.Organization, "shouldUseNewPrivilegeSystem"))) {
|
||||||
|
await knex.schema.alterTable(TableName.Organization, (t) => {
|
||||||
|
t.boolean("shouldUseNewPrivilegeSystem");
|
||||||
|
t.string("privilegeUpgradeInitiatedByUsername");
|
||||||
|
t.dateTime("privilegeUpgradeInitiatedAt");
|
||||||
|
});
|
||||||
|
|
||||||
|
await knex(TableName.Organization).update({
|
||||||
|
shouldUseNewPrivilegeSystem: false
|
||||||
|
});
|
||||||
|
|
||||||
|
await knex.schema.alterTable(TableName.Organization, (t) => {
|
||||||
|
t.boolean("shouldUseNewPrivilegeSystem").defaultTo(true).notNullable().alter();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(knex: Knex): Promise<void> {
|
||||||
|
if (await knex.schema.hasColumn(TableName.Organization, "shouldUseNewPrivilegeSystem")) {
|
||||||
|
await knex.schema.alterTable(TableName.Organization, (t) => {
|
||||||
|
t.dropColumn("shouldUseNewPrivilegeSystem");
|
||||||
|
t.dropColumn("privilegeUpgradeInitiatedByUsername");
|
||||||
|
t.dropColumn("privilegeUpgradeInitiatedAt");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -22,7 +22,10 @@ export const OrganizationsSchema = z.object({
|
|||||||
kmsEncryptedDataKey: zodBuffer.nullable().optional(),
|
kmsEncryptedDataKey: zodBuffer.nullable().optional(),
|
||||||
defaultMembershipRole: z.string().default("member"),
|
defaultMembershipRole: z.string().default("member"),
|
||||||
enforceMfa: z.boolean().default(false),
|
enforceMfa: z.boolean().default(false),
|
||||||
selectedMfaMethod: z.string().nullable().optional()
|
selectedMfaMethod: z.string().nullable().optional(),
|
||||||
|
shouldUseNewPrivilegeSystem: z.boolean().default(true),
|
||||||
|
privilegeUpgradeInitiatedByUsername: z.string().nullable().optional(),
|
||||||
|
privilegeUpgradeInitiatedAt: z.date().nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
export type TOrganizations = z.infer<typeof OrganizationsSchema>;
|
export type TOrganizations = z.infer<typeof OrganizationsSchema>;
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import {
|
|||||||
AuditLogsSchema,
|
AuditLogsSchema,
|
||||||
GroupsSchema,
|
GroupsSchema,
|
||||||
IncidentContactsSchema,
|
IncidentContactsSchema,
|
||||||
OrganizationsSchema,
|
|
||||||
OrgMembershipsSchema,
|
OrgMembershipsSchema,
|
||||||
OrgRolesSchema,
|
OrgRolesSchema,
|
||||||
UsersSchema
|
UsersSchema
|
||||||
@@ -57,7 +56,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
organization: OrganizationsSchema
|
organization: sanitizedOrganizationSchema
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -262,7 +261,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
message: z.string(),
|
message: z.string(),
|
||||||
organization: OrganizationsSchema
|
organization: sanitizedOrganizationSchema
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
OrganizationsSchema,
|
|
||||||
OrgMembershipsSchema,
|
OrgMembershipsSchema,
|
||||||
ProjectMembershipsSchema,
|
ProjectMembershipsSchema,
|
||||||
ProjectsSchema,
|
ProjectsSchema,
|
||||||
@@ -15,6 +14,7 @@ import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
|||||||
import { GenericResourceNameSchema } from "@app/server/lib/schemas";
|
import { GenericResourceNameSchema } from "@app/server/lib/schemas";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { ActorType, AuthMode } from "@app/services/auth/auth-type";
|
import { ActorType, AuthMode } from "@app/services/auth/auth-type";
|
||||||
|
import { sanitizedOrganizationSchema } from "@app/services/org/org-schema";
|
||||||
|
|
||||||
export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
||||||
server.route({
|
server.route({
|
||||||
@@ -335,7 +335,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
organization: OrganizationsSchema
|
organization: sanitizedOrganizationSchema
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -365,7 +365,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
}),
|
}),
|
||||||
response: {
|
response: {
|
||||||
200: z.object({
|
200: z.object({
|
||||||
organization: OrganizationsSchema,
|
organization: sanitizedOrganizationSchema,
|
||||||
accessToken: z.string()
|
accessToken: z.string()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -396,4 +396,30 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
|
|||||||
return { organization, accessToken: tokens.accessToken };
|
return { organization, accessToken: tokens.accessToken };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: "POST",
|
||||||
|
url: "/privilege-system-upgrade",
|
||||||
|
config: {
|
||||||
|
rateLimit: writeLimit
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
organization: sanitizedOrganizationSchema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT]),
|
||||||
|
handler: async (req) => {
|
||||||
|
const organization = await server.services.org.upgradePrivilegeSystem({
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorOrgId: req.permission.orgId,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
orgId: req.permission.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
return { organization };
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -12,5 +12,8 @@ export const sanitizedOrganizationSchema = OrganizationsSchema.pick({
|
|||||||
kmsDefaultKeyId: true,
|
kmsDefaultKeyId: true,
|
||||||
defaultMembershipRole: true,
|
defaultMembershipRole: true,
|
||||||
enforceMfa: true,
|
enforceMfa: true,
|
||||||
selectedMfaMethod: true
|
selectedMfaMethod: true,
|
||||||
|
shouldUseNewPrivilegeSystem: true,
|
||||||
|
privilegeUpgradeInitiatedByUsername: true,
|
||||||
|
privilegeUpgradeInitiatedAt: true
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ import {
|
|||||||
TResendOrgMemberInvitationDTO,
|
TResendOrgMemberInvitationDTO,
|
||||||
TUpdateOrgDTO,
|
TUpdateOrgDTO,
|
||||||
TUpdateOrgMembershipDTO,
|
TUpdateOrgMembershipDTO,
|
||||||
|
TUpgradePrivilegeSystemDTO,
|
||||||
TVerifyUserToOrgDTO
|
TVerifyUserToOrgDTO
|
||||||
} from "./org-types";
|
} from "./org-types";
|
||||||
|
|
||||||
@@ -282,6 +283,45 @@ export const orgServiceFactory = ({
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const upgradePrivilegeSystem = async ({
|
||||||
|
actorId,
|
||||||
|
actorOrgId,
|
||||||
|
actorAuthMethod,
|
||||||
|
orgId
|
||||||
|
}: TUpgradePrivilegeSystemDTO) => {
|
||||||
|
const { membership } = await permissionService.getUserOrgPermission(actorId, orgId, actorAuthMethod, actorOrgId);
|
||||||
|
|
||||||
|
if (membership.role != OrgMembershipRole.Admin) {
|
||||||
|
throw new ForbiddenRequestError({
|
||||||
|
message: "Insufficient privileges - only the organization admin can upgrade the privilege system."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return orgDAL.transaction(async (tx) => {
|
||||||
|
const org = await orgDAL.findById(actorOrgId, tx);
|
||||||
|
if (org.shouldUseNewPrivilegeSystem) {
|
||||||
|
throw new BadRequestError({
|
||||||
|
message: "Privilege system already upgraded"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await userDAL.findById(actorId, tx);
|
||||||
|
if (!user) {
|
||||||
|
throw new NotFoundError({ message: `User with ID '${actorId}' not found` });
|
||||||
|
}
|
||||||
|
|
||||||
|
return orgDAL.updateById(
|
||||||
|
actorOrgId,
|
||||||
|
{
|
||||||
|
shouldUseNewPrivilegeSystem: true,
|
||||||
|
privilegeUpgradeInitiatedAt: new Date(),
|
||||||
|
privilegeUpgradeInitiatedByUsername: user.username
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Update organization details
|
* Update organization details
|
||||||
* */
|
* */
|
||||||
@@ -1310,6 +1350,7 @@ export const orgServiceFactory = ({
|
|||||||
getOrgGroups,
|
getOrgGroups,
|
||||||
listProjectMembershipsByOrgMembershipId,
|
listProjectMembershipsByOrgMembershipId,
|
||||||
findOrgBySlug,
|
findOrgBySlug,
|
||||||
resendOrgMemberInvitation
|
resendOrgMemberInvitation,
|
||||||
|
upgradePrivilegeSystem
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -75,6 +75,8 @@ export type TUpdateOrgDTO = {
|
|||||||
}>;
|
}>;
|
||||||
} & TOrgPermission;
|
} & TOrgPermission;
|
||||||
|
|
||||||
|
export type TUpgradePrivilegeSystemDTO = Omit<TOrgPermission, "actor">;
|
||||||
|
|
||||||
export type TGetOrgGroupsDTO = TOrgPermission;
|
export type TGetOrgGroupsDTO = TOrgPermission;
|
||||||
|
|
||||||
export type TListProjectMembershipsByOrgMembershipIdDTO = {
|
export type TListProjectMembershipsByOrgMembershipIdDTO = {
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ export type CheckboxProps = Omit<
|
|||||||
isError?: boolean;
|
isError?: boolean;
|
||||||
isIndeterminate?: boolean;
|
isIndeterminate?: boolean;
|
||||||
containerClassName?: string;
|
containerClassName?: string;
|
||||||
|
indicatorClassName?: string;
|
||||||
|
allowMultilineLabel?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Checkbox = ({
|
export const Checkbox = ({
|
||||||
@@ -30,6 +32,8 @@ export const Checkbox = ({
|
|||||||
isError,
|
isError,
|
||||||
isIndeterminate,
|
isIndeterminate,
|
||||||
containerClassName,
|
containerClassName,
|
||||||
|
indicatorClassName,
|
||||||
|
allowMultilineLabel,
|
||||||
...props
|
...props
|
||||||
}: CheckboxProps): JSX.Element => {
|
}: CheckboxProps): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
@@ -48,7 +52,9 @@ export const Checkbox = ({
|
|||||||
{...props}
|
{...props}
|
||||||
id={id}
|
id={id}
|
||||||
>
|
>
|
||||||
<CheckboxPrimitive.Indicator className={`${checkIndicatorBg || "text-bunker-800"}`}>
|
<CheckboxPrimitive.Indicator
|
||||||
|
className={twMerge(`${checkIndicatorBg || "text-bunker-800"}`, indicatorClassName)}
|
||||||
|
>
|
||||||
{isIndeterminate ? (
|
{isIndeterminate ? (
|
||||||
<FontAwesomeIcon icon={faMinus} size="sm" />
|
<FontAwesomeIcon icon={faMinus} size="sm" />
|
||||||
) : (
|
) : (
|
||||||
@@ -57,7 +63,11 @@ export const Checkbox = ({
|
|||||||
</CheckboxPrimitive.Indicator>
|
</CheckboxPrimitive.Indicator>
|
||||||
</CheckboxPrimitive.Root>
|
</CheckboxPrimitive.Root>
|
||||||
<label
|
<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}
|
htmlFor={id}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -20,5 +20,6 @@ export {
|
|||||||
useGetOrgTaxIds,
|
useGetOrgTaxIds,
|
||||||
useGetOrgTrialUrl,
|
useGetOrgTrialUrl,
|
||||||
useUpdateOrg,
|
useUpdateOrg,
|
||||||
useUpdateOrgBillingDetails
|
useUpdateOrgBillingDetails,
|
||||||
|
useUpgradePrivilegeSystem
|
||||||
} from "./queries";
|
} 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 = () => {
|
export const useGetOrgTrialUrl = () => {
|
||||||
return useMutation({
|
return useMutation({
|
||||||
mutationFn: async ({ orgId, success_url }: { orgId: string; success_url: string }) => {
|
mutationFn: async ({ orgId, success_url }: { orgId: string; success_url: string }) => {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ export type Organization = {
|
|||||||
defaultMembershipRole: string;
|
defaultMembershipRole: string;
|
||||||
enforceMfa: boolean;
|
enforceMfa: boolean;
|
||||||
selectedMfaMethod?: MfaMethod;
|
selectedMfaMethod?: MfaMethod;
|
||||||
|
shouldUseNewPrivilegeSystem: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type UpdateOrgDTO = {
|
export type UpdateOrgDTO = {
|
||||||
|
|||||||
@@ -2,22 +2,29 @@ import { Helmet } from "react-helmet";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useNavigate, useSearch } from "@tanstack/react-router";
|
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 { ROUTE_PATHS } from "@app/const/routes";
|
||||||
import {
|
import {
|
||||||
OrgPermissionActions,
|
OrgPermissionActions,
|
||||||
OrgPermissionGroupActions,
|
OrgPermissionGroupActions,
|
||||||
OrgPermissionIdentityActions,
|
OrgPermissionIdentityActions,
|
||||||
OrgPermissionSubjects,
|
OrgPermissionSubjects,
|
||||||
|
useOrganization,
|
||||||
useOrgPermission
|
useOrgPermission
|
||||||
} from "@app/context";
|
} from "@app/context";
|
||||||
import { OrgAccessControlTabSections } from "@app/types/org";
|
import { OrgAccessControlTabSections } from "@app/types/org";
|
||||||
|
|
||||||
import { OrgGroupsTab, OrgIdentityTab, OrgMembersTab, OrgRoleTabSection } from "./components";
|
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 = () => {
|
export const AccessManagementPage = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { permission } = useOrgPermission();
|
const { permission } = useOrgPermission();
|
||||||
|
const { currentOrg } = useOrganization();
|
||||||
|
|
||||||
const navigate = useNavigate({
|
const navigate = useNavigate({
|
||||||
from: ROUTE_PATHS.Organization.AccessControlPage.path
|
from: ROUTE_PATHS.Organization.AccessControlPage.path
|
||||||
});
|
});
|
||||||
@@ -27,6 +34,8 @@ export const AccessManagementPage = () => {
|
|||||||
structuralSharing: true
|
structuralSharing: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [isUpgradePrivilegeSystemModalOpen, setIsUpgradePrivilegeSystemModalOpen] = useState(false);
|
||||||
|
|
||||||
const updateSelectedTab = (tab: string) => {
|
const updateSelectedTab = (tab: string) => {
|
||||||
navigate({
|
navigate({
|
||||||
search: { selectedTab: tab }
|
search: { selectedTab: tab }
|
||||||
@@ -73,6 +82,31 @@ export const AccessManagementPage = () => {
|
|||||||
title="Organization Access Control"
|
title="Organization Access Control"
|
||||||
description="Manage fine-grained access for users, groups, roles, and identities within your organization resources."
|
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}>
|
<Tabs value={selectedTab} onValueChange={updateSelectedTab}>
|
||||||
<TabList>
|
<TabList>
|
||||||
{tabSections
|
{tabSections
|
||||||
|
|||||||
+242
@@ -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>
|
||||||
|
);
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user