diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index 5e5e330e1..e151ffb85 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -608,7 +608,9 @@ export const RAW_SECRETS = { skipMultilineEncoding: "Skip multiline encoding for the secret value.", type: "The type of the secret to create.", workspaceId: "The ID of the project to create the secret in.", - tagIds: "The ID of the tags to be attached to the created secret." + tagIds: "The ID of the tags to be attached to the created secret.", + secretReminderRepeatDays: "Interval for secret rotation notifications, measured in days", + secretReminderNote: "Note to be attached in notification email" }, GET: { expand: "Whether or not to expand secret references", @@ -631,7 +633,10 @@ export const RAW_SECRETS = { type: "The type of the secret to update.", projectSlug: "The slug of the project to update the secret in.", workspaceId: "The ID of the project to update the secret in.", - tagIds: "The ID of the tags to be attached to the updated secret." + tagIds: "The ID of the tags to be attached to the updated secret.", + secretReminderRepeatDays: "Interval for secret rotation notifications, measured in days", + secretReminderNote: "Note to be attached in notification email", + newSecretName: "The new name for the secret" }, DELETE: { secretName: "The name of the secret to delete.", diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 59aa8d301..c249580bd 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -703,6 +703,7 @@ export const registerRoutes = async ( }); const secretImportService = secretImportServiceFactory({ licenseService, + projectBotService, projectEnvDAL, folderDAL, permissionService, diff --git a/backend/src/server/routes/sanitizedSchemas.ts b/backend/src/server/routes/sanitizedSchemas.ts index 5b0b754f3..f84cea4b8 100644 --- a/backend/src/server/routes/sanitizedSchemas.ts +++ b/backend/src/server/routes/sanitizedSchemas.ts @@ -63,7 +63,13 @@ export const secretRawSchema = z.object({ type: z.string(), secretKey: z.string(), secretValue: z.string(), - secretComment: z.string().optional() + secretComment: z.string().optional(), + secretReminderNote: z.string().nullable().optional(), + secretReminderRepeatDays: z.number().nullable().optional(), + skipMultilineEncoding: z.boolean().default(false).nullable().optional(), + metadata: z.unknown().nullable().optional(), + createdAt: z.date(), + updatedAt: z.date() }); export const ProjectPermissionSchema = z.object({ diff --git a/backend/src/server/routes/v1/secret-import-router.ts b/backend/src/server/routes/v1/secret-import-router.ts index ca604e738..ec48803f6 100644 --- a/backend/src/server/routes/v1/secret-import-router.ts +++ b/backend/src/server/routes/v1/secret-import-router.ts @@ -8,6 +8,8 @@ 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"; +import { secretRawSchema } from "../sanitizedSchemas"; + export const registerSecretImportRouter = async (server: FastifyZodProvider) => { server.route({ method: "POST", @@ -353,4 +355,48 @@ export const registerSecretImportRouter = async (server: FastifyZodProvider) => return { secrets: importedSecrets }; } }); + + server.route({ + url: "/secrets/raw", + method: "GET", + config: { + rateLimit: secretsLimit + }, + schema: { + querystring: z.object({ + workspaceId: z.string().trim(), + environment: z.string().trim(), + path: z.string().trim().default("/").transform(removeTrailingSlash) + }), + response: { + 200: z.object({ + secrets: z + .object({ + secretPath: z.string(), + environment: z.string(), + environmentInfo: z.object({ + id: z.string(), + name: z.string(), + slug: z.string() + }), + folderId: z.string().optional(), + secrets: secretRawSchema.array() + }) + .array() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const importedSecrets = await server.services.secretImport.getRawSecretsFromImports({ + actorId: req.permission.id, + actor: req.permission.type, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId, + ...req.query, + projectId: req.query.workspaceId + }); + return { secrets: importedSecrets }; + } + }); }; diff --git a/backend/src/server/routes/v3/secret-router.ts b/backend/src/server/routes/v3/secret-router.ts index 2a4f9b464..d19e89547 100644 --- a/backend/src/server/routes/v3/secret-router.ts +++ b/backend/src/server/routes/v3/secret-router.ts @@ -186,7 +186,15 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { 200: z.object({ secrets: secretRawSchema .extend({ - secretPath: z.string().optional() + secretPath: z.string().optional(), + tags: SecretTagsSchema.pick({ + id: true, + slug: true, + name: true, + color: true + }) + .array() + .optional() }) .array(), imports: z @@ -194,7 +202,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { secretPath: z.string(), environment: z.string(), folderId: z.string().optional(), - secrets: secretRawSchema.array() + secrets: secretRawSchema.omit({ createdAt: true, updatedAt: true }).array() }) .array() .optional() @@ -425,7 +433,13 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { secretComment: z.string().trim().optional().default("").describe(RAW_SECRETS.CREATE.secretComment), tagIds: z.string().array().optional().describe(RAW_SECRETS.CREATE.tagIds), skipMultilineEncoding: z.boolean().optional().describe(RAW_SECRETS.CREATE.skipMultilineEncoding), - type: z.nativeEnum(SecretType).default(SecretType.Shared).describe(RAW_SECRETS.CREATE.type) + type: z.nativeEnum(SecretType).default(SecretType.Shared).describe(RAW_SECRETS.CREATE.type), + secretReminderRepeatDays: z + .number() + .optional() + .nullable() + .describe(RAW_SECRETS.CREATE.secretReminderRepeatDays), + secretReminderNote: z.string().optional().nullable().describe(RAW_SECRETS.CREATE.secretReminderNote) }), response: { 200: z.object({ @@ -448,7 +462,9 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { secretValue: req.body.secretValue, skipMultilineEncoding: req.body.skipMultilineEncoding, secretComment: req.body.secretComment, - tagIds: req.body.tagIds + tagIds: req.body.tagIds, + secretReminderNote: req.body.secretReminderNote, + secretReminderRepeatDays: req.body.secretReminderRepeatDays }); await server.services.auditLog.createAuditLog({ @@ -514,7 +530,16 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { .describe(RAW_SECRETS.UPDATE.secretPath), skipMultilineEncoding: z.boolean().optional().describe(RAW_SECRETS.UPDATE.skipMultilineEncoding), type: z.nativeEnum(SecretType).default(SecretType.Shared).describe(RAW_SECRETS.UPDATE.type), - tagIds: z.string().array().optional().describe(RAW_SECRETS.UPDATE.tagIds) + tagIds: z.string().array().optional().describe(RAW_SECRETS.UPDATE.tagIds), + metadata: z.record(z.string()).optional(), + secretReminderNote: z.string().optional().nullable().describe(RAW_SECRETS.UPDATE.secretReminderNote), + secretReminderRepeatDays: z + .number() + .optional() + .nullable() + .describe(RAW_SECRETS.UPDATE.secretReminderRepeatDays), + newSecretName: z.string().min(1).optional().describe(RAW_SECRETS.UPDATE.newSecretName), + secretComment: z.string().optional().describe(RAW_SECRETS.UPDATE.secretComment) }), response: { 200: z.object({ @@ -536,7 +561,12 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { type: req.body.type, secretValue: req.body.secretValue, skipMultilineEncoding: req.body.skipMultilineEncoding, - tagIds: req.body.tagIds + tagIds: req.body.tagIds, + secretReminderRepeatDays: req.body.secretReminderRepeatDays, + secretReminderNote: req.body.secretReminderNote, + metadata: req.body.metadata, + newSecretName: req.body.newSecretName, + secretComment: req.body.secretComment }); await server.services.auditLog.createAuditLog({ @@ -1760,7 +1790,8 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { } ], body: z.object({ - projectSlug: z.string().trim().describe(RAW_SECRETS.CREATE.projectSlug), + projectSlug: z.string().trim().optional().describe(RAW_SECRETS.UPDATE.projectSlug), + workspaceId: z.string().trim().optional().describe(RAW_SECRETS.UPDATE.workspaceId), environment: z.string().trim().describe(RAW_SECRETS.CREATE.environment), secretPath: z .string() @@ -1776,7 +1807,9 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { .transform((val) => (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim())) .describe(RAW_SECRETS.CREATE.secretValue), secretComment: z.string().trim().optional().default("").describe(RAW_SECRETS.CREATE.secretComment), - skipMultilineEncoding: z.boolean().optional().describe(RAW_SECRETS.CREATE.skipMultilineEncoding) + skipMultilineEncoding: z.boolean().optional().describe(RAW_SECRETS.CREATE.skipMultilineEncoding), + metadata: z.record(z.string()).optional(), + tagIds: z.string().array().optional().describe(RAW_SECRETS.CREATE.tagIds) }) .array() .min(1) @@ -1799,6 +1832,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { secretPath, environment, projectSlug, + projectId: req.body.workspaceId, secrets: inputSecrets }); @@ -1849,7 +1883,8 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { } ], body: z.object({ - projectSlug: z.string().trim().describe(RAW_SECRETS.UPDATE.projectSlug), + projectSlug: z.string().trim().optional().describe(RAW_SECRETS.DELETE.projectSlug), + workspaceId: z.string().trim().optional().describe(RAW_SECRETS.DELETE.workspaceId), environment: z.string().trim().describe(RAW_SECRETS.UPDATE.environment), secretPath: z .string() @@ -1865,7 +1900,15 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { .transform((val) => (val.at(-1) === "\n" ? `${val.trim()}\n` : val.trim())) .describe(RAW_SECRETS.UPDATE.secretValue), secretComment: z.string().trim().optional().describe(RAW_SECRETS.UPDATE.secretComment), - skipMultilineEncoding: z.boolean().optional().describe(RAW_SECRETS.UPDATE.skipMultilineEncoding) + skipMultilineEncoding: z.boolean().optional().describe(RAW_SECRETS.UPDATE.skipMultilineEncoding), + newSecretName: z.string().min(1).optional().describe(RAW_SECRETS.UPDATE.newSecretName), + tagIds: z.string().array().optional().describe(RAW_SECRETS.UPDATE.tagIds), + secretReminderNote: z.string().optional().nullable().describe(RAW_SECRETS.UPDATE.secretReminderNote), + secretReminderRepeatDays: z + .number() + .optional() + .nullable() + .describe(RAW_SECRETS.UPDATE.secretReminderRepeatDays) }) .array() .min(1) @@ -1887,6 +1930,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { secretPath, environment, projectSlug, + projectId: req.body.workspaceId, secrets: inputSecrets }); @@ -1937,7 +1981,8 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { } ], body: z.object({ - projectSlug: z.string().trim().describe(RAW_SECRETS.DELETE.projectSlug), + projectSlug: z.string().trim().optional().describe(RAW_SECRETS.DELETE.projectSlug), + workspaceId: z.string().trim().optional().describe(RAW_SECRETS.DELETE.workspaceId), environment: z.string().trim().describe(RAW_SECRETS.DELETE.environment), secretPath: z .string() @@ -1947,7 +1992,8 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { .describe(RAW_SECRETS.DELETE.secretPath), secrets: z .object({ - secretKey: z.string().trim().describe(RAW_SECRETS.DELETE.secretName) + secretKey: z.string().trim().describe(RAW_SECRETS.DELETE.secretName), + type: z.nativeEnum(SecretType).default(SecretType.Shared) }) .array() .min(1) @@ -1969,6 +2015,7 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { environment, projectSlug, secretPath, + projectId: req.body.workspaceId, secrets: inputSecrets }); diff --git a/backend/src/services/secret-import/secret-import-service.ts b/backend/src/services/secret-import/secret-import-service.ts index 237c7cfe4..c20c2345e 100644 --- a/backend/src/services/secret-import/secret-import-service.ts +++ b/backend/src/services/secret-import/secret-import-service.ts @@ -10,8 +10,10 @@ import { getReplicationFolderName } from "@app/ee/services/secret-replication/se import { BadRequestError } from "@app/lib/errors"; import { TProjectDALFactory } from "../project/project-dal"; +import { TProjectBotServiceFactory } from "../project-bot/project-bot-service"; import { TProjectEnvDALFactory } from "../project-env/project-env-dal"; import { TSecretDALFactory } from "../secret/secret-dal"; +import { decryptSecretRaw } from "../secret/secret-fns"; import { TSecretQueueFactory } from "../secret/secret-queue"; import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal"; import { TSecretImportDALFactory } from "./secret-import-dal"; @@ -29,6 +31,7 @@ type TSecretImportServiceFactoryDep = { secretImportDAL: TSecretImportDALFactory; folderDAL: TSecretFolderDALFactory; secretDAL: Pick; + projectBotService: Pick; projectDAL: Pick; projectEnvDAL: TProjectEnvDALFactory; permissionService: Pick; @@ -48,7 +51,8 @@ export const secretImportServiceFactory = ({ projectDAL, secretDAL, secretQueueService, - licenseService + licenseService, + projectBotService }: TSecretImportServiceFactoryDep) => { const createImport = async ({ environment, @@ -449,12 +453,61 @@ export const secretImportServiceFactory = ({ return fnSecretsFromImports({ allowedImports, folderDAL, secretDAL, secretImportDAL }); }; + const getRawSecretsFromImports = async ({ + path: secretPath, + environment, + projectId, + actor, + actorAuthMethod, + actorId, + actorOrgId + }: TGetSecretsFromImportDTO) => { + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); + ForbiddenError.from(permission).throwUnlessCan( + ProjectPermissionActions.Read, + subject(ProjectPermissionSub.Secrets, { environment, secretPath }) + ); + const folder = await folderDAL.findBySecretPath(projectId, environment, secretPath); + if (!folder) return []; + // this will already order by position + // so anything based on this order will also be in right position + const secretImports = await secretImportDAL.find({ folderId: folder.id, isReplication: false }); + + const allowedImports = secretImports.filter(({ importEnv, importPath }) => + permission.can( + ProjectPermissionActions.Read, + subject(ProjectPermissionSub.Secrets, { + environment: importEnv.slug, + secretPath: importPath + }) + ) + ); + + const botKey = await projectBotService.getBotKey(projectId); + if (!botKey) throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" }); + + const importedSecrets = await fnSecretsFromImports({ allowedImports, folderDAL, secretDAL, secretImportDAL }); + return importedSecrets.map((el) => ({ + ...el, + secrets: el.secrets.map((encryptedSecret) => + decryptSecretRaw({ ...encryptedSecret, workspace: projectId, environment, secretPath }, botKey) + ) + })); + }; + return { createImport, updateImport, deleteImport, getImports, getSecretsFromImports, + getRawSecretsFromImports, resyncSecretImportReplication, fnSecretsFromImports }; diff --git a/backend/src/services/secret/secret-fns.ts b/backend/src/services/secret/secret-fns.ts index aa112e6b0..419a41b10 100644 --- a/backend/src/services/secret/secret-fns.ts +++ b/backend/src/services/secret/secret-fns.ts @@ -407,7 +407,12 @@ export const decryptSecretRaw = ( id: secret.id, user: secret.userId, tags: secret.tags, - skipMultilineEncoding: secret.skipMultilineEncoding + skipMultilineEncoding: secret.skipMultilineEncoding, + secretReminderRepeatDays: secret.secretReminderRepeatDays, + secretReminderNote: secret.secretReminderNote, + metadata: secret.metadata, + createdAt: secret.createdAt, + updatedAt: secret.updatedAt }; }; diff --git a/backend/src/services/secret/secret-service.ts b/backend/src/services/secret/secret-service.ts index efe5af03e..39b9ea332 100644 --- a/backend/src/services/secret/secret-service.ts +++ b/backend/src/services/secret/secret-service.ts @@ -1176,7 +1176,9 @@ export const secretServiceFactory = ({ secretValue, secretComment, skipMultilineEncoding, - tagIds + tagIds, + secretReminderNote, + secretReminderRepeatDays }: TCreateSecretRawDTO) => { const botKey = await projectBotService.getBotKey(projectId); if (!botKey) throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" }); @@ -1205,6 +1207,8 @@ export const secretServiceFactory = ({ secretCommentIV: secretCommentEncrypted.iv, secretCommentTag: secretCommentEncrypted.tag, skipMultilineEncoding, + secretReminderRepeatDays, + secretReminderNote, tags: tagIds }); @@ -1223,12 +1227,19 @@ export const secretServiceFactory = ({ secretPath, secretValue, skipMultilineEncoding, - tagIds + tagIds, + secretReminderNote, + secretReminderRepeatDays, + metadata, + secretComment, + newSecretName }: TUpdateSecretRawDTO) => { const botKey = await projectBotService.getBotKey(projectId); if (!botKey) throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" }); const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8(secretValue || "", botKey); + const secretCommentEncrypted = encryptSymmetric128BitHexKeyUTF8(secretComment || "", botKey); + const secretKeyEncrypted = encryptSymmetric128BitHexKeyUTF8(newSecretName || secretName, botKey); const secret = await updateSecret({ secretName, @@ -1244,7 +1255,17 @@ export const secretServiceFactory = ({ secretValueIV: secretValueEncrypted.iv, secretValueTag: secretValueEncrypted.tag, skipMultilineEncoding, - tags: tagIds + tags: tagIds, + metadata, + secretReminderRepeatDays, + secretReminderNote, + newSecretName, + secretKeyIV: secretKeyEncrypted.iv, + secretKeyTag: secretKeyEncrypted.tag, + secretKeyCiphertext: secretKeyEncrypted.ciphertext, + secretCommentIV: secretCommentEncrypted.iv, + secretCommentTag: secretCommentEncrypted.tag, + secretCommentCiphertext: secretCommentEncrypted.ciphertext }); await snapshotService.performSnapshot(secret.folderId); @@ -1283,6 +1304,7 @@ export const secretServiceFactory = ({ const createManySecretsRaw = async ({ actorId, projectSlug, + projectId: optionalProjectId, environment, actor, actorOrgId, @@ -1290,9 +1312,16 @@ export const secretServiceFactory = ({ secretPath, secrets: inputSecrets = [] }: TCreateManySecretRawDTO) => { - const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId); - if (!project) throw new BadRequestError({ message: "Project not found" }); - const projectId = project.id; + if (!projectSlug && !optionalProjectId) + throw new BadRequestError({ message: "Must provide either project slug or projectId" }); + + let projectId = optionalProjectId as string; + // pick either project slug or projectid + if (!optionalProjectId && projectSlug) { + const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId); + if (!project) throw new BadRequestError({ message: "Project not found" }); + projectId = project.id; + } const botKey = await projectBotService.getBotKey(projectId); if (!botKey) throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" }); @@ -1305,24 +1334,28 @@ export const secretServiceFactory = ({ actorId, actorOrgId, actorAuthMethod, - secrets: inputSecrets.map(({ secretComment, secretKey, secretValue, skipMultilineEncoding }) => { - const secretKeyEncrypted = encryptSymmetric128BitHexKeyUTF8(secretKey, botKey); - const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8(secretValue || "", botKey); - const secretCommentEncrypted = encryptSymmetric128BitHexKeyUTF8(secretComment || "", botKey); - return { - secretName: secretKey, - skipMultilineEncoding, - secretKeyCiphertext: secretKeyEncrypted.ciphertext, - secretKeyIV: secretKeyEncrypted.iv, - secretKeyTag: secretKeyEncrypted.tag, - secretValueCiphertext: secretValueEncrypted.ciphertext, - secretValueIV: secretValueEncrypted.iv, - secretValueTag: secretValueEncrypted.tag, - secretCommentCiphertext: secretCommentEncrypted.ciphertext, - secretCommentIV: secretCommentEncrypted.iv, - secretCommentTag: secretCommentEncrypted.tag - }; - }) + secrets: inputSecrets.map( + ({ secretComment, secretKey, metadata, tagIds, secretValue, skipMultilineEncoding }) => { + const secretKeyEncrypted = encryptSymmetric128BitHexKeyUTF8(secretKey, botKey); + const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8(secretValue || "", botKey); + const secretCommentEncrypted = encryptSymmetric128BitHexKeyUTF8(secretComment || "", botKey); + return { + secretName: secretKey, + skipMultilineEncoding, + secretKeyCiphertext: secretKeyEncrypted.ciphertext, + secretKeyIV: secretKeyEncrypted.iv, + secretKeyTag: secretKeyEncrypted.tag, + secretValueCiphertext: secretValueEncrypted.ciphertext, + secretValueIV: secretValueEncrypted.iv, + secretValueTag: secretValueEncrypted.tag, + secretCommentCiphertext: secretCommentEncrypted.ciphertext, + secretCommentIV: secretCommentEncrypted.iv, + secretCommentTag: secretCommentEncrypted.tag, + tags: tagIds, + metadata + }; + } + ) }); return secrets.map((secret) => @@ -1333,6 +1366,7 @@ export const secretServiceFactory = ({ const updateManySecretsRaw = async ({ actorId, projectSlug, + projectId: optionalProjectId, environment, actor, actorOrgId, @@ -1340,9 +1374,15 @@ export const secretServiceFactory = ({ secretPath, secrets: inputSecrets = [] }: TUpdateManySecretRawDTO) => { - const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId); - if (!project) throw new BadRequestError({ message: "Project not found" }); - const projectId = project.id; + if (!projectSlug && !optionalProjectId) + throw new BadRequestError({ message: "Must provide either project slug or projectId" }); + + let projectId = optionalProjectId as string; + if (!optionalProjectId && projectSlug) { + const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId); + if (!project) throw new BadRequestError({ message: "Project not found" }); + projectId = project.id; + } const botKey = await projectBotService.getBotKey(projectId); if (!botKey) throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" }); @@ -1355,25 +1395,40 @@ export const secretServiceFactory = ({ actorId, actorOrgId, actorAuthMethod, - secrets: inputSecrets.map(({ secretComment, secretKey, secretValue, skipMultilineEncoding }) => { - const secretKeyEncrypted = encryptSymmetric128BitHexKeyUTF8(secretKey, botKey); - const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8(secretValue || "", botKey); - const secretCommentEncrypted = encryptSymmetric128BitHexKeyUTF8(secretComment || "", botKey); - return { - secretName: secretKey, - type: SecretType.Shared, + secrets: inputSecrets.map( + ({ + secretComment, + secretKey, + secretValue, skipMultilineEncoding, - secretKeyCiphertext: secretKeyEncrypted.ciphertext, - secretKeyIV: secretKeyEncrypted.iv, - secretKeyTag: secretKeyEncrypted.tag, - secretValueCiphertext: secretValueEncrypted.ciphertext, - secretValueIV: secretValueEncrypted.iv, - secretValueTag: secretValueEncrypted.tag, - secretCommentCiphertext: secretCommentEncrypted.ciphertext, - secretCommentIV: secretCommentEncrypted.iv, - secretCommentTag: secretCommentEncrypted.tag - }; - }) + tagIds: tags, + newSecretName, + secretReminderNote, + secretReminderRepeatDays + }) => { + const secretKeyEncrypted = encryptSymmetric128BitHexKeyUTF8(newSecretName || secretKey, botKey); + const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8(secretValue || "", botKey); + const secretCommentEncrypted = encryptSymmetric128BitHexKeyUTF8(secretComment || "", botKey); + return { + secretName: secretKey, + newSecretName, + tags, + secretReminderRepeatDays, + secretReminderNote, + type: SecretType.Shared, + skipMultilineEncoding, + secretKeyCiphertext: secretKeyEncrypted.ciphertext, + secretKeyIV: secretKeyEncrypted.iv, + secretKeyTag: secretKeyEncrypted.tag, + secretValueCiphertext: secretValueEncrypted.ciphertext, + secretValueIV: secretValueEncrypted.iv, + secretValueTag: secretValueEncrypted.tag, + secretCommentCiphertext: secretCommentEncrypted.ciphertext, + secretCommentIV: secretCommentEncrypted.iv, + secretCommentTag: secretCommentEncrypted.tag + }; + } + ) }); return secrets.map((secret) => @@ -1384,6 +1439,7 @@ export const secretServiceFactory = ({ const deleteManySecretsRaw = async ({ actorId, projectSlug, + projectId: optionalProjectId, environment, actor, actorOrgId, @@ -1391,9 +1447,15 @@ export const secretServiceFactory = ({ secretPath, secrets: inputSecrets = [] }: TDeleteManySecretRawDTO) => { - const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId); - if (!project) throw new BadRequestError({ message: "Project not found" }); - const projectId = project.id; + if (!projectSlug && !optionalProjectId) + throw new BadRequestError({ message: "Must provide either project slug or projectId" }); + + let projectId = optionalProjectId as string; + if (!optionalProjectId && projectSlug) { + const project = await projectDAL.findProjectBySlug(projectSlug, actorOrgId); + if (!project) throw new BadRequestError({ message: "Project not found" }); + projectId = project.id; + } const botKey = await projectBotService.getBotKey(projectId); if (!botKey) throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" }); @@ -1406,7 +1468,7 @@ export const secretServiceFactory = ({ actorId, actorOrgId, actorAuthMethod, - secrets: inputSecrets.map(({ secretKey }) => ({ secretName: secretKey, type: SecretType.Shared })) + secrets: inputSecrets.map(({ secretKey, type = SecretType.Shared }) => ({ secretName: secretKey, type })) }); return secrets.map((secret) => diff --git a/backend/src/services/secret/secret-types.ts b/backend/src/services/secret/secret-types.ts index d806eab11..16bcf6172 100644 --- a/backend/src/services/secret/secret-types.ts +++ b/backend/src/services/secret/secret-types.ts @@ -160,14 +160,16 @@ export type TGetASecretRawDTO = { } & Omit; export type TCreateSecretRawDTO = TProjectPermission & { + secretName: string; secretPath: string; environment: string; - secretName: string; secretValue: string; type: SecretType; tagIds?: string[]; secretComment?: string; skipMultilineEncoding?: boolean; + secretReminderRepeatDays?: number | null; + secretReminderNote?: string | null; }; export type TUpdateSecretRawDTO = TProjectPermission & { @@ -175,11 +177,16 @@ export type TUpdateSecretRawDTO = TProjectPermission & { environment: string; secretName: string; secretValue?: string; + newSecretName?: string; + secretComment?: string; type: SecretType; tagIds?: string[]; skipMultilineEncoding?: boolean; secretReminderRepeatDays?: number | null; secretReminderNote?: string | null; + metadata?: { + source?: string; + }; }; export type TDeleteSecretRawDTO = TProjectPermission & { @@ -191,34 +198,46 @@ export type TDeleteSecretRawDTO = TProjectPermission & { export type TCreateManySecretRawDTO = Omit & { secretPath: string; - projectSlug: string; + projectId?: string; + projectSlug?: string; environment: string; secrets: { secretKey: string; secretValue: string; secretComment?: string; skipMultilineEncoding?: boolean; + tagIds?: string[]; + metadata?: { + source?: string; + }; }[]; }; export type TUpdateManySecretRawDTO = Omit & { secretPath: string; - projectSlug: string; + projectId?: string; + projectSlug?: string; environment: string; secrets: { secretKey: string; + newSecretName?: string; secretValue: string; secretComment?: string; skipMultilineEncoding?: boolean; + tagIds?: string[]; + secretReminderRepeatDays?: number | null; + secretReminderNote?: string | null; }[]; }; export type TDeleteManySecretRawDTO = Omit & { secretPath: string; - projectSlug: string; + projectId?: string; + projectSlug?: string; environment: string; secrets: { secretKey: string; + type?: SecretType; }[]; };