diff --git a/backend-pg/src/@types/fastify.d.ts b/backend-pg/src/@types/fastify.d.ts index c3c75db72..9c3d934a0 100644 --- a/backend-pg/src/@types/fastify.d.ts +++ b/backend-pg/src/@types/fastify.d.ts @@ -34,6 +34,7 @@ import { TProjectKeyServiceFactory } from "@app/services/project-key/project-key import { TProjectMembershipServiceFactory } from "@app/services/project-membership/project-membership-service"; import { TProjectRoleServiceFactory } from "@app/services/project-role/project-role-service"; import { TSecretServiceFactory } from "@app/services/secret/secret-service"; +import { TSecretBlindIndexServiceFactory } from "@app/services/secret-blind-index/secret-blind-index-service"; import { TSecretFolderServiceFactory } from "@app/services/secret-folder/secret-folder-service"; import { TSecretImportServiceFactory } from "@app/services/secret-import/secret-import-service"; import { TSecretTagServiceFactory } from "@app/services/secret-tag/secret-tag-service"; @@ -105,6 +106,7 @@ declare module "fastify" { secretScanning: TSecretScanningServiceFactory; license: TLicenseServiceFactory; trustedIp: TTrustedIpServiceFactory; + secretBlindIndex: TSecretBlindIndexServiceFactory; }; // this is exclusive use for middlewares in which we need to inject data // everywhere else access using service layer 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 18ee8779a..4809bd846 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 @@ -11,10 +11,10 @@ import { BadRequestError, UnauthorizedError } from "@app/lib/errors"; 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"; 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 { TSecretBlindIndexDALFactory } from "@app/services/secret-blind-index/secret-blind-index-dal"; import { TSecretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal"; import { TSecretTagDALFactory } from "@app/services/secret-tag/secret-tag-dal"; diff --git a/backend-pg/src/server/routes/index.ts b/backend-pg/src/server/routes/index.ts index 4d0886426..6b329d3d4 100644 --- a/backend-pg/src/server/routes/index.ts +++ b/backend-pg/src/server/routes/index.ts @@ -74,12 +74,13 @@ import { projectMembershipDALFactory } from "@app/services/project-membership/pr import { projectMembershipServiceFactory } from "@app/services/project-membership/project-membership-service"; import { projectRoleDALFactory } from "@app/services/project-role/project-role-dal"; import { projectRoleServiceFactory } from "@app/services/project-role/project-role-service"; -import { secretBlindIndexDALFactory } from "@app/services/secret/secret-blind-index-dal"; import { secretDALFactory } from "@app/services/secret/secret-dal"; import { secretQueueFactory } from "@app/services/secret/secret-queue"; import { secretServiceFactory } from "@app/services/secret/secret-service"; import { secretVersionDALFactory } from "@app/services/secret/secret-version-dal"; import { secretVersionTagDALFactory } from "@app/services/secret/secret-version-tag-dal"; +import { secretBlindIndexDALFactory } from "@app/services/secret-blind-index/secret-blind-index-dal"; +import { secretBlindIndexServiceFactory } from "@app/services/secret-blind-index/secret-blind-index-service"; import { secretFolderDALFactory } from "@app/services/secret-folder/secret-folder-dal"; import { secretFolderServiceFactory } from "@app/services/secret-folder/secret-folder-service"; import { secretFolderVersionDALFactory } from "@app/services/secret-folder/secret-folder-version-dal"; @@ -356,7 +357,11 @@ export const registerRoutes = async ( smtpService, projectDAL }); - + const secretBlindIndexService = secretBlindIndexServiceFactory({ + permissionService, + secretDAL, + secretBlindIndexDAL + }); const secretService = secretServiceFactory({ folderDAL, secretVersionDAL, @@ -477,7 +482,8 @@ export const registerRoutes = async ( auditLog: auditLogService, secretScanning: secretScanningService, license: licenseService, - trustedIp: trustedIpService + trustedIp: trustedIpService, + secretBlindIndex: secretBlindIndexService }); server.decorate("store", { diff --git a/backend-pg/src/server/routes/v3/index.ts b/backend-pg/src/server/routes/v3/index.ts index 58591a555..6a061a635 100644 --- a/backend-pg/src/server/routes/v3/index.ts +++ b/backend-pg/src/server/routes/v3/index.ts @@ -1,4 +1,5 @@ import { registerLoginRouter } from "./login-router"; +import { registerSecretBlindIndexRouter } from "./secret-blind-index-router"; import { registerSecretRouter } from "./secret-router"; import { registerSignupRouter } from "./signup-router"; import { registerUserRouter } from "./user-router"; @@ -8,4 +9,5 @@ export const registerV3Routes = async (server: FastifyZodProvider) => { await server.register(registerLoginRouter, { prefix: "/auth" }); await server.register(registerUserRouter, { prefix: "/users" }); await server.register(registerSecretRouter, { prefix: "/secrets" }); + await server.register(registerSecretBlindIndexRouter, { prefix: "/workspaces" }); }; diff --git a/backend-pg/src/server/routes/v3/secret-blind-index-router.ts b/backend-pg/src/server/routes/v3/secret-blind-index-router.ts new file mode 100644 index 000000000..643f63c81 --- /dev/null +++ b/backend-pg/src/server/routes/v3/secret-blind-index-router.ts @@ -0,0 +1,93 @@ +import { z } from "zod"; + +import { SecretsSchema } from "@app/db/schemas"; +import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; +import { AuthMode } from "@app/services/auth/auth-type"; + +export const registerSecretBlindIndexRouter = async (server: FastifyZodProvider) => { + server.route({ + url: "/:projectId/secrets/blind-index-status", + method: "GET", + schema: { + params: z.object({ + projectId: z.string().trim() + }), + response: { + 200: z.boolean() + } + }, + onRequest: verifyAuth([AuthMode.JWT]), + handler: async (req) => { + const count = await server.services.secretBlindIndex.getSecretBlindIndexStatus({ + projectId: req.params.projectId, + actorId: req.permission.id, + actor: req.permission.type + }); + return count === 0; + } + }); + + server.route({ + url: "/:projectId/secrets", + method: "GET", + schema: { + params: z.object({ + projectId: z.string().trim() + }), + response: { + 200: z.object({ + secrets: SecretsSchema.omit({ secretBlindIndex: true }) + .merge( + z.object({ + environment: z.string(), + workspace: z.string() + }) + ) + .array() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT]), + handler: async (req) => { + const secrets = await server.services.secretBlindIndex.getProjectSecrets({ + projectId: req.params.projectId, + actorId: req.permission.id, + actor: req.permission.type + }); + return { secrets }; + } + }); + + server.route({ + url: "/:projectId/secrets/name", + method: "POST", + schema: { + params: z.object({ + projectId: z.string().trim() + }), + body: z.object({ + secretsToUpdate: z + .object({ + secretName: z.string().trim(), + secretId: z.string().trim() + }) + .array() + }), + response: { + 200: z.object({ + message: z.string() + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT]), + handler: async (req) => { + await server.services.secretBlindIndex.updateProjectSecretName({ + projectId: req.params.projectId, + secretsToUpdate: req.body.secretsToUpdate, + actorId: req.permission.id, + actor: req.permission.type + }); + return { message: "Successfully named workspace secrets" }; + } + }); +}; diff --git a/backend-pg/src/services/project/project-service.ts b/backend-pg/src/services/project/project-service.ts index 6ed45d717..fbaa9e76a 100644 --- a/backend-pg/src/services/project/project-service.ts +++ b/backend-pg/src/services/project/project-service.ts @@ -19,7 +19,7 @@ import { alphaNumericNanoId } from "@app/lib/nanoid"; import { TProjectEnvDALFactory } from "../project-env/project-env-dal"; import { TProjectMembershipDALFactory } from "../project-membership/project-membership-dal"; -import { TSecretBlindIndexDALFactory } from "../secret/secret-blind-index-dal"; +import { TSecretBlindIndexDALFactory } from "../secret-blind-index/secret-blind-index-dal"; import { ROOT_FOLDER_NAME, TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal"; import { TProjectDALFactory } from "./project-dal"; import { TCreateProjectDTO, TDeleteProjectDTO, TGetProjectDTO } from "./project-types"; diff --git a/backend-pg/src/services/secret-blind-index/secret-blind-index-dal.ts b/backend-pg/src/services/secret-blind-index/secret-blind-index-dal.ts new file mode 100644 index 000000000..e69ec8bae --- /dev/null +++ b/backend-pg/src/services/secret-blind-index/secret-blind-index-dal.ts @@ -0,0 +1,66 @@ +import { Knex } from "knex"; + +import { TDbClient } from "@app/db"; +import { TableName } from "@app/db/schemas"; +import { DatabaseError } from "@app/lib/errors"; +import { ormify, selectAllTableCols } from "@app/lib/knex"; + +export type TSecretBlindIndexDALFactory = ReturnType; + +export const secretBlindIndexDALFactory = (db: TDbClient) => { + const secretBlindIndexOrm = ormify(db, TableName.SecretBlindIndex); + + const countOfSecretsWithNullSecretBlindIndex = async (projectId: string, tx?: Knex) => { + try { + const doc = await (tx || db)(TableName.Secret) + .leftJoin( + TableName.SecretFolder, + `${TableName.SecretFolder}.id`, + `${TableName.Secret}.folderId` + ) + .leftJoin( + TableName.Environment, + `${TableName.Environment}.id`, + `${TableName.SecretFolder}.envId` + ) + .where({ projectId }) + .whereNull("secretBlindIndex") + .count(`${TableName.Secret}.id`); + return (doc as any)?.[0]?.count || 0; + } catch (error) { + throw new DatabaseError({ error, name: "CountOfSecretWillNullSecretBlindIndex" }); + } + }; + + const findAllSecretsByProjectId = async (projectId: string, tx?: Knex) => { + try { + const docs = await (tx || db)(TableName.Secret) + .leftJoin( + TableName.SecretFolder, + `${TableName.SecretFolder}.id`, + `${TableName.Secret}.folderId` + ) + .leftJoin( + TableName.Environment, + `${TableName.Environment}.id`, + `${TableName.SecretFolder}.envId` + ) + .where({ projectId }) + .whereNull("secretBlindIndex") + .select(selectAllTableCols(TableName.Secret)) + .select( + db.ref("slug").withSchema(TableName.Environment).as("environment"), + db.ref("projectId").withSchema(TableName.Environment).as("workspace") + ); + return docs; + } catch (error) { + throw new DatabaseError({ error, name: "CountOfSecretWillNullSecretBlindIndex" }); + } + }; + + return { + ...secretBlindIndexOrm, + countOfSecretsWithNullSecretBlindIndex, + findAllSecretsByProjectId + }; +}; diff --git a/backend-pg/src/services/secret-blind-index/secret-blind-index-service.ts b/backend-pg/src/services/secret-blind-index/secret-blind-index-service.ts new file mode 100644 index 000000000..153eda498 --- /dev/null +++ b/backend-pg/src/services/secret-blind-index/secret-blind-index-service.ts @@ -0,0 +1,80 @@ +import { ProjectMembershipRole } from "@app/db/schemas"; +import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; +import { BadRequestError, UnauthorizedError } from "@app/lib/errors"; + +import { TSecretDALFactory } from "../secret/secret-dal"; +import { generateSecretBlindIndexBySalt } from "../secret/secret-fns"; +import { TSecretBlindIndexDALFactory } from "./secret-blind-index-dal"; +import { + TGetProjectBlindIndexStatusDTO, + TGetProjectSecretsDTO, + TUpdateProjectSecretNameDTO +} from "./secret-blind-index-types"; + +type TSecretBlindIndexServiceFactoryDep = { + permissionService: Pick; + secretBlindIndexDAL: TSecretBlindIndexDALFactory; + secretDAL:Pick; +}; + +export type TSecretBlindIndexServiceFactory = ReturnType; + +export const secretBlindIndexServiceFactory = ({ + secretBlindIndexDAL, + permissionService, + secretDAL +}: TSecretBlindIndexServiceFactoryDep) => { + const getSecretBlindIndexStatus = async ({ + actor, + projectId, + actorId + }: TGetProjectBlindIndexStatusDTO) => { + const { membership } = await permissionService.getProjectPermission(actor, actorId, projectId); + if (membership?.role !== ProjectMembershipRole.Admin) { + throw new UnauthorizedError({ message: "User must be admin" }); + } + + const secretCount = await secretBlindIndexDAL.countOfSecretsWithNullSecretBlindIndex(projectId); + return secretCount; + }; + + const getProjectSecrets = async ({ projectId, actorId, actor }: TGetProjectSecretsDTO) => { + const { membership } = await permissionService.getProjectPermission(actor, actorId, projectId); + if (membership?.role !== ProjectMembershipRole.Admin) { + throw new UnauthorizedError({ message: "User must be admin" }); + } + + const secrets = await secretBlindIndexDAL.findAllSecretsByProjectId(projectId); + return secrets; + }; + + const updateProjectSecretName = async ({ + projectId, + actor, + actorId, + secretsToUpdate + }: TUpdateProjectSecretNameDTO) => { + const { membership } = await permissionService.getProjectPermission(actor, actorId, projectId); + if (membership?.role !== ProjectMembershipRole.Admin) { + throw new UnauthorizedError({ message: "User must be admin" }); + } + const blindIndexCfg = await secretBlindIndexDAL.findOne({ projectId }); + if (!blindIndexCfg) + throw new BadRequestError({ message: "Blind index not found", name: "CreateSecret" }); + + const operations = await Promise.all(secretsToUpdate.map(async ({secretName,secretId:id})=>{ + const secretBlindIndex = await generateSecretBlindIndexBySalt(secretName,blindIndexCfg); + return { filter:{id},data:{secretBlindIndex} } + })) + + await secretBlindIndexDAL.transaction(async(tx)=>{ + await secretDAL.bulkUpdate(operations,tx) + }) + }; + + return { + getSecretBlindIndexStatus, + getProjectSecrets, + updateProjectSecretName + }; +}; diff --git a/backend-pg/src/services/secret-blind-index/secret-blind-index-types.ts b/backend-pg/src/services/secret-blind-index/secret-blind-index-types.ts new file mode 100644 index 000000000..83bc9b0b9 --- /dev/null +++ b/backend-pg/src/services/secret-blind-index/secret-blind-index-types.ts @@ -0,0 +1,12 @@ +import { TProjectPermission } from "@app/lib/types"; + +export type TGetProjectBlindIndexStatusDTO = TProjectPermission; + +export type TGetProjectSecretsDTO = TProjectPermission; + +export type TUpdateProjectSecretNameDTO = TProjectPermission & { + secretsToUpdate: { + secretName: string; + secretId: string; + }[]; +}; diff --git a/backend-pg/src/services/secret/secret-blind-index-dal.ts b/backend-pg/src/services/secret/secret-blind-index-dal.ts deleted file mode 100644 index 2c90e1a63..000000000 --- a/backend-pg/src/services/secret/secret-blind-index-dal.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { TDbClient } from "@app/db"; -import { TableName } from "@app/db/schemas"; -import { ormify } from "@app/lib/knex"; - -export type TSecretBlindIndexDALFactory = ReturnType; - -export const secretBlindIndexDALFactory = (db: TDbClient) => { - const secretBlindIndexOrm = ormify(db, TableName.SecretBlindIndex); - return secretBlindIndexOrm; -}; diff --git a/backend-pg/src/services/secret/secret-service.ts b/backend-pg/src/services/secret/secret-service.ts index 8f754e7c5..029db815b 100644 --- a/backend-pg/src/services/secret/secret-service.ts +++ b/backend-pg/src/services/secret/secret-service.ts @@ -21,11 +21,11 @@ import { logger } from "@app/lib/logger"; import { ActorType } from "../auth/auth-type"; import { TProjectBotServiceFactory } from "../project-bot/project-bot-service"; +import { TSecretBlindIndexDALFactory } from "../secret-blind-index/secret-blind-index-dal"; import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal"; import { TSecretImportDALFactory } from "../secret-import/secret-import-dal"; import { fnSecretsFromImports } from "../secret-import/secret-import-fns"; import { TSecretTagDALFactory } from "../secret-tag/secret-tag-dal"; -import { TSecretBlindIndexDALFactory } from "./secret-blind-index-dal"; import { TSecretDALFactory } from "./secret-dal"; import { decryptSecretRaw, generateSecretBlindIndexBySalt } from "./secret-fns"; import { TSecretQueueFactory } from "./secret-queue"; diff --git a/frontend/src/hooks/api/workspace/types.ts b/frontend/src/hooks/api/workspace/types.ts index acec07d45..10d222a2a 100644 --- a/frontend/src/hooks/api/workspace/types.ts +++ b/frontend/src/hooks/api/workspace/types.ts @@ -19,7 +19,7 @@ export type NameWorkspaceSecretsDTO = { workspaceId: string; secretsToUpdate: { secretName: string; - id: string; + secretId: string; }[]; }; diff --git a/frontend/src/views/SecretOverviewPage/components/ProjectIndexSecretsSection/ProjectIndexSecretsSection.tsx b/frontend/src/views/SecretOverviewPage/components/ProjectIndexSecretsSection/ProjectIndexSecretsSection.tsx index bdb63e021..2210171b0 100644 --- a/frontend/src/views/SecretOverviewPage/components/ProjectIndexSecretsSection/ProjectIndexSecretsSection.tsx +++ b/frontend/src/views/SecretOverviewPage/components/ProjectIndexSecretsSection/ProjectIndexSecretsSection.tsx @@ -47,7 +47,7 @@ export const ProjectIndexSecretsSection = ({ decryptFileKey }: Props) => { return { secretName, - id: encryptedSecret.id + secretId: encryptedSecret.id }; }); await nameWorkspaceSecrets.mutateAsync({