diff --git a/backend-pg/src/@types/knex.d.ts b/backend-pg/src/@types/knex.d.ts index 56ef68760..05ce1e59d 100644 --- a/backend-pg/src/@types/knex.d.ts +++ b/backend-pg/src/@types/knex.d.ts @@ -83,23 +83,23 @@ import { TSamlConfigs, TSamlConfigsInsert, TSamlConfigsUpdate, - TSapApprovers, - TSapApproversInsert, - TSapApproversUpdate, - TSaRequestSecrets, - TSaRequestSecretsInsert, - TSaRequestSecretsUpdate, - TSaRequestSecretTags, - TSaRequestSecretTagsInsert, - TSaRequestSecretTagsUpdate, - TSarReviewers, - TSarReviewersInsert, - TSarReviewersUpdate, TSecretApprovalPolicies, + TSecretApprovalPoliciesApprovers, + TSecretApprovalPoliciesApproversInsert, + TSecretApprovalPoliciesApproversUpdate, TSecretApprovalPoliciesInsert, TSecretApprovalPoliciesUpdate, TSecretApprovalRequests, + TSecretApprovalRequestSecretTags, + TSecretApprovalRequestSecretTagsInsert, + TSecretApprovalRequestSecretTagsUpdate, TSecretApprovalRequestsInsert, + TSecretApprovalRequestsReviewers, + TSecretApprovalRequestsReviewersInsert, + TSecretApprovalRequestsReviewersUpdate, + TSecretApprovalRequestsSecrets, + TSecretApprovalRequestsSecretsInsert, + TSecretApprovalRequestsSecretsUpdate, TSecretApprovalRequestsUpdate, TSecretBlindIndexes, TSecretBlindIndexesInsert, @@ -166,7 +166,8 @@ import { TUsersUpdate, TWebhooks, TWebhooksInsert, - TWebhooksUpdate} from "@app/db/schemas"; + TWebhooksUpdate +} from "@app/db/schemas"; declare module "knex/types/tables" { interface Tables { @@ -327,9 +328,9 @@ declare module "knex/types/tables" { TSecretApprovalPoliciesUpdate >; [TableName.SecretApprovalPolicyApprover]: Knex.CompositeTableType< - TSapApprovers, - TSapApproversInsert, - TSapApproversUpdate + TSecretApprovalPoliciesApprovers, + TSecretApprovalPoliciesApproversInsert, + TSecretApprovalPoliciesApproversUpdate >; [TableName.SecretApprovalRequest]: Knex.CompositeTableType< TSecretApprovalRequests, @@ -337,19 +338,19 @@ declare module "knex/types/tables" { TSecretApprovalRequestsUpdate >; [TableName.SecretApprovalRequestReviewer]: Knex.CompositeTableType< - TSarReviewers, - TSarReviewersInsert, - TSarReviewersUpdate + TSecretApprovalRequestsReviewers, + TSecretApprovalRequestsReviewersInsert, + TSecretApprovalRequestsReviewersUpdate >; [TableName.SecretApprovalRequestSecret]: Knex.CompositeTableType< - TSaRequestSecrets, - TSaRequestSecretsInsert, - TSaRequestSecretsUpdate + TSecretApprovalRequestsSecrets, + TSecretApprovalRequestsSecretsInsert, + TSecretApprovalRequestsSecretsUpdate >; [TableName.SecretApprovalRequestSecretTag]: Knex.CompositeTableType< - TSaRequestSecretTags, - TSaRequestSecretTagsInsert, - TSaRequestSecretTagsUpdate + TSecretApprovalRequestSecretTags, + TSecretApprovalRequestSecretTagsInsert, + TSecretApprovalRequestSecretTagsUpdate >; [TableName.SecretRotation]: Knex.CompositeTableType< TSecretRotations, diff --git a/backend-pg/src/ee/routes/v1/secret-approval-request-router.ts b/backend-pg/src/ee/routes/v1/secret-approval-request-router.ts index 6abdaff5c..a745a9b04 100644 --- a/backend-pg/src/ee/routes/v1/secret-approval-request-router.ts +++ b/backend-pg/src/ee/routes/v1/secret-approval-request-router.ts @@ -5,6 +5,7 @@ import { SecretApprovalRequestsSchema, SecretApprovalRequestsSecretsSchema, SecretsSchema, + SecretTagsSchema, SecretVersionsSchema } from "@app/db/schemas"; import { EventType } from "@app/ee/services/audit-log/audit-log-types"; @@ -90,77 +91,6 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv } }); - server.route({ - url: "/:id", - method: "GET", - schema: { - params: z.object({ - id: z.string() - }), - response: { - 200: z.object({ - approval: SecretApprovalRequestsSchema.merge( - z.object({ - // secretPath: z.string(), - policy: z.object({ - id: z.string(), - name: z.string(), - approvals: z.number(), - approvers: z.string().array(), - secretPath: z.string().optional().nullable() - }), - environment: z.string(), - reviewers: z.object({ member: z.string(), status: z.string() }).array(), - approvers: z.string().array(), - secretPath: z.string(), - commits: SecretApprovalRequestsSecretsSchema.omit({ secretBlindIndex: true }) - .merge( - z.object({ - secret: SecretsSchema.pick({ - id: true, - secretKeyIV: true, - secretKeyTag: true, - secretKeyCiphertext: true, - secretValueIV: true, - secretValueTag: true, - secretValueCiphertext: true, - secretCommentIV: true, - secretCommentTag: true, - secretCommentCiphertext: true - }) - .optional() - .nullable(), - secretVersion: SecretVersionsSchema.pick({ - id: true, - secretKeyIV: true, - secretKeyTag: true, - secretKeyCiphertext: true, - secretValueIV: true, - secretValueTag: true, - secretValueCiphertext: true, - secretCommentIV: true, - secretCommentTag: true, - secretCommentCiphertext: true - }).optional() - }) - ) - .array() - }) - ) - }) - } - }, - onRequest: verifyAuth([AuthMode.JWT]), - handler: async (req) => { - const approval = await server.services.secretApprovalRequest.getSecretApprovalDetails({ - actor: req.permission.type, - actorId: req.permission.id, - id: req.params.id - }); - return { approval }; - } - }); - server.route({ url: "/:id/merge", method: "POST", @@ -257,4 +187,92 @@ export const registerSecretApprovalRequestRouter = async (server: FastifyZodProv return { approval }; } }); + + const tagSchema = SecretTagsSchema.pick({ + id: true, + slug: true, + name: true, + color: true + }) + .array() + .optional(); + server.route({ + url: "/:id", + method: "GET", + schema: { + params: z.object({ + id: z.string() + }), + response: { + 200: z.object({ + approval: SecretApprovalRequestsSchema.merge( + z.object({ + // secretPath: z.string(), + policy: z.object({ + id: z.string(), + name: z.string(), + approvals: z.number(), + approvers: z.string().array(), + secretPath: z.string().optional().nullable() + }), + environment: z.string(), + reviewers: z.object({ member: z.string(), status: z.string() }).array(), + approvers: z.string().array(), + secretPath: z.string(), + commits: SecretApprovalRequestsSecretsSchema.omit({ secretBlindIndex: true }) + .merge( + z.object({ + tags: tagSchema, + secret: SecretsSchema.pick({ + id: true, + version: true, + secretKeyIV: true, + secretKeyTag: true, + secretKeyCiphertext: true, + secretValueIV: true, + secretValueTag: true, + secretValueCiphertext: true, + secretCommentIV: true, + secretCommentTag: true, + secretCommentCiphertext: true + }) + .optional() + .nullable(), + secretVersion: SecretVersionsSchema.pick({ + id: true, + version: true, + secretKeyIV: true, + secretKeyTag: true, + secretKeyCiphertext: true, + secretValueIV: true, + secretValueTag: true, + secretValueCiphertext: true, + secretCommentIV: true, + secretCommentTag: true, + secretCommentCiphertext: true + }) + .merge( + z.object({ + tags: tagSchema + }) + ) + .optional() + }) + ) + .array() + }) + ) + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT]), + handler: async (req) => { + const approval = await server.services.secretApprovalRequest.getSecretApprovalDetails({ + actor: req.permission.type, + actorId: req.permission.id, + id: req.params.id + }); + return { approval }; + } + }); }; diff --git a/backend-pg/src/ee/services/secret-approval-request/secret-approval-request-secret-dal.ts b/backend-pg/src/ee/services/secret-approval-request/secret-approval-request-secret-dal.ts index bd2281eb0..a0d25ae65 100644 --- a/backend-pg/src/ee/services/secret-approval-request/secret-approval-request-secret-dal.ts +++ b/backend-pg/src/ee/services/secret-approval-request/secret-approval-request-secret-dal.ts @@ -1,19 +1,35 @@ import { Knex } from "knex"; import { TDbClient } from "@app/db"; -import { TableName } from "@app/db/schemas"; +import { SecretApprovalRequestsSecretsSchema, TableName } from "@app/db/schemas"; import { DatabaseError } from "@app/lib/errors"; -import { ormify, selectAllTableCols } from "@app/lib/knex"; +import { ormify, selectAllTableCols, sqlNestRelationships } from "@app/lib/knex"; -export type TSecretApprovalRequestSecretDALFactory = ReturnType; +export type TSecretApprovalRequestSecretDALFactory = ReturnType< + typeof secretApprovalRequestSecretDALFactory +>; export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => { - const sarSecretOrm = ormify(db, TableName.SecretApprovalRequestSecret); + const secretApprovalRequestSecretOrm = ormify(db, TableName.SecretApprovalRequestSecret); + const secretApprovalRequestSecretTagOrm = ormify(db, TableName.SecretApprovalRequestSecretTag); const findByRequestId = async (requestId: string, tx?: Knex) => { try { - const doc = await (tx || db)(TableName.SecretApprovalRequestSecret) + const doc = await (tx || db)({ + secVerTag: TableName.SecretTag + }) + .from(TableName.SecretApprovalRequestSecret) .where({ requestId }) + .leftJoin( + TableName.SecretApprovalRequestSecretTag, + `${TableName.SecretApprovalRequestSecret}.id`, + `${TableName.SecretApprovalRequestSecretTag}.secretId` + ) + .leftJoin( + TableName.SecretTag, + `${TableName.SecretApprovalRequestSecretTag}.tagId`, + `${TableName.SecretTag}.id` + ) .leftJoin( TableName.Secret, `${TableName.SecretApprovalRequestSecret}.secretId`, @@ -24,7 +40,30 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => { `${TableName.SecretVersion}.id`, `${TableName.SecretApprovalRequestSecret}.secretVersion` ) + .leftJoin( + TableName.SecretVersionTag, + `${TableName.SecretVersionTag}.${TableName.SecretVersion}Id`, + `${TableName.SecretVersion}.id` + ) + .leftJoin( + db.ref(TableName.SecretTag).as("secVerTag"), + `${TableName.SecretVersionTag}.${TableName.SecretTag}Id`, + db.ref("id").withSchema("secVerTag") + ) .select(selectAllTableCols(TableName.SecretApprovalRequestSecret)) + .select({ + secVerTagId: "secVerTag.id", + secVerTagColor: "secVerTag.color", + secVerTagSlug: "secVerTag.slug", + secVerTagName: "secVerTag.name" + }) + .select( + db.ref("id").withSchema(TableName.SecretTag).as("tagId"), + db.ref("id").withSchema(TableName.SecretApprovalRequestSecretTag).as("tagJnId"), + db.ref("color").withSchema(TableName.SecretTag).as("tagColor"), + db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug"), + db.ref("name").withSchema(TableName.SecretTag).as("tagName") + ) .select( db.ref("secretBlindIndex").withSchema(TableName.Secret).as("orgSecBlindIndex"), db.ref("version").withSchema(TableName.Secret).as("orgSecVersion"), @@ -42,7 +81,6 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => { .as("orgSecCommentCiphertext") ) .select( - // db.ref("secretBlindIndex").withSchema(TableName.Secret).as("orgSecBlindInex"), db.ref("version").withSchema(TableName.SecretVersion).as("secVerVersion"), db.ref("secretKeyIV").withSchema(TableName.SecretVersion).as("secVerKeyIV"), db.ref("secretKeyTag").withSchema(TableName.SecretVersion).as("secVerKeyTag"), @@ -63,67 +101,119 @@ export const secretApprovalRequestSecretDALFactory = (db: TDbClient) => { .withSchema(TableName.SecretVersion) .as("secVerCommentCiphertext") ); - return doc.map( - ({ - orgSecKeyIV, - orgSecKeyTag, - orgSecValueIV, - orgSecVersion, - orgSecValueTag, - orgSecCommentIV, - orgSecBlindIndex, - orgSecCommentTag, - orgSecKeyCiphertext, - orgSecValueCiphertext, - orgSecCommentCiphertext, - secVerCommentIV, - secVerCommentCiphertext, - secVerCommentTag, - secVerValueCiphertext, - secVerKeyIV, - secVerKeyTag, - secVerValueIV, - secVerVersion, - secVerValueTag, - secVerKeyCiphertext, - ...el - }) => ({ - ...el, - secret: el.secretId - ? { - id: el.secretId, - version: orgSecVersion, - secretBlindIndex: orgSecBlindIndex, - secretKeyIV: orgSecKeyIV, - secretKeyTag: orgSecKeyTag, - secretKeyCiphertext: orgSecKeyCiphertext, - secretValueIV: orgSecValueIV, - secretValueTag: orgSecValueTag, - secretValueCiphertext: orgSecValueCiphertext, - secretCommentIV: orgSecCommentIV, - secretCommentTag: orgSecCommentTag, - secretCommentCiphertext: orgSecCommentCiphertext + const formatedDoc = sqlNestRelationships({ + data: doc, + key: "id", + parentMapper: (data) => + SecretApprovalRequestsSecretsSchema.omit({ secretVersion: true }).parse(data), + childrenMapper: [ + { + key: "tagJnId", + label: "tags" as const, + mapper: ({ tagId: id, tagName: name, tagSlug: slug, tagColor: color }) => ({ + id, + name, + slug, + color + }) + }, + { + key: "secretId", + label: "secret" as const, + mapper: ({ + orgSecKeyIV, + orgSecKeyTag, + orgSecValueIV, + orgSecVersion, + orgSecValueTag, + orgSecCommentIV, + orgSecBlindIndex, + orgSecCommentTag, + orgSecKeyCiphertext, + orgSecValueCiphertext, + orgSecCommentCiphertext, + secretId + }) => + secretId + ? { + id: secretId, + version: orgSecVersion, + secretBlindIndex: orgSecBlindIndex, + secretKeyIV: orgSecKeyIV, + secretKeyTag: orgSecKeyTag, + secretKeyCiphertext: orgSecKeyCiphertext, + secretValueIV: orgSecValueIV, + secretValueTag: orgSecValueTag, + secretValueCiphertext: orgSecValueCiphertext, + secretCommentIV: orgSecCommentIV, + secretCommentTag: orgSecCommentTag, + secretCommentCiphertext: orgSecCommentCiphertext + } + : undefined + }, + { + key: "secretVersion", + label: "secretVersion" as const, + mapper: ({ + secVerCommentIV, + secVerCommentCiphertext, + secVerCommentTag, + secVerValueCiphertext, + secVerKeyIV, + secVerKeyTag, + secVerValueIV, + secretVersion, + secVerValueTag, + secVerKeyCiphertext, + secVerVersion + }) => + secretVersion + ? { + version: secVerVersion, + id: secretVersion, + secretKeyIV: secVerKeyIV, + secretKeyTag: secVerKeyTag, + secretKeyCiphertext: secVerKeyCiphertext, + secretValueIV: secVerValueIV, + secretValueTag: secVerValueTag, + secretValueCiphertext: secVerValueCiphertext, + secretCommentIV: secVerCommentIV, + secretCommentTag: secVerCommentTag, + secretCommentCiphertext: secVerCommentCiphertext + } + : undefined, + childrenMapper: [ + { + key: "secVerTagId", + label: "tags" as const, + mapper: ({ + secVerTagId: id, + secVerTagName: name, + secVerTagSlug: slug, + secVerTagColor: color + }) => ({ + id, + name, + slug, + color + }) } - : undefined, - secretVersion: el.secretVersion - ? { - id: el.secretVersion, - secretKeyIV: secVerKeyIV, - secretKeyTag: secVerKeyTag, - secretKeyCiphertext: secVerKeyCiphertext, - secretValueIV: secVerValueIV, - secretValueTag: secVerValueTag, - secretValueCiphertext: secVerValueCiphertext, - secretCommentIV: secVerCommentIV, - secretCommentTag: secVerCommentTag, - secretCommentCiphertext: secVerCommentCiphertext - } - : undefined - }) - ); + ] + } + ] + }); + return formatedDoc?.map(({ secret, secretVersion, ...el }) => ({ + ...el, + secret: secret?.[0], + secretVersion: secretVersion?.[0] + })); } catch (error) { throw new DatabaseError({ error, name: "FindByRequestId" }); } }; - return { ...sarSecretOrm, findByRequestId }; + return { + ...secretApprovalRequestSecretOrm, + findByRequestId, + insertApprovalSecretTags: secretApprovalRequestSecretTagOrm.insertMany + }; }; 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 f1910a80c..18ee8779a 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 @@ -8,7 +8,7 @@ import { TSecretApprovalRequestsSecretsInsert } from "@app/db/schemas"; import { BadRequestError, UnauthorizedError } from "@app/lib/errors"; -import { groupBy, pick } from "@app/lib/fn"; +import { groupBy, pick, unique } from "@app/lib/fn"; import { alphaNumericNanoId } from "@app/lib/nanoid"; import { ActorType } from "@app/services/auth/auth-type"; import { TSecretBlindIndexDALFactory } from "@app/services/secret/secret-blind-index-dal"; @@ -16,6 +16,7 @@ import { TSecretQueueFactory } from "@app/services/secret/secret-queue"; import { TSecretServiceFactory } from "@app/services/secret/secret-service"; import { TSecretVersionDALFactory } from "@app/services/secret/secret-version-dal"; import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal"; +import { TSecretTagDALFactory } from "@app/services/secret-tag/secret-tag-dal"; import { TPermissionServiceFactory } from "../permission/permission-service"; import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission"; @@ -39,12 +40,13 @@ import { type TSecretApprovalRequestServiceFactoryDep = { permissionService: Pick; secretApprovalRequestDAL: TSecretApprovalRequestDALFactory; - sarSecretDAL: TSecretApprovalRequestSecretDALFactory; - sarReviewerDAL: TSecretApprovalRequestReviewerDALFactory; + secretApprovalRequestSecretDAL: TSecretApprovalRequestSecretDALFactory; + secretApprovalRequestReviewerDAL: TSecretApprovalRequestReviewerDALFactory; folderDAL: Pick< TSecretFolderDALFactory, "findBySecretPath" | "findById" | "findSecretPathByFolderIds" >; + secretTagDAL: Pick; secretBlindIndexDAL: Pick; snapshotService: Pick; secretVersionDAL: Pick; @@ -66,8 +68,9 @@ export type TSecretApprovalRequestServiceFactory = ReturnType< export const secretApprovalRequestServiceFactory = ({ secretApprovalRequestDAL, folderDAL, - sarReviewerDAL, - sarSecretDAL, + secretTagDAL, + secretApprovalRequestReviewerDAL, + secretApprovalRequestSecretDAL, secretBlindIndexDAL, permissionService, snapshotService, @@ -137,7 +140,7 @@ export const secretApprovalRequestServiceFactory = ({ throw new UnauthorizedError({ message: "User has no access" }); } - const secrets = await sarSecretDAL.findByRequestId(secretApprovalRequest.id); + const secrets = await secretApprovalRequestSecretDAL.findByRequestId(secretApprovalRequest.id); const secretPath = await folderDAL.findSecretPathByFolderIds(secretApprovalRequest.projectId, [ secretApprovalRequest.folderId ]); @@ -163,8 +166,8 @@ export const secretApprovalRequestServiceFactory = ({ ) { throw new UnauthorizedError({ message: "User has no access" }); } - const reviewStatus = await sarReviewerDAL.transaction(async (tx) => { - const review = await sarReviewerDAL.findOne( + const reviewStatus = await secretApprovalRequestReviewerDAL.transaction(async (tx) => { + const review = await secretApprovalRequestReviewerDAL.findOne( { requestId: secretApprovalRequest.id, member: membership.id @@ -172,7 +175,7 @@ export const secretApprovalRequestServiceFactory = ({ tx ); if (!review) { - return sarReviewerDAL.create( + return secretApprovalRequestReviewerDAL.create( { status, requestId: secretApprovalRequest.id, @@ -181,7 +184,7 @@ export const secretApprovalRequestServiceFactory = ({ tx ); } - return sarReviewerDAL.updateById(review.id, { status }, tx); + return secretApprovalRequestReviewerDAL.updateById(review.id, { status }, tx); }); return reviewStatus; }; @@ -255,7 +258,9 @@ export const secretApprovalRequestServiceFactory = ({ if (!hasMinApproval) throw new BadRequestError({ message: "Doesn't have minimum approvals needed" }); - const secretApprovalSecrets = await sarSecretDAL.findByRequestId(secretApprovalRequest.id); + const secretApprovalSecrets = await secretApprovalRequestSecretDAL.findByRequestId( + secretApprovalRequest.id + ); if (!secretApprovalSecrets) throw new BadRequestError({ message: "No secrets found" }); const conflicts: Array<{ secretId: string; op: CommitType }> = []; @@ -328,11 +333,12 @@ export const secretApprovalRequestServiceFactory = ({ "skipMultilineEncoding", "secretReminderNote", "secretReminderRepeatDays", - "version", "algorithm", "keyEncoding", "secretBlindIndex" ]), + tags: el?.tags.map(({ id }) => id), + version: 1, type: SecretType.Shared })) }) @@ -344,25 +350,28 @@ export const secretApprovalRequestServiceFactory = ({ tx, inputSecrets: secretUpdationCommits.map((el) => ({ filter: { - id: el.secretId, + id: el.secretId as string, // this null check is already checked at top on conflict strategy type: SecretType.Shared }, - data: pick(el, [ - "secretCommentCiphertext", - "secretCommentTag", - "secretCommentIV", - "secretValueIV", - "secretValueTag", - "secretValueCiphertext", - "secretKeyCiphertext", - "secretKeyTag", - "secretKeyIV", - "metadata", - "skipMultilineEncoding", - "secretReminderNote", - "secretReminderRepeatDays", - "secretBlindIndex" - ]) + data: { + tags: el?.tags.map(({ id }) => id), + ...pick(el, [ + "secretCommentCiphertext", + "secretCommentTag", + "secretCommentIV", + "secretValueIV", + "secretValueTag", + "secretValueCiphertext", + "secretKeyCiphertext", + "secretKeyTag", + "secretKeyIV", + "metadata", + "skipMultilineEncoding", + "secretReminderNote", + "secretReminderRepeatDays", + "secretBlindIndex" + ]) + } })) }) : []; @@ -438,6 +447,7 @@ export const secretApprovalRequestServiceFactory = ({ throw new BadRequestError({ message: "Blind index not found", name: "Update secret" }); const commits: Omit[] = []; + const commitTagIds: Record = {}; // for created secret approval change const createdSecrets = data[CommitType.Create]; if (createdSecrets && createdSecrets?.length) { @@ -452,12 +462,15 @@ export const secretApprovalRequestServiceFactory = ({ ...createdSecrets.map(({ secretName, ...el }) => ({ ...el, op: CommitType.Create as const, - version: 0, + version: 1, secretBlindIndex: keyName2BlindIndex[secretName], algorithm: SecretEncryptionAlgo.AES_256_GCM, keyEncoding: SecretKeyEncoding.BASE64 })) ); + createdSecrets.forEach(({ tagIds, secretName }) => { + if (tagIds?.length) commitTagIds[keyName2BlindIndex[secretName]] = tagIds; + }); } // not secret approval for update operations const updatedSecrets = data[CommitType.Update]; @@ -495,18 +508,21 @@ export const secretApprovalRequestServiceFactory = ({ updatedSecretIds ); commits.push( - ...updatedSecrets.map(({ newSecretName, secretName, ...el }) => { + ...updatedSecrets.map(({ newSecretName, secretName, tagIds, ...el }) => { const secretId = secsGroupedByBlindIndex[keyName2BlindIndex[secretName]][0].id; + const secretBlindIndex = + newSecretName && newKeyName2BlindIndex[newSecretName] + ? newKeyName2BlindIndex?.[secretName] + : keyName2BlindIndex[secretName]; + // add tags + if (tagIds?.length) commitTagIds[keyName2BlindIndex[secretName]] = tagIds; return { ...latestSecretVersions[secretId], ...el, op: CommitType.Update as const, secret: secretId, secretVersion: latestSecretVersions[secretId].id, - secretBlindIndex: - newSecretName && newKeyName2BlindIndex[newSecretName] - ? newKeyName2BlindIndex?.[secretName] - : keyName2BlindIndex[secretName], + secretBlindIndex, version: secsGroupedByBlindIndex[keyName2BlindIndex[secretName]][0].version || 1 }; }) @@ -546,6 +562,11 @@ export const secretApprovalRequestServiceFactory = ({ } if (!commits.length) throw new BadRequestError({ message: "Empty commits" }); + + const tagIds = unique(Object.values(commitTagIds).flat()); + const tags = tagIds.length ? await secretTagDAL.findManyTagsById(projectId, tagIds) : []; + if (tagIds.length !== tags.length) throw new BadRequestError({ message: "Tag not found" }); + const secretApprovalRequest = await secretApprovalRequestDAL.transaction(async (tx) => { const doc = await secretApprovalRequestDAL.create( { @@ -558,7 +579,7 @@ export const secretApprovalRequestServiceFactory = ({ }, tx ); - const approvalCommits = await sarSecretDAL.insertMany( + const approvalCommits = await secretApprovalRequestSecretDAL.insertMany( commits.map( ({ version, @@ -607,6 +628,20 @@ export const secretApprovalRequestServiceFactory = ({ ), tx ); + const commitsGroupByBlindIndex = groupBy(approvalCommits, (i) => i.secretBlindIndex); + if (tagIds.length) { + await secretApprovalRequestSecretDAL.insertApprovalSecretTags( + Object.keys(commitTagIds).flatMap((blindIndex) => + commitTagIds[blindIndex] + ? commitTagIds[blindIndex].map((tagId) => ({ + secretId: commitsGroupByBlindIndex[blindIndex][0].id, + tagId + })) + : [] + ), + tx + ); + } return { ...doc, commits: approvalCommits }; }); return secretApprovalRequest; diff --git a/backend-pg/src/lib/fn/array.ts b/backend-pg/src/lib/fn/array.ts index 4b06774aa..abe8e3466 100644 --- a/backend-pg/src/lib/fn/array.ts +++ b/backend-pg/src/lib/fn/array.ts @@ -16,3 +16,25 @@ export const groupBy = ( }, {} as Record ); + +/** + * Given a list of items returns a new list with only + * unique items. Accepts an optional identity function + * to convert each item in the list to a comparable identity + * value + */ +export const unique = ( + array: readonly T[], + toKey?: (item: T) => K +): T[] => { + const valueMap = array.reduce( + (acc, item) => { + const key = toKey ? toKey(item) : (item as any as string | number | symbol); + if (acc[key]) return acc; + acc[key] = item; + return acc; + }, + {} as Record + ); + return Object.values(valueMap); +}; diff --git a/backend-pg/src/lib/knex/join.ts b/backend-pg/src/lib/knex/join.ts index 965fd3e95..59144fbcc 100644 --- a/backend-pg/src/lib/knex/join.ts +++ b/backend-pg/src/lib/knex/join.ts @@ -72,6 +72,7 @@ const sqlChildMapper = < childrenMapper: C ) => { if (!docsByPk) return; + type Cm = MappedRecord<(typeof childrenMapper)[number]>; childrenMapper.forEach(({ label, mapper, key: childPk, childrenMapper: nestedMappers }) => { // eslint-disable-next-line @@ -79,12 +80,12 @@ const sqlChildMapper = < if (doc?.[childPk] !== null && typeof doc?.[childPk] !== "undefined") { const ck = `${prefix}-${label}-${doc[childPk]}`; + const val = mapper(doc); if (!lookupTable.has(ck)) { - const val = mapper(doc); if (typeof val !== "undefined" && val !== null) docsByPk[pk as keyof P][label].push(val); lookupTable.add(ck); } - if (nestedMappers) { + if (nestedMappers && val) { sqlChildMapper( doc, docsByPk[pk][label as keyof P], diff --git a/backend-pg/src/server/routes/index.ts b/backend-pg/src/server/routes/index.ts index e054ace62..83a247767 100644 --- a/backend-pg/src/server/routes/index.ts +++ b/backend-pg/src/server/routes/index.ts @@ -368,8 +368,9 @@ export const registerRoutes = async ( const sarService = secretApprovalRequestServiceFactory({ permissionService, folderDAL, - sarSecretDAL, - sarReviewerDAL, + secretTagDAL, + secretApprovalRequestSecretDAL: sarSecretDAL, + secretApprovalRequestReviewerDAL: sarReviewerDAL, secretVersionDAL, secretBlindIndexDAL, secretApprovalRequestDAL, diff --git a/backend-pg/src/server/routes/v3/secret-router.ts b/backend-pg/src/server/routes/v3/secret-router.ts index aedc102f5..ee17c3fe6 100644 --- a/backend-pg/src/server/routes/v3/secret-router.ts +++ b/backend-pg/src/server/routes/v3/secret-router.ts @@ -726,7 +726,8 @@ export const registerSecretRouter = async (server: FastifyZodProvider) => { skipMultilineEncoding, secretKeyTag, secretKeyCiphertext, - secretKeyIV + secretKeyIV, + tagIds: tags } ] } diff --git a/backend-pg/src/services/secret/secret-dal.ts b/backend-pg/src/services/secret/secret-dal.ts index 27a7201f5..7031bb995 100644 --- a/backend-pg/src/services/secret/secret-dal.ts +++ b/backend-pg/src/services/secret/secret-dal.ts @@ -99,7 +99,6 @@ export const secretDALFactory = (db: TDbClient) => { .select(db.ref("color").withSchema(TableName.SecretTag).as("tagColor")) .select(db.ref("slug").withSchema(TableName.SecretTag).as("tagSlug")) .select(db.ref("name").withSchema(TableName.SecretTag).as("tagName")); - console.log(JSON.stringify(secs, null, 4)); const data = sqlNestRelationships({ data: secs, key: "id", @@ -117,7 +116,6 @@ export const secretDALFactory = (db: TDbClient) => { } ] }); - console.log(JSON.stringify(data, null, 4)); return data; } catch (error) { throw new DatabaseError({ error, name: "get all secret" }); diff --git a/backend-pg/src/services/secret/secret-service.ts b/backend-pg/src/services/secret/secret-service.ts index 8cf6a4a54..eaf40e13b 100644 --- a/backend-pg/src/services/secret/secret-service.ts +++ b/backend-pg/src/services/secret/secret-service.ts @@ -164,19 +164,17 @@ export const secretServiceFactory = ({ tags !== undefined ? { tags, secretId: newSecrets[i].id } : [] ); if (secsUpdatedTag.length) { - const delTags = await secretTagDAL.deleteTagsManySecret( + await secretTagDAL.deleteTagsManySecret( projectId, secsUpdatedTag.map(({ secretId }) => secretId), tx ); - console.log(delTags); const newSecretTags = secsUpdatedTag.flatMap(({ tags: secretTags = [], secretId }) => secretTags.map((tag) => ({ [`${TableName.SecretTag}Id` as const]: tag, [`${TableName.Secret}Id` as const]: secretId })) ); - console.log(newSecretTags); if (newSecretTags.length) { const secTags = await secretTagDAL.saveTagsToSecret(newSecretTags, tx); const secVersionsGroupBySecId = groupBy(secretVersions, (i) => i.secretId);