From 1e1ad450d22aef70997e4bff1affb23d4d3502dd Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Sun, 7 Jan 2024 14:25:33 +0100 Subject: [PATCH] Add version query param to GET secret endpoint --- .../src/controllers/v3/secretsController.ts | 10 +-- backend/src/helpers/secrets.ts | 69 +++++++++++++++---- .../services/SecretService/index.ts | 1 + backend/src/services/SecretImportService.ts | 28 ++++++-- backend/src/validation/secrets.ts | 20 +++++- 5 files changed, 102 insertions(+), 26 deletions(-) diff --git a/backend/src/controllers/v3/secretsController.ts b/backend/src/controllers/v3/secretsController.ts index 88d5ab8d9..b9edc431c 100644 --- a/backend/src/controllers/v3/secretsController.ts +++ b/backend/src/controllers/v3/secretsController.ts @@ -348,7 +348,7 @@ export const getSecretByNameRaw = async (req: Request, res: Response) => { } */ const { - query: { secretPath, environment, workspaceId, type, include_imports }, + query: { secretPath, environment, workspaceId, type, include_imports, version }, params: { secretName } } = await validateRequest(reqValidator.GetSecretByNameRawV3, req); @@ -371,7 +371,8 @@ export const getSecretByNameRaw = async (req: Request, res: Response) => { type, secretPath, authData: req.authData, - include_imports + include_imports, + version }); const key = await BotService.getWorkspaceKeyWithBot({ @@ -865,7 +866,7 @@ export const getSecrets = async (req: Request, res: Response) => { */ export const getSecretByName = async (req: Request, res: Response) => { const { - query: { secretPath, environment, workspaceId, type, include_imports }, + query: { secretPath, environment, workspaceId, type, include_imports, version }, params: { secretName } } = await validateRequest(reqValidator.GetSecretByNameV3, req); @@ -888,7 +889,8 @@ export const getSecretByName = async (req: Request, res: Response) => { type, secretPath, authData: req.authData, - include_imports + include_imports, + version }); return res.status(200).send({ diff --git a/backend/src/helpers/secrets.ts b/backend/src/helpers/secrets.ts index 533dd8461..b1340ff0f 100644 --- a/backend/src/helpers/secrets.ts +++ b/backend/src/helpers/secrets.ts @@ -611,42 +611,81 @@ export const getSecretHelper = async ({ type, authData, secretPath = "/", - include_imports = true + include_imports = true, + version }: GetSecretParams) => { + const secretBlindIndex = await generateSecretBlindIndexHelper({ secretName, workspaceId: new Types.ObjectId(workspaceId) }); let secret: ISecret | null | undefined = null; + // if using service token filter towards the folderId by secretpath const folderId = await getFolderIdFromServiceToken(workspaceId, environment, secretPath); // try getting personal secret first (if exists) - secret = await Secret.findOne({ - secretBlindIndex, - workspace: new Types.ObjectId(workspaceId), - environment, - folder: folderId, - type: type ?? SECRET_PERSONAL, - ...(type === SECRET_PERSONAL ? getAuthDataPayloadUserObj(authData) : {}) - }).lean(); - - if (!secret) { - // case: failed to find personal secret matching criteria - // -> find shared secret matching criteria + if (version === undefined) { secret = await Secret.findOne({ secretBlindIndex, workspace: new Types.ObjectId(workspaceId), environment, folder: folderId, - type: SECRET_SHARED + type: type ?? SECRET_PERSONAL, + ...(type === SECRET_PERSONAL ? getAuthDataPayloadUserObj(authData) : {}) }).lean(); + } else { + const secretVersion = await SecretVersion.findOne({ + secretBlindIndex, + workspace: new Types.ObjectId(workspaceId), + environment, + folder: folderId, + type: type ?? SECRET_PERSONAL, + version + }).lean(); + + if (secretVersion) { + secret = await new Secret({ + ...secretVersion, + _id: secretVersion?.secret + }); + } } + if (!secret) { + // case: failed to find personal secret matching criteria + // -> find shared secret matching criteria + if (version === undefined) { + secret = await Secret.findOne({ + secretBlindIndex, + workspace: new Types.ObjectId(workspaceId), + environment, + folder: folderId, + type: SECRET_SHARED + }).lean(); + } else { + const secretVersion = await SecretVersion.findOne({ + secretBlindIndex, + workspace: new Types.ObjectId(workspaceId), + environment, + folder: folderId, + type: SECRET_SHARED, + version + }).lean(); + + if (secretVersion) { + secret = await new Secret({ + ...secretVersion, + _id: secretVersion?.secret + }); + } + } + } + if (!secret && include_imports) { // if still no secret found search in imported secret and retreive - secret = await getAnImportedSecret(secretName, workspaceId.toString(), environment, folderId); + secret = await getAnImportedSecret(secretName, workspaceId.toString(), environment, folderId, version); } if (!secret) throw SecretNotFoundError(); diff --git a/backend/src/interfaces/services/SecretService/index.ts b/backend/src/interfaces/services/SecretService/index.ts index d549debfd..495abf726 100644 --- a/backend/src/interfaces/services/SecretService/index.ts +++ b/backend/src/interfaces/services/SecretService/index.ts @@ -38,6 +38,7 @@ export interface GetSecretParams { type?: "shared" | "personal"; authData: AuthData; include_imports?: boolean; + version?: number; } export interface UpdateSecretParams { diff --git a/backend/src/services/SecretImportService.ts b/backend/src/services/SecretImportService.ts index ffe4df9c6..b8d622d30 100644 --- a/backend/src/services/SecretImportService.ts +++ b/backend/src/services/SecretImportService.ts @@ -1,5 +1,6 @@ import { Types } from "mongoose"; import { generateSecretBlindIndexHelper } from "../helpers"; +import { SecretVersion } from "../ee/models"; import { Folder, ISecret, Secret, SecretImport } from "../models"; import { getFolderByPath } from "./FolderService"; @@ -9,7 +10,8 @@ export const getAnImportedSecret = async ( secretName: string, workspaceId: string, environment: string, - folderId = "root" + folderId = "root", + version?: number ) => { const secretBlindIndex = await generateSecretBlindIndexHelper({ secretName, @@ -48,10 +50,26 @@ export const getAnImportedSecret = async ( }); if (importedSecByFid.length === 0) return; - const secret = await Secret.findOne({ - workspace: workspaceId, - secretBlindIndex - }).or(importedSecByFid.map(({ environment, folderId }) => ({ environment, folder: folderId }))).lean() + let secret; + if (version === undefined) { + secret = await Secret.findOne({ + workspace: workspaceId, + secretBlindIndex + }).or(importedSecByFid.map(({ environment, folderId }) => ({ environment, folder: folderId }))).lean() + } else { + const secretVersion = await SecretVersion.findOne({ + workspace: workspaceId, + secretBlindIndex, + version + }).or(importedSecByFid.map(({ environment, folderId }) => ({ environment, folder: folderId }))).lean(); + + if (secretVersion) { + secret = await new Secret({ + ...secretVersion, + _id: secretVersion.secret, + }); + } + } return secret; }; diff --git a/backend/src/validation/secrets.ts b/backend/src/validation/secrets.ts index 7c0f2d69f..016fe750f 100644 --- a/backend/src/validation/secrets.ts +++ b/backend/src/validation/secrets.ts @@ -246,7 +246,15 @@ export const GetSecretByNameRawV3 = z.object({ include_imports: z .enum(["true", "false"]) .default("true") - .transform((value) => value === "true") + .transform((value) => value === "true"), + version: z + .string() + .trim() + .optional() + .transform((value) => value === undefined ? undefined : parseInt(value, 10)) + .refine((value) => value === undefined || !isNaN(value), { + message: "Version must be a number", + }) }) }); @@ -318,7 +326,15 @@ export const GetSecretByNameV3 = z.object({ include_imports: z .enum(["true", "false"]) .default("true") - .transform((value) => value === "true") + .transform((value) => value === "true"), + version: z + .string() + .trim() + .optional() + .transform((value) => value === undefined ? undefined : parseInt(value, 10)) + .refine((value) => value === undefined || !isNaN(value), { + message: "Version must be a number", + }) }), params: z.object({ secretName: z.string().trim()