From 20069f450eebf7fcef6aeef47fd323b9ef66fd5c Mon Sep 17 00:00:00 2001 From: yogesh1801 Date: Wed, 3 Jul 2024 22:41:02 +0530 Subject: [PATCH 1/3] extracted functions to common --- .../src/common/get-environmets-of-project.ts | 37 +++++ apps/api/src/common/get-secrets-of-project.ts | 151 ++++++++++++++++++ .../src/common/get-variables-of-project.ts | 122 ++++++++++++++ 3 files changed, 310 insertions(+) create mode 100644 apps/api/src/common/get-environmets-of-project.ts create mode 100644 apps/api/src/common/get-secrets-of-project.ts create mode 100644 apps/api/src/common/get-variables-of-project.ts diff --git a/apps/api/src/common/get-environmets-of-project.ts b/apps/api/src/common/get-environmets-of-project.ts new file mode 100644 index 00000000..6acb6c8d --- /dev/null +++ b/apps/api/src/common/get-environmets-of-project.ts @@ -0,0 +1,37 @@ +import { PrismaService } from "src/prisma/prisma.service"; +import { AuthorityCheckerService } from "./authority-checker.service"; +import { Authority, Project, User } from "@prisma/client"; + +export default async function getEnvironmentsOfProject( + prisma: PrismaService, + authorityCheckerService: AuthorityCheckerService, + user: User, + projectId: Project['id'], + sort: string, + order: string, + search: string +) { + // Check authority + await authorityCheckerService.checkAuthorityOverProject({ + userId: user.id, + entity: { id: projectId }, + authority: Authority.READ_ENVIRONMENT, + prisma + }); + + // Get the environments + return await prisma.environment.findMany({ + where: { + projectId, + name: { + contains: search + } + }, + include: { + lastUpdatedBy: true + }, + orderBy: { + [sort]: order + } + }); +} diff --git a/apps/api/src/common/get-secrets-of-project.ts b/apps/api/src/common/get-secrets-of-project.ts new file mode 100644 index 00000000..008b9790 --- /dev/null +++ b/apps/api/src/common/get-secrets-of-project.ts @@ -0,0 +1,151 @@ +import { User, Project, Authority, Secret, Environment, SecretVersion } from "@prisma/client" +import { PrismaService } from "src/prisma/prisma.service" +import { AuthorityCheckerService } from "./authority-checker.service" +import { decrypt } from "./decrypt" +import { BadRequestException, NotFoundException } from "@nestjs/common" + +async function checkAutoDecrypt(decryptValue: boolean, project: Project) { + // Check if the project is allowed to store the private key + if (decryptValue && !project.storePrivateKey) { + throw new BadRequestException( + `Cannot decrypt secret values as the project does not store the private key` + ) + } + + // Check if the project has a private key. This is just to ensure that we don't run into any + // problems while decrypting the secret + if (decryptValue && !project.privateKey) { + throw new NotFoundException( + `Cannot decrypt secret values as the project does not have a private key` + ) + } +} + +export default async function getAllSecretsOfProject( + prisma: PrismaService, + authorityCheckerService: AuthorityCheckerService, + user: User, + projectId: Project['id'], + decryptValue: boolean, + page: number, + limit: number, + sort: string, + order: string, + search: string +) { + // Fetch the project + const project = + await authorityCheckerService.checkAuthorityOverProject({ + userId: user.id, + entity: { id: projectId }, + authority: Authority.READ_SECRET, + prisma: prisma + }) + + // Check if the secret values can be decrypted + await checkAutoDecrypt(decryptValue, project) + + const secrets = await prisma.secret.findMany({ + where: { + projectId, + name: { + contains: search + } + }, + include: { + lastUpdatedBy: { + select: { + id: true, + name: true + } + } + }, + skip: page * limit, + take: limit, + orderBy: { + [sort]: order + } + }) + + const secretsWithEnvironmentalValues = new Map< + Secret['id'], + { + secret: Secret + values: { + environment: { + name: Environment['name'] + id: Environment['id'] + } + value: SecretVersion['value'] + version: SecretVersion['version'] + }[] + } + >() + + // Find all the environments for this project + const environments = await prisma.environment.findMany({ + where: { + projectId + } + }) + const environmentIds = new Map( + environments.map((env) => [env.id, env.name]) + ) + + for (const secret of secrets) { + // Make a copy of the environment IDs + const envIds = new Map(environmentIds) + let iterations = envIds.size + + // Find the latest version for each environment + while (iterations--) { + const latestVersion = await prisma.secretVersion.findFirst({ + where: { + secretId: secret.id, + environmentId: { + in: Array.from(envIds.keys()) + } + }, + orderBy: { + version: 'desc' + } + }) + + if (!latestVersion) continue + + if (secretsWithEnvironmentalValues.has(secret.id)) { + secretsWithEnvironmentalValues.get(secret.id).values.push({ + environment: { + id: latestVersion.environmentId, + name: envIds.get(latestVersion.environmentId) + }, + value: decryptValue + ? await decrypt(project.privateKey, latestVersion.value) + : latestVersion.value, + version: latestVersion.version + }) + } else { + secretsWithEnvironmentalValues.set(secret.id, { + secret, + values: [ + { + environment: { + id: latestVersion.environmentId, + name: envIds.get(latestVersion.environmentId) + }, + value: decryptValue + ? await decrypt(project.privateKey, latestVersion.value) + : latestVersion.value, + version: latestVersion.version + } + ] + }) + } + + envIds.delete(latestVersion.environmentId) + } + } + + return Array.from(secretsWithEnvironmentalValues.values()) +} + diff --git a/apps/api/src/common/get-variables-of-project.ts b/apps/api/src/common/get-variables-of-project.ts new file mode 100644 index 00000000..78f4bc19 --- /dev/null +++ b/apps/api/src/common/get-variables-of-project.ts @@ -0,0 +1,122 @@ +import { Authority, Environment, Project, User, Variable, VariableVersion } from "@prisma/client" +import { PrismaService } from "src/prisma/prisma.service" +import { AuthorityCheckerService } from "./authority-checker.service" + +export default async function getAllVariablesOfProject( + prisma: PrismaService, + authorityCheckerService: AuthorityCheckerService, + user: User, + projectId: Project['id'], + page: number, + limit: number, + sort: string, + order: string, + search: string +) { + // Check if the user has the required authorities in the project + await authorityCheckerService.checkAuthorityOverProject({ + userId: user.id, + entity: { id: projectId }, + authority: Authority.READ_VARIABLE, + prisma: prisma + }) + + const variables = await prisma.variable.findMany({ + where: { + projectId, + name: { + contains: search + } + }, + include: { + lastUpdatedBy: { + select: { + id: true, + name: true + } + } + }, + skip: page * limit, + take: limit, + orderBy: { + [sort]: order + } + }) + + const variablesWithEnvironmentalValues = new Map< + Variable['id'], + { + variable: Variable + values: { + environment: { + name: Environment['name'] + id: Environment['id'] + } + value: VariableVersion['value'] + version: VariableVersion['version'] + }[] + } + >() + + // Find all the environments for this project + const environments = await prisma.environment.findMany({ + where: { + projectId + } + }) + const environmentIds = new Map( + environments.map((env) => [env.id, env.name]) + ) + + for (const variable of variables) { + // Make a copy of the environment IDs + const envIds = new Map(environmentIds) + let iterations = envIds.size + + // Find the latest version for each environment + while (iterations--) { + const latestVersion = await prisma.variableVersion.findFirst({ + where: { + variableId: variable.id, + environmentId: { + in: Array.from(envIds.keys()) + } + }, + orderBy: { + version: 'desc' + } + }) + + if (!latestVersion) continue + + if (variablesWithEnvironmentalValues.has(variable.id)) { + variablesWithEnvironmentalValues.get(variable.id).values.push({ + environment: { + id: latestVersion.environmentId, + name: envIds.get(latestVersion.environmentId) + }, + value: latestVersion.value, + version: latestVersion.version + }) + } else { + variablesWithEnvironmentalValues.set(variable.id, { + variable, + values: [ + { + environment: { + id: latestVersion.environmentId, + name: envIds.get(latestVersion.environmentId) + }, + value: latestVersion.value, + version: latestVersion.version + } + ] + }) + } + + envIds.delete(latestVersion.environmentId) + } + } + + return Array.from(variablesWithEnvironmentalValues.values()) +} \ No newline at end of file From efb8cadeacd9f96dd2fffa702ea6b831c6700489 Mon Sep 17 00:00:00 2001 From: yogesh1801 Date: Wed, 3 Jul 2024 22:44:12 +0530 Subject: [PATCH 2/3] extracted functions to common --- .../src/common/get-environmets-of-project.ts | 10 +++--- apps/api/src/common/get-secrets-of-project.ts | 35 ++++++++++--------- .../src/common/get-variables-of-project.ts | 19 ++++++---- 3 files changed, 36 insertions(+), 28 deletions(-) diff --git a/apps/api/src/common/get-environmets-of-project.ts b/apps/api/src/common/get-environmets-of-project.ts index 6acb6c8d..205e619f 100644 --- a/apps/api/src/common/get-environmets-of-project.ts +++ b/apps/api/src/common/get-environmets-of-project.ts @@ -1,6 +1,6 @@ -import { PrismaService } from "src/prisma/prisma.service"; -import { AuthorityCheckerService } from "./authority-checker.service"; -import { Authority, Project, User } from "@prisma/client"; +import { PrismaService } from 'src/prisma/prisma.service' +import { AuthorityCheckerService } from './authority-checker.service' +import { Authority, Project, User } from '@prisma/client' export default async function getEnvironmentsOfProject( prisma: PrismaService, @@ -17,7 +17,7 @@ export default async function getEnvironmentsOfProject( entity: { id: projectId }, authority: Authority.READ_ENVIRONMENT, prisma - }); + }) // Get the environments return await prisma.environment.findMany({ @@ -33,5 +33,5 @@ export default async function getEnvironmentsOfProject( orderBy: { [sort]: order } - }); + }) } diff --git a/apps/api/src/common/get-secrets-of-project.ts b/apps/api/src/common/get-secrets-of-project.ts index 008b9790..9b1f4cbe 100644 --- a/apps/api/src/common/get-secrets-of-project.ts +++ b/apps/api/src/common/get-secrets-of-project.ts @@ -1,8 +1,15 @@ -import { User, Project, Authority, Secret, Environment, SecretVersion } from "@prisma/client" -import { PrismaService } from "src/prisma/prisma.service" -import { AuthorityCheckerService } from "./authority-checker.service" -import { decrypt } from "./decrypt" -import { BadRequestException, NotFoundException } from "@nestjs/common" +import { + User, + Project, + Authority, + Secret, + Environment, + SecretVersion +} from '@prisma/client' +import { PrismaService } from 'src/prisma/prisma.service' +import { AuthorityCheckerService } from './authority-checker.service' +import { decrypt } from './decrypt' +import { BadRequestException, NotFoundException } from '@nestjs/common' async function checkAutoDecrypt(decryptValue: boolean, project: Project) { // Check if the project is allowed to store the private key @@ -34,13 +41,12 @@ export default async function getAllSecretsOfProject( search: string ) { // Fetch the project - const project = - await authorityCheckerService.checkAuthorityOverProject({ - userId: user.id, - entity: { id: projectId }, - authority: Authority.READ_SECRET, - prisma: prisma - }) + const project = await authorityCheckerService.checkAuthorityOverProject({ + userId: user.id, + entity: { id: projectId }, + authority: Authority.READ_SECRET, + prisma: prisma + }) // Check if the secret values can be decrypted await checkAutoDecrypt(decryptValue, project) @@ -88,9 +94,7 @@ export default async function getAllSecretsOfProject( projectId } }) - const environmentIds = new Map( - environments.map((env) => [env.id, env.name]) - ) + const environmentIds = new Map(environments.map((env) => [env.id, env.name])) for (const secret of secrets) { // Make a copy of the environment IDs @@ -148,4 +152,3 @@ export default async function getAllSecretsOfProject( return Array.from(secretsWithEnvironmentalValues.values()) } - diff --git a/apps/api/src/common/get-variables-of-project.ts b/apps/api/src/common/get-variables-of-project.ts index 78f4bc19..061f8c2a 100644 --- a/apps/api/src/common/get-variables-of-project.ts +++ b/apps/api/src/common/get-variables-of-project.ts @@ -1,6 +1,13 @@ -import { Authority, Environment, Project, User, Variable, VariableVersion } from "@prisma/client" -import { PrismaService } from "src/prisma/prisma.service" -import { AuthorityCheckerService } from "./authority-checker.service" +import { + Authority, + Environment, + Project, + User, + Variable, + VariableVersion +} from '@prisma/client' +import { PrismaService } from 'src/prisma/prisma.service' +import { AuthorityCheckerService } from './authority-checker.service' export default async function getAllVariablesOfProject( prisma: PrismaService, @@ -64,9 +71,7 @@ export default async function getAllVariablesOfProject( projectId } }) - const environmentIds = new Map( - environments.map((env) => [env.id, env.name]) - ) + const environmentIds = new Map(environments.map((env) => [env.id, env.name])) for (const variable of variables) { // Make a copy of the environment IDs @@ -119,4 +124,4 @@ export default async function getAllVariablesOfProject( } return Array.from(variablesWithEnvironmentalValues.values()) -} \ No newline at end of file +} From 271c991c8157316587f82643ba8427e49b6b4698 Mon Sep 17 00:00:00 2001 From: yogesh1801 Date: Wed, 3 Jul 2024 23:35:38 +0530 Subject: [PATCH 3/3] added the calls --- apps/api/src/common/get-secrets-of-project.ts | 4 -- .../src/common/get-variables-of-project.ts | 4 -- .../src/project/service/project.service.ts | 48 ++++++++++++++++++- 3 files changed, 47 insertions(+), 9 deletions(-) diff --git a/apps/api/src/common/get-secrets-of-project.ts b/apps/api/src/common/get-secrets-of-project.ts index 9b1f4cbe..750c3b28 100644 --- a/apps/api/src/common/get-secrets-of-project.ts +++ b/apps/api/src/common/get-secrets-of-project.ts @@ -34,8 +34,6 @@ export default async function getAllSecretsOfProject( user: User, projectId: Project['id'], decryptValue: boolean, - page: number, - limit: number, sort: string, order: string, search: string @@ -66,8 +64,6 @@ export default async function getAllSecretsOfProject( } } }, - skip: page * limit, - take: limit, orderBy: { [sort]: order } diff --git a/apps/api/src/common/get-variables-of-project.ts b/apps/api/src/common/get-variables-of-project.ts index 061f8c2a..1d42c360 100644 --- a/apps/api/src/common/get-variables-of-project.ts +++ b/apps/api/src/common/get-variables-of-project.ts @@ -14,8 +14,6 @@ export default async function getAllVariablesOfProject( authorityCheckerService: AuthorityCheckerService, user: User, projectId: Project['id'], - page: number, - limit: number, sort: string, order: string, search: string @@ -43,8 +41,6 @@ export default async function getAllVariablesOfProject( } } }, - skip: page * limit, - take: limit, orderBy: { [sort]: order } diff --git a/apps/api/src/project/service/project.service.ts b/apps/api/src/project/service/project.service.ts index 18253372..a2c5f12f 100644 --- a/apps/api/src/project/service/project.service.ts +++ b/apps/api/src/project/service/project.service.ts @@ -29,6 +29,9 @@ import createEvent from '../../common/create-event' import { ProjectWithSecrets } from '../project.types' import { AuthorityCheckerService } from '../../common/authority-checker.service' import { ForkProject } from '../dto/fork.project/fork.project' +import getEnvironmentsOfProject from 'src/common/get-environmets-of-project' +import getAllVariablesOfProject from 'src/common/get-variables-of-project' +import getAllSecretsOfProject from 'src/common/get-secrets-of-project' @Injectable() export class ProjectService { @@ -638,7 +641,7 @@ export class ProjectService { prisma: this.prisma }) - return ( + const projects = ( await this.prisma.project.findMany({ skip: page * limit, take: limit, @@ -669,6 +672,49 @@ export class ProjectService { } }) ).map((project) => excludeFields(project, 'privateKey', 'publicKey')) + + const response = await Promise.all( + projects.map(async (project) => { + const totalenvironmets = await getEnvironmentsOfProject( + this.prisma, + this.authorityCheckerService, + user, + project.id, + 'name', + 'asc', + '' + ) + const totalvariables = await getAllVariablesOfProject( + this.prisma, + this.authorityCheckerService, + user, + project.id, + 'name', + 'asc', + '' + ) + const totalsecrets = await getAllSecretsOfProject( + this.prisma, + this.authorityCheckerService, + user, + project.id, + false, + 'name', + 'asc', + '' + ) + + // Append counts to the project object + return { + ...project, + totalenvironmets, + totalvariables, + totalsecrets + } + }) + ) + + return response } private async projectExists(