mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-14 22:51:53 +00:00
Add project delete protection
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { TableName } from "../schemas";
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
const hasCol = await knex.schema.hasColumn(TableName.Project, "hasDeleteProtection");
|
||||
if (!hasCol) {
|
||||
await knex.schema.alterTable(TableName.Project, (t) => {
|
||||
t.boolean("hasDeleteProtection").defaultTo(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
const hasCol = await knex.schema.hasColumn(TableName.Project, "hasDeleteProtection");
|
||||
if (hasCol) {
|
||||
await knex.schema.alterTable(TableName.Project, (t) => {
|
||||
t.dropColumn("hasDeleteProtection");
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,8 @@ export const ProjectsSchema = z.object({
|
||||
kmsSecretManagerEncryptedDataKey: zodBuffer.nullable().optional(),
|
||||
description: z.string().nullable().optional(),
|
||||
type: z.string(),
|
||||
enforceCapitalization: z.boolean().default(false)
|
||||
enforceCapitalization: z.boolean().default(false),
|
||||
hasDeleteProtection: z.boolean().default(true).nullable().optional()
|
||||
});
|
||||
|
||||
export type TProjects = z.infer<typeof ProjectsSchema>;
|
||||
|
||||
@@ -255,7 +255,8 @@ export const SanitizedProjectSchema = ProjectsSchema.pick({
|
||||
upgradeStatus: true,
|
||||
pitVersionLimit: true,
|
||||
kmsCertificateKeyId: true,
|
||||
auditLogsRetentionDays: true
|
||||
auditLogsRetentionDays: true,
|
||||
hasDeleteProtection: true
|
||||
});
|
||||
|
||||
export const SanitizedTagSchema = SecretTagsSchema.pick({
|
||||
|
||||
@@ -390,6 +390,43 @@ export const registerProjectRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "POST",
|
||||
url: "/:workspaceId/delete-protection",
|
||||
config: {
|
||||
rateLimit: writeLimit
|
||||
},
|
||||
schema: {
|
||||
params: z.object({
|
||||
workspaceId: z.string().trim()
|
||||
}),
|
||||
body: z.object({
|
||||
hasDeleteProtection: z.boolean()
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
message: z.string(),
|
||||
workspace: SanitizedProjectSchema
|
||||
})
|
||||
}
|
||||
},
|
||||
onRequest: verifyAuth([AuthMode.JWT]),
|
||||
handler: async (req) => {
|
||||
const workspace = await server.services.project.toggleDeleteProtection({
|
||||
actorId: req.permission.id,
|
||||
actor: req.permission.type,
|
||||
actorAuthMethod: req.permission.authMethod,
|
||||
actorOrgId: req.permission.orgId,
|
||||
projectId: req.params.workspaceId,
|
||||
hasDeleteProtection: req.body.hasDeleteProtection
|
||||
});
|
||||
return {
|
||||
message: "Successfully changed workspace settings",
|
||||
workspace
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
server.route({
|
||||
method: "PUT",
|
||||
url: "/:workspaceSlug/version-limit",
|
||||
|
||||
@@ -86,6 +86,7 @@ import {
|
||||
TProjectAccessRequestDTO,
|
||||
TSearchProjectsDTO,
|
||||
TToggleProjectAutoCapitalizationDTO,
|
||||
TToggleProjectDeleteProtectionDTO,
|
||||
TUpdateAuditLogsRetentionDTO,
|
||||
TUpdateProjectDTO,
|
||||
TUpdateProjectKmsDTO,
|
||||
@@ -482,6 +483,12 @@ export const projectServiceFactory = ({
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Project);
|
||||
|
||||
if (project.hasDeleteProtection) {
|
||||
throw new ForbiddenRequestError({
|
||||
message: "Project delete protection is enabled"
|
||||
});
|
||||
}
|
||||
|
||||
const deletedProject = await projectDAL.transaction(async (tx) => {
|
||||
// delete these so that project custom roles can be deleted in cascade effect
|
||||
// direct deletion of project without these will cause fk error
|
||||
@@ -648,6 +655,29 @@ export const projectServiceFactory = ({
|
||||
return updatedProject;
|
||||
};
|
||||
|
||||
const toggleDeleteProtection = async ({
|
||||
projectId,
|
||||
actor,
|
||||
actorId,
|
||||
actorOrgId,
|
||||
actorAuthMethod,
|
||||
hasDeleteProtection
|
||||
}: TToggleProjectDeleteProtectionDTO) => {
|
||||
const { permission } = await permissionService.getProjectPermission({
|
||||
actor,
|
||||
actorId,
|
||||
projectId,
|
||||
actorAuthMethod,
|
||||
actorOrgId,
|
||||
actionProjectType: ActionProjectType.Any
|
||||
});
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings);
|
||||
|
||||
const updatedProject = await projectDAL.updateById(projectId, { hasDeleteProtection });
|
||||
|
||||
return updatedProject;
|
||||
};
|
||||
|
||||
const updateVersionLimit = async ({
|
||||
actor,
|
||||
actorId,
|
||||
@@ -1499,6 +1529,7 @@ export const projectServiceFactory = ({
|
||||
getProjectUpgradeStatus,
|
||||
getAProject,
|
||||
toggleAutoCapitalization,
|
||||
toggleDeleteProtection,
|
||||
updateName,
|
||||
upgradeProject,
|
||||
listProjectCas,
|
||||
|
||||
@@ -66,6 +66,10 @@ export type TToggleProjectAutoCapitalizationDTO = {
|
||||
autoCapitalization: boolean;
|
||||
} & TProjectPermission;
|
||||
|
||||
export type TToggleProjectDeleteProtectionDTO = {
|
||||
hasDeleteProtection: boolean;
|
||||
} & TProjectPermission;
|
||||
|
||||
export type TUpdateProjectVersionLimitDTO = {
|
||||
pitVersionLimit: number;
|
||||
workspaceSlug: string;
|
||||
|
||||
Reference in New Issue
Block a user