Merge pull request #3422 from Infisical/feat/addProjectDeletionProtection

Add project delete protection
This commit is contained in:
carlosmonastyrski
2025-04-16 16:30:46 -03:00
committed by GitHub
14 changed files with 192 additions and 8 deletions
+22 -2
View File
@@ -33,6 +33,7 @@ import {
TGetUpgradeProjectStatusDTO,
TListProjectIdentitiesDTO,
ToggleAutoCapitalizationDTO,
ToggleDeleteProjectProtectionDTO,
TSearchProjectsDTO,
TUpdateWorkspaceIdentityRoleDTO,
TUpdateWorkspaceUserRoleDTO,
@@ -306,6 +307,25 @@ export const useToggleAutoCapitalization = () => {
});
};
export const useToggleDeleteProjectProtection = () => {
const queryClient = useQueryClient();
return useMutation<Workspace, object, ToggleDeleteProjectProtectionDTO>({
mutationFn: async ({ workspaceID, state }) => {
const { data } = await apiRequest.post<{ workspace: Workspace }>(
`/api/v1/workspace/${workspaceID}/delete-protection`,
{
hasDeleteProtection: state
}
);
return data.workspace;
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: workspaceKeys.getAllUserWorkspace() });
}
});
};
export const useUpdateWorkspaceVersionLimit = () => {
const queryClient = useQueryClient();
@@ -316,8 +336,8 @@ export const useUpdateWorkspaceVersionLimit = () => {
});
return data.workspace;
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: workspaceKeys.getAllUserWorkspace() });
onSuccess: (dto) => {
queryClient.invalidateQueries({ queryKey: workspaceKeys.getAllUserWorkspace(dto.type) });
}
});
};
@@ -36,6 +36,7 @@ export type Workspace = {
slug: string;
createdAt: string;
roles?: TProjectRole[];
hasDeleteProtection: boolean;
};
export type WorkspaceEnv = {
@@ -80,6 +81,7 @@ export type UpdateProjectDTO = {
export type UpdatePitVersionLimitDTO = { projectSlug: string; pitVersionLimit: number };
export type UpdateAuditLogsRetentionDTO = { projectSlug: string; auditLogsRetentionDays: number };
export type ToggleAutoCapitalizationDTO = { workspaceID: string; state: boolean };
export type ToggleDeleteProjectProtectionDTO = { workspaceID: string; state: boolean };
export type DeleteWorkspaceDTO = { workspaceID: string };
@@ -0,0 +1,57 @@
import { createNotification } from "@app/components/notifications";
import { ProjectPermissionCan } from "@app/components/permissions";
import { Checkbox } from "@app/components/v2";
import { ProjectPermissionActions, ProjectPermissionSub, useWorkspace } from "@app/context";
import { useToggleDeleteProjectProtection } from "@app/hooks/api/workspace/queries";
export const DeleteProjectProtection = () => {
const { currentWorkspace } = useWorkspace();
const { mutateAsync } = useToggleDeleteProjectProtection();
const handleToggleDeleteProjectProtection = async (state: boolean) => {
try {
if (!currentWorkspace?.id) return;
await mutateAsync({
workspaceID: currentWorkspace.id,
state
});
const text = `Successfully ${state ? "enabled" : "disabled"} delete protection`;
createNotification({
text,
type: "success"
});
} catch (err) {
console.error(err);
createNotification({
text: "Failed to update delete protection",
type: "error"
});
}
};
return (
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<p className="mb-3 text-xl font-semibold">Delete Protection</p>
<ProjectPermissionCan I={ProjectPermissionActions.Edit} a={ProjectPermissionSub.Settings}>
{(isAllowed) => (
<div className="w-max">
<Checkbox
className="data-[state=checked]:bg-primary"
id="hasDeleteProtection"
isDisabled={!isAllowed}
isChecked={currentWorkspace?.hasDeleteProtection ?? false}
onCheckedChange={(state) => {
handleToggleDeleteProjectProtection(state as boolean);
}}
>
Protects the project from being deleted accidentally. While this option is enabled,
you can&apos;t delete the project.
</Checkbox>
</div>
)}
</ProjectPermissionCan>
</div>
);
};
@@ -0,0 +1 @@
export { DeleteProjectProtection } from "./DeleteProjectProtection";
@@ -144,7 +144,7 @@ export const DeleteProjectSection = () => {
{(isAllowed) => (
<Button
isLoading={isDeleting}
isDisabled={!isAllowed || isDeleting}
isDisabled={!isAllowed || isDeleting || currentWorkspace?.hasDeleteProtection}
colorSchema="danger"
variant="outline_bg"
type="submit"
@@ -5,6 +5,7 @@ import { ProjectType, ProjectVersion } from "@app/hooks/api/workspace/types";
import { AuditLogsRetentionSection } from "../AuditLogsRetentionSection";
import { AutoCapitalizationSection } from "../AutoCapitalizationSection";
import { BackfillSecretReferenceSecretion } from "../BackfillSecretReferenceSection";
import { DeleteProjectProtection } from "../DeleteProjectProtection";
import { DeleteProjectSection } from "../DeleteProjectSection";
import { EnvironmentSection } from "../EnvironmentSection";
import { PointInTimeVersionLimitSection } from "../PointInTimeVersionLimitSection";
@@ -27,6 +28,7 @@ export const ProjectGeneralTab = () => {
{currentWorkspace?.version !== ProjectVersion.V3 && isSecretManager && (
<RebuildSecretIndicesSection />
)}
<DeleteProjectProtection />
<DeleteProjectSection />
</div>
);