From e2e0f6a346f837def82c969ae105a5a7e0b0cd97 Mon Sep 17 00:00:00 2001 From: carlosmonastyrski Date: Fri, 21 Mar 2025 18:08:22 -0300 Subject: [PATCH 1/3] Add recursive flag to folders get endpoint to retrieve all nested folders --- backend/src/lib/api-docs/constants.ts | 3 ++- .../server/routes/v1/secret-folder-router.ts | 20 +++++++++++++++++-- .../secret-folder/secret-folder-service.ts | 14 ++++++++++++- .../secret-folder/secret-folder-types.ts | 1 + 4 files changed, 34 insertions(+), 4 deletions(-) diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index 8338492a4..85c0cf296 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -631,7 +631,8 @@ export const FOLDERS = { workspaceId: "The ID of the project to list folders from.", environment: "The slug of the environment to list folders from.", path: "The path to list folders from.", - directory: "The directory to list folders from. (Deprecated in favor of path)" + directory: "The directory to list folders from. (Deprecated in favor of path)", + recursive: "Whether or not to fetch all folders from the specified base path, and all of its subdirectories." }, GET_BY_ID: { folderId: "The ID of the folder to get details." diff --git a/backend/src/server/routes/v1/secret-folder-router.ts b/backend/src/server/routes/v1/secret-folder-router.ts index b0a22548a..7b5c1f113 100644 --- a/backend/src/server/routes/v1/secret-folder-router.ts +++ b/backend/src/server/routes/v1/secret-folder-router.ts @@ -9,6 +9,19 @@ import { readLimit, secretsLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; +const booleanSchema = z + .union([z.boolean(), z.string().trim()]) + .transform((value) => { + if (typeof value === "string") { + // ie if not empty, 0 or false, return true + return Boolean(value) && Number(value) !== 0 && value.toLowerCase() !== "false"; + } + + return value; + }) + .optional() + .default(false); + export const registerSecretFolderRouter = async (server: FastifyZodProvider) => { server.route({ url: "/", @@ -347,11 +360,14 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => .default("/") .transform(prefixWithSlash) .transform(removeTrailingSlash) - .describe(FOLDERS.LIST.directory) + .describe(FOLDERS.LIST.directory), + recursive: booleanSchema.describe(FOLDERS.LIST.recursive) }), response: { 200: z.object({ - folders: SecretFoldersSchema.array() + folders: SecretFoldersSchema.extend({ + relativePath: z.string().optional() + }).array() }) } }, diff --git a/backend/src/services/secret-folder/secret-folder-service.ts b/backend/src/services/secret-folder/secret-folder-service.ts index 7afbb290f..d4f58a1d4 100644 --- a/backend/src/services/secret-folder/secret-folder-service.ts +++ b/backend/src/services/secret-folder/secret-folder-service.ts @@ -401,7 +401,8 @@ export const secretFolderServiceFactory = ({ orderBy, orderDirection, limit, - offset + offset, + recursive }: TGetFolderDTO) => { // folder list is allowed to be read by anyone // permission to check does user has access @@ -420,6 +421,17 @@ export const secretFolderServiceFactory = ({ const parentFolder = await folderDAL.findBySecretPath(projectId, environment, secretPath); if (!parentFolder) return []; + if (recursive) { + const recursiveFolders = await folderDAL.findByEnvsDeep({ parentIds: [parentFolder.id] }); + // remove the parent folder + return recursiveFolders + .filter((folder) => folder.id !== parentFolder.id) + .map((folder) => ({ + ...folder, + relativePath: folder.path + })); + } + const folders = await folderDAL.find( { envId: env.id, diff --git a/backend/src/services/secret-folder/secret-folder-types.ts b/backend/src/services/secret-folder/secret-folder-types.ts index bbe4c7223..2a5681b5b 100644 --- a/backend/src/services/secret-folder/secret-folder-types.ts +++ b/backend/src/services/secret-folder/secret-folder-types.ts @@ -45,6 +45,7 @@ export type TGetFolderDTO = { orderDirection?: OrderByDirection; limit?: number; offset?: number; + recursive?: boolean; } & TProjectPermission; export type TGetFolderByIdDTO = { From d918dd89679d97fb4888567341a930aba938da6c Mon Sep 17 00:00:00 2001 From: carlosmonastyrski Date: Fri, 21 Mar 2025 18:29:55 -0300 Subject: [PATCH 2/3] Move booleanSchema to sanitizedSchema --- backend/src/server/routes/sanitizedSchemas.ts | 13 ++++++++++++ .../src/server/routes/v1/dashboard-router.ts | 21 ++++++------------- .../server/routes/v1/secret-folder-router.ts | 13 +----------- 3 files changed, 20 insertions(+), 27 deletions(-) diff --git a/backend/src/server/routes/sanitizedSchemas.ts b/backend/src/server/routes/sanitizedSchemas.ts index 77149eb1b..e85fe5d0e 100644 --- a/backend/src/server/routes/sanitizedSchemas.ts +++ b/backend/src/server/routes/sanitizedSchemas.ts @@ -70,6 +70,19 @@ export const DefaultResponseErrorsSchema = { }) }; +export const booleanSchema = z + .union([z.boolean(), z.string().trim()]) + .transform((value) => { + if (typeof value === "string") { + // ie if not empty, 0 or false, return true + return Boolean(value) && Number(value) !== 0 && value.toLowerCase() !== "false"; + } + + return value; + }) + .optional() + .default(false); + export const sapPubSchema = SecretApprovalPoliciesSchema.merge( z.object({ environment: z.object({ diff --git a/backend/src/server/routes/v1/dashboard-router.ts b/backend/src/server/routes/v1/dashboard-router.ts index db61594a2..3813c051f 100644 --- a/backend/src/server/routes/v1/dashboard-router.ts +++ b/backend/src/server/routes/v1/dashboard-router.ts @@ -16,7 +16,12 @@ import { secretsLimit } from "@app/server/config/rateLimiter"; import { getTelemetryDistinctId } from "@app/server/lib/telemetry"; import { getUserAgentType } from "@app/server/plugins/audit-log"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; -import { SanitizedDynamicSecretSchema, SanitizedTagSchema, secretRawSchema } from "@app/server/routes/sanitizedSchemas"; +import { + booleanSchema, + SanitizedDynamicSecretSchema, + SanitizedTagSchema, + secretRawSchema +} from "@app/server/routes/sanitizedSchemas"; import { AuthMode } from "@app/services/auth/auth-type"; import { ResourceMetadataSchema } from "@app/services/resource-metadata/resource-metadata-schema"; import { SecretsOrderBy } from "@app/services/secret/secret-types"; @@ -24,20 +29,6 @@ import { PostHogEventTypes } from "@app/services/telemetry/telemetry-types"; const MAX_DEEP_SEARCH_LIMIT = 500; // arbitrary limit to prevent excessive results -// handle querystring boolean values -const booleanSchema = z - .union([z.boolean(), z.string().trim()]) - .transform((value) => { - if (typeof value === "string") { - // ie if not empty, 0 or false, return true - return Boolean(value) && Number(value) !== 0 && value.toLowerCase() !== "false"; - } - - return value; - }) - .optional() - .default(true); - const parseSecretPathSearch = (search?: string) => { if (!search) return { diff --git a/backend/src/server/routes/v1/secret-folder-router.ts b/backend/src/server/routes/v1/secret-folder-router.ts index 7b5c1f113..6df4ddefa 100644 --- a/backend/src/server/routes/v1/secret-folder-router.ts +++ b/backend/src/server/routes/v1/secret-folder-router.ts @@ -9,18 +9,7 @@ import { readLimit, secretsLimit } from "@app/server/config/rateLimiter"; import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; -const booleanSchema = z - .union([z.boolean(), z.string().trim()]) - .transform((value) => { - if (typeof value === "string") { - // ie if not empty, 0 or false, return true - return Boolean(value) && Number(value) !== 0 && value.toLowerCase() !== "false"; - } - - return value; - }) - .optional() - .default(false); +import { booleanSchema } from "../sanitizedSchemas"; export const registerSecretFolderRouter = async (server: FastifyZodProvider) => { server.route({ From 52bd1afb0ad45ae7730413114e134b0891b9dff6 Mon Sep 17 00:00:00 2001 From: carlosmonastyrski Date: Fri, 21 Mar 2025 18:35:32 -0300 Subject: [PATCH 3/3] Move booleanSchema to sanitizedSchema - fix default value --- backend/src/server/routes/sanitizedSchemas.ts | 2 +- backend/src/server/routes/v1/secret-folder-router.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/server/routes/sanitizedSchemas.ts b/backend/src/server/routes/sanitizedSchemas.ts index e85fe5d0e..67bd26552 100644 --- a/backend/src/server/routes/sanitizedSchemas.ts +++ b/backend/src/server/routes/sanitizedSchemas.ts @@ -81,7 +81,7 @@ export const booleanSchema = z return value; }) .optional() - .default(false); + .default(true); export const sapPubSchema = SecretApprovalPoliciesSchema.merge( z.object({ diff --git a/backend/src/server/routes/v1/secret-folder-router.ts b/backend/src/server/routes/v1/secret-folder-router.ts index 6df4ddefa..5f8b553f5 100644 --- a/backend/src/server/routes/v1/secret-folder-router.ts +++ b/backend/src/server/routes/v1/secret-folder-router.ts @@ -350,7 +350,7 @@ export const registerSecretFolderRouter = async (server: FastifyZodProvider) => .transform(prefixWithSlash) .transform(removeTrailingSlash) .describe(FOLDERS.LIST.directory), - recursive: booleanSchema.describe(FOLDERS.LIST.recursive) + recursive: booleanSchema.default(false).describe(FOLDERS.LIST.recursive) }), response: { 200: z.object({