diff --git a/backend-pg/src/ee/services/secret-approval-request/secret-approval-request-dal.ts b/backend-pg/src/ee/services/secret-approval-request/secret-approval-request-dal.ts index 27148c46b..b86034c5b 100644 --- a/backend-pg/src/ee/services/secret-approval-request/secret-approval-request-dal.ts +++ b/backend-pg/src/ee/services/secret-approval-request/secret-approval-request-dal.ts @@ -103,35 +103,47 @@ export const secretApprovalRequestDalFactory = (db: TDbClient) => { const findProjectRequestCount = async (projectId: string, membershipId: string, tx?: Knex) => { try { - const doc = await (tx || db)(TableName.SecretApprovalRequest) - .join( - TableName.SecretFolder, - `${TableName.SecretApprovalRequest}.folderId`, - `${TableName.SecretFolder}.id` + const docs = await (tx || db) + .with( + "temp", + (tx || db)(TableName.SecretApprovalRequest) + .join( + TableName.SecretFolder, + `${TableName.SecretApprovalRequest}.folderId`, + `${TableName.SecretFolder}.id` + ) + .join( + TableName.Environment, + `${TableName.SecretFolder}.envId`, + `${TableName.Environment}.id` + ) + .join( + TableName.SapApprover, + `${TableName.SecretApprovalRequest}.policyId`, + `${TableName.SapApprover}.policyId` + ) + .where({ projectId }) + .andWhere((bd) => + bd + .where(`${TableName.SapApprover}.approverId`, membershipId) + .orWhere(`${TableName.SecretApprovalRequest}.committerId`, membershipId) + ) + .select("status", `${TableName.SecretApprovalRequest}.id`) + .groupBy(`${TableName.SecretApprovalRequest}.id`, "status") + .count("status") ) - .join( - TableName.Environment, - `${TableName.SecretFolder}.envId`, - `${TableName.Environment}.id` - ) - .join( - TableName.SapApprover, - `${TableName.SecretApprovalRequest}.policyId`, - `${TableName.SapApprover}.policyId` - ) - .where({ projectId }) - .where(`${TableName.SapApprover}.approverId`, membershipId) - .orWhere(`${TableName.SecretApprovalRequest}.committerId`, membershipId) + .select("status") + .from("temp") .groupBy("status") - .count("status") - .select("status"); + .count("status"); + return { open: parseInt( - (doc.find(({ status }) => status === RequestState.Open)?.count as string) || "0", + (docs.find(({ status }) => status === RequestState.Open)?.count as string) || "0", 10 ), closed: parseInt( - (doc.find(({ status }) => status === RequestState.Closed)?.count as string) || "0", + (docs.find(({ status }) => status === RequestState.Closed)?.count as string) || "0", 10 ) }; @@ -141,11 +153,21 @@ export const secretApprovalRequestDalFactory = (db: TDbClient) => { }; const findByProjectId = async ( - { status, limit, offset, projectId, committer, environment, membershipId }: TFindQueryFilter, + { + status, + limit = 20, + offset = 0, + projectId, + committer, + environment, + membershipId + }: TFindQueryFilter, tx?: Knex ) => { try { - const docs = await (tx || db)(TableName.SecretApprovalRequest) + // akhilmhdh: If ever u wanted a 1 to so many relationship connected with pagination + // this is the place u wanna look at. + const query = (tx || db)(TableName.SecretApprovalRequest) .join( TableName.SecretFolder, `${TableName.SecretApprovalRequest}.folderId`, @@ -174,7 +196,7 @@ export const secretApprovalRequestDalFactory = (db: TDbClient) => { .where( stripUndefinedInWhere({ projectId, - slug: environment, + [`${TableName.Environment}.slug` as "slug"]: environment, [`${TableName.SecretApprovalRequest}.status`]: status, committerId: committer }) @@ -190,13 +212,27 @@ export const secretApprovalRequestDalFactory = (db: TDbClient) => { .select(db.ref("status").withSchema(TableName.SarReviewer).as("reviewerStatus")) .select(db.ref("id").withSchema(TableName.SecretApprovalPolicy).as("policyId")) .select(db.ref("name").withSchema(TableName.SecretApprovalPolicy).as("policyName")) + .select( + db.raw( + `DENSE_RANK() OVER (partition by ${TableName.Environment}."projectId" ORDER BY ${TableName.SecretApprovalRequest}."id" DESC) as rank` + ) + ) .select( db.ref("secretPath").withSchema(TableName.SecretApprovalPolicy).as("policySecretPath") ) .select( db.ref("approvals").withSchema(TableName.SecretApprovalPolicy).as("policyApprovals") ) - .select(db.ref("approverId").withSchema(TableName.SapApprover)); + .select(db.ref("approverId").withSchema(TableName.SapApprover)) + .orderBy("createdAt", "desc"); + + const docs = await (tx || db) + .with("w", query) + .select("*") + .from[number]>("w") + .where("w.rank", ">=", offset) + .andWhere("w.rank", "<", offset + limit); + const formatedDoc = sqlNestRelationships({ data: docs, key: "id", diff --git a/backend-pg/src/ee/services/secret-approval-request/secret-approval-request-service.ts b/backend-pg/src/ee/services/secret-approval-request/secret-approval-request-service.ts index 4abc607c8..86262c227 100644 --- a/backend-pg/src/ee/services/secret-approval-request/secret-approval-request-service.ts +++ b/backend-pg/src/ee/services/secret-approval-request/secret-approval-request-service.ts @@ -81,6 +81,7 @@ export const secretApprovalRequestServiceFactory = ({ actorId, projectId ); + const count = await secretApprovalRequestDal.findProjectRequestCount(projectId, membership.id); return count; }; @@ -91,7 +92,9 @@ export const secretApprovalRequestServiceFactory = ({ actor, status, environment, - committer + committer, + limit, + offset }: TListApprovalsDTO) => { if (actor === ActorType.SERVICE) throw new BadRequestError({ message: "Cannot use service token" }); @@ -102,7 +105,9 @@ export const secretApprovalRequestServiceFactory = ({ committer, environment, status, - membershipId: membership.id + membershipId: membership.id, + limit, + offset }); return approvals; }; @@ -332,7 +337,11 @@ export const secretApprovalRequestServiceFactory = ({ projectId, tx, inputSecrets: secretUpdationCommits.map((el) => ({ - ...pick(el, [ + filter: { + id: el.secretId, + type: SecretType.Shared + }, + data: pick(el, [ "secretCommentCiphertext", "secretCommentTag", "secretCommentIV", @@ -346,14 +355,8 @@ export const secretApprovalRequestServiceFactory = ({ "skipMultilineEncoding", "secretReminderNote", "secretReminderRepeatDays", - "version", - "algorithm", - "keyEncoding", "secretBlindIndex" - ]), - version: (el.secret?.version || 0) + 1, - id: el.secretId, - type: SecretType.Shared + ]) })) }) : []; diff --git a/backend-pg/src/ee/services/secret-rotation/secret-rotation-queue/secret-rotation-queue-fn.ts b/backend-pg/src/ee/services/secret-rotation/secret-rotation-queue/secret-rotation-queue-fn.ts index 428501f1f..bcd96e090 100644 --- a/backend-pg/src/ee/services/secret-rotation/secret-rotation-queue/secret-rotation-queue-fn.ts +++ b/backend-pg/src/ee/services/secret-rotation/secret-rotation-queue/secret-rotation-queue-fn.ts @@ -98,7 +98,7 @@ export const secretRotationDbFn = async ({ const db = knex({ client, connection: { - db: database, + database, port, host, user: username, @@ -142,17 +142,17 @@ export const secretRotationHttpSetFn = async ( export const getDbSetQuery = ( db: TDbProviderClients, - variable: { username: string; password: string } + variables: { username: string; password: string } ) => { if (db === TDbProviderClients.Pg) { return { - query: "ALTER USER :username WITH PASSWORD :password", - variable + query: `ALTER USER ?? WITH PASSWORD '${variables.password}'`, + variables: [variables.username] }; } // add more based on client return { - query: "ALTER USER :username IDENTIFIED BY :password", - variable + query: `ALTER USER ?? IDENTIFIED BY '${variables.password}'`, + variables: [variables.username] }; }; diff --git a/backend-pg/src/ee/services/secret-rotation/secret-rotation-queue/secret-rotation-queue-types.ts b/backend-pg/src/ee/services/secret-rotation/secret-rotation-queue/secret-rotation-queue-types.ts index b030b0f53..c40a8da0f 100644 --- a/backend-pg/src/ee/services/secret-rotation/secret-rotation-queue/secret-rotation-queue-types.ts +++ b/backend-pg/src/ee/services/secret-rotation/secret-rotation-queue/secret-rotation-queue-types.ts @@ -22,6 +22,6 @@ export type TSecretRotationDbFn = { database: string; port: number; query: string; - variables: Record; + variables: unknown[]; ca?: string; }; diff --git a/backend-pg/src/ee/services/secret-rotation/secret-rotation-queue/secret-rotation-queue.ts b/backend-pg/src/ee/services/secret-rotation/secret-rotation-queue/secret-rotation-queue.ts index 93788a89f..d31b144c1 100644 --- a/backend-pg/src/ee/services/secret-rotation/secret-rotation-queue/secret-rotation-queue.ts +++ b/backend-pg/src/ee/services/secret-rotation/secret-rotation-queue/secret-rotation-queue.ts @@ -1,10 +1,11 @@ -import { SecretKeyEncoding } from "@app/db/schemas"; +import { SecretKeyEncoding, SecretType } from "@app/db/schemas"; +import { getConfig } from "@app/lib/config/env"; import { encryptSymmetric128BitHexKeyUTF8, infisicalSymmetricDecrypt, infisicalSymmetricEncypt } from "@app/lib/crypto/encryption"; -import { daysToMillisecond } from "@app/lib/dates"; +import { daysToMillisecond, secondsToMillis } from "@app/lib/dates"; import { logger } from "@app/lib/logger"; import { alphaNumericNanoId } from "@app/lib/nanoid"; import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue"; @@ -34,7 +35,7 @@ type TSecretRotationQueueFactoryDep = { queue: TQueueServiceFactory; secretRotationDal: TSecretRotationDalFactory; projectBotService: Pick; - secretDal: Pick; + secretDal: Pick; secretVersionDal: Pick; }; @@ -58,13 +59,25 @@ export const secretRotationQueueFactory = ({ secretDal, secretVersionDal }: TSecretRotationQueueFactoryDep) => { - const addToQueue = async (rotationId: string, interval: number) => + const addToQueue = async (rotationId: string, interval: number) => { + const appCfg = getConfig(); queue.queue( QueueName.SecretRotation, QueueJobs.SecretRotation, { rotationId }, - { jobId: rotationId, repeat: { every: daysToMillisecond(interval), immediately: true } } + { + jobId: rotationId, + repeat: { + // on prod it this will be in days, in development this will be second + every: + appCfg.NODE_ENV === "development" + ? secondsToMillis(interval) + : daysToMillisecond(interval), + immediately: true + } + } ); + }; const removeFromQueue = async (rotationId: string) => queue.stopRepeatableJob(QueueName.SecretRotation, rotationId); @@ -127,7 +140,14 @@ export const secretRotationQueueFactory = ({ } // set a random value for new password newCredential.internal.rotated_password = alphaNumericNanoId(32); - const { username, password, host, database, port, ca } = newCredential.inputs; + const { + admin_username: username, + admin_password: password, + host, + database, + port, + ca + } = newCredential.inputs; const dbFunctionArg = { username, password, @@ -149,8 +169,10 @@ export const secretRotationQueueFactory = ({ await secretRotationDbFn({ ...dbFunctionArg, query: "SELECT NOW()", - variables: {} + variables: [] }); + newCredential.outputs.db_username = newCredential.internal.username; + newCredential.outputs.db_password = newCredential.internal.rotated_password; // clean up if (variables.creds.length === 2) variables.creds.pop(); } @@ -172,7 +194,6 @@ export const secretRotationQueueFactory = ({ } } } - variables.creds.unshift({ outputs: newCredential.outputs, internal: newCredential.internal @@ -205,17 +226,16 @@ export const secretRotationQueueFactory = ({ ); const updatedSecrets = await secretDal.bulkUpdate( encryptedSecrets.map(({ secretId, value }) => ({ - id: secretId, - secretValueCiphertext: value.ciphertext, - secretValueIV: value.iv, - secretValueTag: value.tag + // this secret id is validated when user is inserted + filter: { id: secretId, type: SecretType.Shared }, + data: { + secretValueCiphertext: value.ciphertext, + secretValueIV: value.iv, + secretValueTag: value.tag + } })), tx ); - await secretDal.bulkUpdate( - updatedSecrets.map(({ id, version }) => ({ id, version: (version || 0) + 1 })), - tx - ); await secretVersionDal.insertMany( updatedSecrets.map(({ id, updatedAt, createdAt, ...el }) => ({ ...el, @@ -224,7 +244,9 @@ export const secretRotationQueueFactory = ({ tx ); }); + logger.info("Finished logging: rotation id: ", rotationId); } catch (error) { + logger.error(error); if (error instanceof DisableRotationErrors) { if (job.id) { queue.stopRepeatableJob(QueueName.SecretRotation, job.id); diff --git a/backend-pg/src/ee/services/secret-rotation/secret-rotation-service.ts b/backend-pg/src/ee/services/secret-rotation/secret-rotation-service.ts index 0c2f16084..a0a0f19b0 100644 --- a/backend-pg/src/ee/services/secret-rotation/secret-rotation-service.ts +++ b/backend-pg/src/ee/services/secret-rotation/secret-rotation-service.ts @@ -1,11 +1,12 @@ -import { ForbiddenError } from "@casl/ability"; +import { ForbiddenError, subject } from "@casl/ability"; import Ajv from "ajv"; import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption"; import { BadRequestError } from "@app/lib/errors"; import { TProjectPermission } from "@app/lib/types"; import { TProjectDalFactory } from "@app/services/project/project-dal"; -import { TProjectEnvDalFactory } from "@app/services/project-env/project-env-dal"; +import { TSecretDalFactory } from "@app/services/secret/secret-dal"; +import { TSecretFolderDalFactory } from "@app/services/secret-folder/secret-folder-dal"; import { TLicenseServiceFactory } from "../license/license-service"; import { TPermissionServiceFactory } from "../permission/permission-service"; @@ -25,8 +26,9 @@ import { rotationTemplates } from "./templates"; type TSecretRotationServiceFactoryDep = { secretRotationDal: TSecretRotationDalFactory; projectDal: Pick; + folderDal: Pick; + secretDal: Pick; licenseService: Pick; - projectEnvDal: Pick; permissionService: Pick; secretRotationQueue: TSecretRotationQueueFactory; }; @@ -37,10 +39,11 @@ const ajv = new Ajv({ strict: false }); export const secretRotationServiceFactory = ({ secretRotationDal, permissionService, - projectEnvDal, secretRotationQueue, licenseService, - projectDal + projectDal, + folderDal, + secretDal }: TSecretRotationServiceFactoryDep) => { const getProviderTemplates = async ({ actor, actorId, projectId }: TProjectPermission) => { const { permission } = await permissionService.getProjectPermission(actor, actorId, projectId); @@ -71,8 +74,20 @@ export const secretRotationServiceFactory = ({ ProjectPermissionActions.Create, ProjectPermissionSub.SecretRotation ); - const env = await projectEnvDal.findOne({ slug: environment }); - if (!env) throw new BadRequestError({ message: "Environment not found" }); + + const folder = await folderDal.findBySecretPath(projectId, environment, secretPath); + if (!folder) throw new BadRequestError({ message: "Secret path not found" }); + ForbiddenError.from(permission).throwUnlessCan( + ProjectPermissionActions.Edit, + subject(ProjectPermissionSub.Secrets, { environment, secretPath }) + ); + + const selectedSecrets = await secretDal.find({ + folderId: folder.id, + $in: { id: Object.values(outputs) } + }); + if (selectedSecrets.length !== Object.values(outputs).length) + throw new BadRequestError({ message: "Secrets not found" }); const project = await projectDal.findById(projectId); const plan = await licenseService.getPlan(project.orgId); @@ -114,7 +129,7 @@ export const secretRotationServiceFactory = ({ provider, secretPath, interval, - envId: env.id, + envId: folder.envId, encryptedDataTag: encData.tag, encryptedDataIV: encData.iv, encryptedData: encData.ciphertext, @@ -128,7 +143,7 @@ export const secretRotationServiceFactory = ({ Object.entries(outputs).map(([key, secretId]) => ({ key, secretId, rotationId: doc.id })), tx ); - return { ...doc, outputs: outputSecretMapping, environment: env }; + return { ...doc, outputs: outputSecretMapping, environment: folder.environment }; }); return secretRotation; }; diff --git a/backend-pg/src/lib/config/env.ts b/backend-pg/src/lib/config/env.ts index 88249f27d..d9b9dbc4e 100644 --- a/backend-pg/src/lib/config/env.ts +++ b/backend-pg/src/lib/config/env.ts @@ -16,7 +16,7 @@ const envSchema = z REDIS_URL: zpStr(z.string()), HOST: zpStr(z.string().default("localhost")), DB_CONNECTION_URI: zpStr(z.string().describe("Postgres database conntection string")), - NODE_ENV: z.enum(["development", "test", "production"]).default("development"), + NODE_ENV: z.enum(["development", "test", "production"]).default("production"), SALT_ROUNDS: z.coerce.number().default(10), // TODO(akhilmhdh): will be changed to one ENCRYPTION_KEY: zpStr(z.string().optional()), diff --git a/backend-pg/src/lib/dates/index.ts b/backend-pg/src/lib/dates/index.ts index b41a02a06..169203e5b 100644 --- a/backend-pg/src/lib/dates/index.ts +++ b/backend-pg/src/lib/dates/index.ts @@ -1 +1,3 @@ export const daysToMillisecond = (days: number) => days * 24 * 60 * 60 * 1000; + +export const secondsToMillis = (seconds: number) => seconds * 1000; diff --git a/backend-pg/src/server/routes/index.ts b/backend-pg/src/server/routes/index.ts index c9154c2dd..4b4d17b3f 100644 --- a/backend-pg/src/server/routes/index.ts +++ b/backend-pg/src/server/routes/index.ts @@ -381,11 +381,12 @@ export const registerRoutes = async ( }); const secretRotationService = secretRotationServiceFactory({ permissionService, - projectEnvDal, secretRotationDal, secretRotationQueue, projectDal, - licenseService + licenseService, + secretDal, + folderDal }); const integrationService = integrationServiceFactory({ diff --git a/backend-pg/src/services/secret-folder/secret-folder-dal.ts b/backend-pg/src/services/secret-folder/secret-folder-dal.ts index d48824789..091b82696 100644 --- a/backend-pg/src/services/secret-folder/secret-folder-dal.ts +++ b/backend-pg/src/services/secret-folder/secret-folder-dal.ts @@ -1,7 +1,12 @@ import { Knex } from "knex"; import { TDbClient } from "@app/db"; -import { TableName, TSecretFolders, TSecretFoldersUpdate } from "@app/db/schemas"; +import { + TableName, + TProjectEnvironments, + TSecretFolders, + TSecretFoldersUpdate +} from "@app/db/schemas"; import { BadRequestError, DatabaseError } from "@app/lib/errors"; import { ormify, selectAllTableCols } from "@app/lib/knex"; @@ -65,19 +70,20 @@ const sqlFindMultipleFolderByEnvPathQuery = ( .select(selectAllTableCols(TableName.SecretFolder)) .where((wb) => formatedQuery.map(({ secretPath }) => - wb - .orWhereRaw( - `depth = array_position(ARRAY[${secretPath - .map(() => "?") - .join(",")}]::varchar[], ${TableName.SecretFolder}.name,depth)`, - [...secretPath] - ) + wb.orWhereRaw( + `depth = array_position(ARRAY[${secretPath + .map(() => "?") + .join(",")}]::varchar[], ${TableName.SecretFolder}.name,depth)`, + [...secretPath] + ) ) ) .from(TableName.SecretFolder) - .join("parent", (bd)=> - bd.on("parent.id", `${TableName.SecretFolder}.parentId`).andOn("parent.envId",`${TableName.SecretFolder}.envId`) - ) + .join("parent", (bd) => + bd + .on("parent.id", `${TableName.SecretFolder}.parentId`) + .andOn("parent.envId", `${TableName.SecretFolder}.envId`) + ) ); }) .select("*") @@ -146,8 +152,28 @@ const sqlFindFolderByPathQuery = ( .join("parent", "parent.id", `${TableName.SecretFolder}.parentId`) ); }) - .select("*") - .from("parent"); + .from("parent") + .leftJoin( + TableName.Environment, + `${TableName.Environment}.id`, + "parent.envId" + ) + .select< + TSecretFolders & { + depth: number; + path: string; + envId: string; + envSlug: string; + envName: string; + projectId: string; + } + >( + selectAllTableCols("parent" as TableName.SecretFolder), + db.ref("id").withSchema(TableName.Environment).as("envId"), + db.ref("slug").withSchema(TableName.Environment).as("envSlug"), + db.ref("name").withSchema(TableName.Environment).as("envName"), + db.ref("projectId").withSchema(TableName.Environment) + ); }; export type TSecretFolderDalFactory = ReturnType; @@ -169,7 +195,9 @@ export const secretFolderDalFactory = (db: TDbClient) => { if (folder && folder.path !== path) { return; } - return folder; + if (!folder) return; + const { envId: id, envName: name, envSlug: slug, ...el } = folder; + return { ...el, envId: id, environment: { id, name, slug } }; } catch (error) { throw new DatabaseError({ error, name: "Find by secret path" }); } diff --git a/backend-pg/src/services/secret/secret-dal.ts b/backend-pg/src/services/secret/secret-dal.ts index 51b0bb75f..d35e80850 100644 --- a/backend-pg/src/services/secret/secret-dal.ts +++ b/backend-pg/src/services/secret/secret-dal.ts @@ -1,14 +1,7 @@ import { Knex } from "knex"; import { TDbClient } from "@app/db"; -import { - SecretsSchema, - SecretType, - TableName, - TSecrets, - TSecretsInsert, - TSecretsUpdate -} from "@app/db/schemas"; +import { SecretsSchema, SecretType, TableName, TSecrets, TSecretsUpdate } from "@app/db/schemas"; import { BadRequestError, DatabaseError } from "@app/lib/errors"; import { ormify, selectAllTableCols, sqlNestRelationships } from "@app/lib/knex"; @@ -36,13 +29,22 @@ export const secretDalFactory = (db: TDbClient) => { // the idea is to use postgres specific function // insert with id this will cause a conflict then merge the data - const bulkUpdate = async (data: Array, tx?: Knex) => { + const bulkUpdate = async ( + data: Array<{ filter: Partial; data: TSecretsUpdate }>, + tx?: Knex + ) => { try { - const secs = await (tx || db)(TableName.Secret) - .insert(data as TSecretsInsert[]) - .onConflict("id") - .merge() - .returning("*"); + const secs = await Promise.all( + data.map(async ({ filter, data: updateData }) => { + const [doc] = await (tx || db)(TableName.Secret) + .where(filter) + .update(updateData) + .increment("version", 1) + .returning("*"); + if (!doc) throw new BadRequestError({ message: "Failed to update document" }); + return doc; + }) + ); return secs; } catch (error) { throw new DatabaseError({ error, name: "bulk update secret" }); diff --git a/backend-pg/src/services/secret/secret-service.ts b/backend-pg/src/services/secret/secret-service.ts index 8b56f7d97..9e7601797 100644 --- a/backend-pg/src/services/secret/secret-service.ts +++ b/backend-pg/src/services/secret/secret-service.ts @@ -132,20 +132,25 @@ export const secretServiceFactory = ({ projectId }: TFnSecretBulkUpdate) => { const newSecrets = await secretDal.bulkUpdate( - inputSecrets.map(({ tags, ...el }) => ({ ...el, folderId })), + inputSecrets.map(({ filter, data: { tags, ...data } }) => ({ + filter: { ...filter, folderId }, + data + })), tx ); - const secsUpdatedTag = inputSecrets.filter(({ tags }) => Boolean(tags)); + const secsUpdatedTag = inputSecrets.flatMap(({ data: { tags } }, i) => + tags?.length ? { tags, secretId: newSecrets[i].id } : [] + ); if (secsUpdatedTag.length) { await secretTagDal.deleteTagsManySecret( projectId, - secsUpdatedTag.map(({ id }) => id), + secsUpdatedTag.flatMap(({ tags }) => tags), tx ); - const newSecretTags = secsUpdatedTag.flatMap(({ tags: secretTags = [], id }) => + const newSecretTags = secsUpdatedTag.flatMap(({ tags: secretTags = [], secretId }) => secretTags.map((tag) => ({ [`${TableName.SecretTag}Id` as const]: tag, - [`${TableName.Secret}Id` as const]: id + [`${TableName.Secret}Id` as const]: secretId })) ); await secretTagDal.saveTagsToSecret(newSecretTags, tx); @@ -391,26 +396,27 @@ export const secretServiceFactory = ({ projectId, inputSecrets: [ { - id: secrets[0].id, - version: (secrets[0].version || 0) + 1, - ...pick(el, [ - "type", - "secretCommentCiphertext", - "secretCommentTag", - "secretCommentIV", - "secretValueIV", - "secretValueTag", - "secretValueCiphertext", - "secretKeyCiphertext", - "secretKeyTag", - "secretKeyIV", - "metadata", - "skipMultilineEncoding", - "secretReminderNote", - "secretReminderRepeatDays", - "tags" - ]), - secretBlindIndex: newSecretNameBlindIndex || keyName2BlindIndex[secretName] + filter: { id: secrets[0].id }, + data: { + ...pick(el, [ + "type", + "secretCommentCiphertext", + "secretCommentTag", + "secretCommentIV", + "secretValueIV", + "secretValueTag", + "secretValueCiphertext", + "secretKeyCiphertext", + "secretKeyTag", + "secretKeyIV", + "metadata", + "skipMultilineEncoding", + "secretReminderNote", + "secretReminderRepeatDays", + "tags" + ]), + secretBlindIndex: newSecretNameBlindIndex || keyName2BlindIndex[secretName] + } } ], tx @@ -667,7 +673,7 @@ export const secretServiceFactory = ({ if (!blindIndexCfg) throw new BadRequestError({ message: "Blind index not found", name: "Update secret" }); - const { keyName2BlindIndex, secrets: secretsToBeUpdated } = await fnSecretBlindIndexCheck({ + const { keyName2BlindIndex } = await fnSecretBlindIndexCheck({ inputSecrets, folderId, isNew: false, @@ -684,7 +690,6 @@ export const secretServiceFactory = ({ blindIndexCfg }); - const secsGroupedByBlindIndex = groupBy(secretsToBeUpdated, (el) => el.secretBlindIndex); // get all tags const tagIds = inputSecrets.flatMap(({ tags = [] }) => tags); const tags = tagIds.length ? await secretTagDal.findManyTagsById(projectId, tagIds) : []; @@ -694,13 +699,10 @@ export const secretServiceFactory = ({ folderId, projectId, tx, - inputSecrets: inputSecrets.map(({ secretName, newSecretName, ...el }) => { - const { version, updatedAt, ...info } = - secsGroupedByBlindIndex[keyName2BlindIndex[secretName]][0]; - return { + inputSecrets: inputSecrets.map(({ secretName, newSecretName, ...el }) => ({ + filter: { secretBlindIndex: keyName2BlindIndex[secretName], type: SecretType.Shared }, + data: { ...el, - version: (version || 0) + 1, - ...info, folderId, type: SecretType.Shared, secretBlindIndex: @@ -709,8 +711,8 @@ export const secretServiceFactory = ({ : keyName2BlindIndex[secretName], algorithm: SecretEncryptionAlgo.AES_256_GCM, keyEncoding: SecretKeyEncoding.UTF8 - }; - }) + } + })) }) ); @@ -806,7 +808,8 @@ export const secretServiceFactory = ({ includeImports }: TGetSecretsRawDTO) => { const botKey = await projectBotService.getBotKey(projectId); - if (!botKey) throw new BadRequestError({ message: "Project bot not found" }); + if (!botKey) + throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" }); const { secrets, imports } = await getSecrets({ actorId, @@ -842,7 +845,8 @@ export const secretServiceFactory = ({ includeImports }: TGetASecretRawDTO) => { const botKey = await projectBotService.getBotKey(projectId); - if (!botKey) throw new BadRequestError({ message: "Project bot not found" }); + if (!botKey) + throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" }); const secret = await getASecret({ actorId, @@ -870,7 +874,8 @@ export const secretServiceFactory = ({ skipMultilineEncoding }: TCreateSecretRawDTO) => { const botKey = await projectBotService.getBotKey(projectId); - if (!botKey) throw new BadRequestError({ message: "Project bot not found" }); + if (!botKey) + throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" }); const secretKeyEncrypted = encryptSymmetric128BitHexKeyUTF8(secretName, botKey); const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8(secretValue || "", botKey); @@ -914,7 +919,8 @@ export const secretServiceFactory = ({ skipMultilineEncoding }: TUpdateSecretRawDTO) => { const botKey = await projectBotService.getBotKey(projectId); - if (!botKey) throw new BadRequestError({ message: "Project bot not found" }); + if (!botKey) + throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" }); const secretValueEncrypted = encryptSymmetric128BitHexKeyUTF8(secretValue || "", botKey); @@ -948,7 +954,8 @@ export const secretServiceFactory = ({ secretPath }: TDeleteSecretRawDTO) => { const botKey = await projectBotService.getBotKey(projectId); - if (!botKey) throw new BadRequestError({ message: "Project bot not found" }); + if (!botKey) + throw new BadRequestError({ message: "Project bot not found", name: "bot_not_found_error" }); const secret = await deleteSecret({ secretName, diff --git a/backend-pg/src/services/secret/secret-types.ts b/backend-pg/src/services/secret/secret-types.ts index 57012b504..54062309d 100644 --- a/backend-pg/src/services/secret/secret-types.ts +++ b/backend-pg/src/services/secret/secret-types.ts @@ -1,6 +1,12 @@ import { Knex } from "knex"; -import { SecretType, TSecretBlindIndexes, TSecretsInsert, TSecretsUpdate } from "@app/db/schemas"; +import { + SecretType, + TSecretBlindIndexes, + TSecrets, + TSecretsInsert, + TSecretsUpdate +} from "@app/db/schemas"; import { TProjectPermission } from "@app/lib/types"; export type TCreateSecretDTO = { @@ -182,7 +188,7 @@ export type TFnSecretBulkInsert = { export type TFnSecretBulkUpdate = { folderId: string; projectId: string; - inputSecrets: Array; + inputSecrets: { filter: Partial; data: TSecretsUpdate & { tags?: string[] } }[]; tx?: Knex; }; diff --git a/docker-compose.pg.yml b/docker-compose.pg.yml index bed74c4df..f20b5bfaf 100644 --- a/docker-compose.pg.yml +++ b/docker-compose.pg.yml @@ -38,7 +38,7 @@ services: profiles: ["test"] image: postgres:14-alpine ports: - - "5432:5432" + - "5430:5432" environment: POSTGRES_PASSWORD: infisical POSTGRES_USER: infisical @@ -54,6 +54,7 @@ services: env_file: - .env environment: + - NODE_ENV=development - DB_CONNECTION_URI=postgres://infisical:infisical@db/infisical?sslmode=disable volumes: - ./backend-pg/src:/app/src