Fix root folder issue with folder policies check and multi env error message improvement

This commit is contained in:
Carlos Monastyrski
2025-07-02 16:22:16 -03:00
parent a215b99b3c
commit e14bb6b901
2 changed files with 53 additions and 13 deletions

View File

@@ -2,7 +2,7 @@ import { ForbiddenError, subject } from "@casl/ability";
import path from "path"; import path from "path";
import { v4 as uuidv4, validate as uuidValidate } from "uuid"; import { v4 as uuidv4, validate as uuidValidate } from "uuid";
import { TSecretFolders, TSecretFoldersInsert } from "@app/db/schemas"; import { TProjectEnvironments, TSecretFolders, TSecretFoldersInsert } from "@app/db/schemas";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { TSecretApprovalPolicyServiceFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-service"; import { TSecretApprovalPolicyServiceFactory } from "@app/ee/services/secret-approval-policy/secret-approval-policy-service";
@@ -469,15 +469,28 @@ export const secretFolderServiceFactory = ({
const $checkFolderPolicy = async ({ const $checkFolderPolicy = async ({
projectId, projectId,
environment, env,
parentId parentId,
idOrName
}: { }: {
projectId: string; projectId: string;
environment: string; env: TProjectEnvironments;
parentId: string; parentId: string;
idOrName: string;
}) => { }) => {
const targetFolder = await folderDAL.findOne({
envId: env.id,
[uuidValidate(idOrName) ? "id" : "name"]: idOrName,
parentId,
isReserved: false
});
if (!targetFolder) {
throw new NotFoundError({ message: `Target folder not found` });
}
// get environment root folder (as it's needed to get all folders under it) // get environment root folder (as it's needed to get all folders under it)
const rootFolder = await folderDAL.findBySecretPath(projectId, environment, "/"); const rootFolder = await folderDAL.findBySecretPath(projectId, env.slug, "/");
if (!rootFolder) throw new NotFoundError({ message: `Root folder not found` }); if (!rootFolder) throw new NotFoundError({ message: `Root folder not found` });
// get all folders under environment root folder // get all folders under environment root folder
const folderPaths = await folderDAL.findByEnvsDeep({ parentIds: [rootFolder.id] }); const folderPaths = await folderDAL.findByEnvsDeep({ parentIds: [rootFolder.id] });
@@ -492,7 +505,13 @@ export const secretFolderServiceFactory = ({
folderMap.get(normalizeKey(folder.parentId))?.push(folder); folderMap.get(normalizeKey(folder.parentId))?.push(folder);
} }
// Recursively collect all folders under the given parentId // Find the target folder in the folderPaths to get its full details
const targetFolderWithPath = folderPaths.find((f) => f.id === targetFolder.id);
if (!targetFolderWithPath) {
throw new NotFoundError({ message: `Target folder path not found` });
}
// Recursively collect all folders under the target folder (descendants only)
const collectDescendants = ( const collectDescendants = (
id: string id: string
): (TSecretFolders & { path: string; depth: number; environment: string })[] => { ): (TSecretFolders & { path: string; depth: number; environment: string })[] => {
@@ -500,23 +519,31 @@ export const secretFolderServiceFactory = ({
return [...children, ...children.flatMap((child) => collectDescendants(child.id))]; return [...children, ...children.flatMap((child) => collectDescendants(child.id))];
}; };
const foldersUnderParent = collectDescendants(parentId); const targetFolderDescendants = collectDescendants(targetFolder.id);
const folderPolicyPaths = foldersUnderParent.map((folder) => ({ // Include the target folder itself plus all its descendants
const foldersToCheck = [targetFolderWithPath, ...targetFolderDescendants];
const folderPolicyPaths = foldersToCheck.map((folder) => ({
path: folder.path, path: folder.path,
id: folder.id id: folder.id
})); }));
// get secrets under the given folders // get secrets under the given folders
const secrets = await secretV2BridgeDAL.findByFolderIds({ folderIds: folderPolicyPaths.map((p) => p.id) }); const secrets = await secretV2BridgeDAL.findByFolderIds({
folderIds: folderPolicyPaths.map((p) => p.id)
});
for await (const folderPolicyPath of folderPolicyPaths) { for await (const folderPolicyPath of folderPolicyPaths) {
// eslint-disable-next-line no-continue // eslint-disable-next-line no-continue
if (!secrets.some((s) => s.folderId === folderPolicyPath.id)) continue; if (!secrets.some((s) => s.folderId === folderPolicyPath.id)) continue;
const policy = await secretApprovalPolicyService.getSecretApprovalPolicy( const policy = await secretApprovalPolicyService.getSecretApprovalPolicy(
projectId, projectId,
environment, env.slug,
folderPolicyPath.path folderPolicyPath.path
); );
// if there is a policy and there are secrets under the given folder, throw error // if there is a policy and there are secrets under the given folder, throw error
if (policy) { if (policy) {
throw new BadRequestError({ throw new BadRequestError({
@@ -560,7 +587,7 @@ export const secretFolderServiceFactory = ({
message: `Folder with path '${secretPath}' in environment with slug '${environment}' not found` message: `Folder with path '${secretPath}' in environment with slug '${environment}' not found`
}); });
await $checkFolderPolicy({ projectId, environment, parentId: parentFolder.id }); await $checkFolderPolicy({ projectId, env, parentId: parentFolder.id, idOrName });
const [doc] = await folderDAL.delete( const [doc] = await folderDAL.delete(
{ {

View File

@@ -166,23 +166,36 @@ export const SelectionPanel = ({
secrets: secretsToDelete secrets: secretsToDelete
}); });
} }
return {
environment: env.slug
};
}); });
const results = await Promise.allSettled(promises); const results = await Promise.allSettled(promises);
const areEntriesDeleted = results.some((result) => result.status === "fulfilled"); const areAllEntriesDeleted = results.every((result) => result.status === "fulfilled");
const areSomeEntriesDeleted = results.some((result) => result.status === "fulfilled");
if (processedEntries === 0) { if (processedEntries === 0) {
handlePopUpClose("bulkDeleteEntries"); handlePopUpClose("bulkDeleteEntries");
createNotification({ createNotification({
type: "info", type: "info",
text: "You don't have access to delete selected items" text: "You don't have access to delete selected items"
}); });
} else if (areEntriesDeleted) { } else if (areAllEntriesDeleted) {
handlePopUpClose("bulkDeleteEntries"); handlePopUpClose("bulkDeleteEntries");
resetSelectedEntries(); resetSelectedEntries();
createNotification({ createNotification({
type: "success", type: "success",
text: "Successfully deleted selected secrets and folders" text: "Successfully deleted selected secrets and folders"
}); });
} else if (areSomeEntriesDeleted) {
createNotification({
type: "warning",
text: `Successfully deleted selected secrets and folders on environments: ${results
.filter((result) => result.status === "fulfilled")
.map((result) => result.value.environment)
.join(", ")} but failed on the other environments`
});
} else { } else {
createNotification({ createNotification({
type: "error", type: "error",