diff --git a/backend/src/ee/services/audit-log/audit-log-types.ts b/backend/src/ee/services/audit-log/audit-log-types.ts index f7577b24e..5dedead70 100644 --- a/backend/src/ee/services/audit-log/audit-log-types.ts +++ b/backend/src/ee/services/audit-log/audit-log-types.ts @@ -106,6 +106,7 @@ export enum EventType { CREATE_ENVIRONMENT = "create-environment", UPDATE_ENVIRONMENT = "update-environment", DELETE_ENVIRONMENT = "delete-environment", + GET_ENVIRONMENT = "get-environment", ADD_WORKSPACE_MEMBER = "add-workspace-member", ADD_BATCH_WORKSPACE_MEMBER = "add-workspace-members", REMOVE_WORKSPACE_MEMBER = "remove-workspace-member", @@ -831,6 +832,13 @@ interface CreateEnvironmentEvent { }; } +interface GetEnvironmentEvent { + type: EventType.GET_ENVIRONMENT; + metadata: { + id: string; + }; +} + interface UpdateEnvironmentEvent { type: EventType.UPDATE_ENVIRONMENT; metadata: { @@ -1230,6 +1238,7 @@ export type Event = | UpdateIdentityOidcAuthEvent | GetIdentityOidcAuthEvent | CreateEnvironmentEvent + | GetEnvironmentEvent | UpdateEnvironmentEvent | DeleteEnvironmentEvent | AddWorkspaceMemberEvent diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index 5aae289bf..c0e59b2b6 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -510,6 +510,10 @@ export const ENVIRONMENTS = { DELETE: { workspaceId: "The ID of the project to delete the environment from.", id: "The ID of the environment to delete." + }, + GET: { + workspaceId: "The ID of the project the environment belongs to.", + id: "The ID of the environment to fetch." } } as const; diff --git a/backend/src/server/routes/v1/project-env-router.ts b/backend/src/server/routes/v1/project-env-router.ts index 341b8a184..fed609196 100644 --- a/backend/src/server/routes/v1/project-env-router.ts +++ b/backend/src/server/routes/v1/project-env-router.ts @@ -9,6 +9,55 @@ import { verifyAuth } from "@app/server/plugins/auth/verify-auth"; import { AuthMode } from "@app/services/auth/auth-type"; export const registerProjectEnvRouter = async (server: FastifyZodProvider) => { + server.route({ + method: "GET", + url: "/:workspaceId/environments/:envId", + config: { + rateLimit: writeLimit + }, + schema: { + description: "Get Environment", + security: [ + { + bearerAuth: [] + } + ], + params: z.object({ + workspaceId: z.string().trim().describe(ENVIRONMENTS.GET.workspaceId), + envId: z.string().trim().describe(ENVIRONMENTS.GET.id) + }), + response: { + 200: z.object({ + environment: ProjectEnvironmentsSchema + }) + } + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + handler: async (req) => { + const environment = await server.services.projectEnv.getEnvironmentById({ + actorId: req.permission.id, + actor: req.permission.type, + actorOrgId: req.permission.orgId, + actorAuthMethod: req.permission.authMethod, + projectId: req.params.workspaceId, + id: req.params.envId + }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId: environment.projectId, + event: { + type: EventType.GET_ENVIRONMENT, + metadata: { + id: environment.id + } + } + }); + + return { environment }; + } + }); + server.route({ method: "POST", url: "/:workspaceId/environments", diff --git a/backend/src/services/project-env/project-env-dal.ts b/backend/src/services/project-env/project-env-dal.ts index d6f4429d0..8d42aab86 100644 --- a/backend/src/services/project-env/project-env-dal.ts +++ b/backend/src/services/project-env/project-env-dal.ts @@ -24,10 +24,15 @@ export const projectEnvDALFactory = (db: TDbClient) => { // we are using postion based sorting as its a small list // this will return the last value of the position in a folder with secret imports const findLastEnvPosition = async (projectId: string, tx?: Knex) => { + // acquire update lock on project environments. + // this ensures that concurrent invocations will wait and execute sequentially + await (tx || db)(TableName.Environment).where({ projectId }).forUpdate(); + const lastPos = await (tx || db)(TableName.Environment) .where({ projectId }) .max("position", { as: "position" }) .first(); + return lastPos?.position || 0; }; diff --git a/backend/src/services/project-env/project-env-service.ts b/backend/src/services/project-env/project-env-service.ts index 2acda33c0..c7af817bd 100644 --- a/backend/src/services/project-env/project-env-service.ts +++ b/backend/src/services/project-env/project-env-service.ts @@ -3,12 +3,12 @@ import { ForbiddenError } from "@casl/ability"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; -import { BadRequestError } from "@app/lib/errors"; +import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { TProjectDALFactory } from "../project/project-dal"; import { TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal"; import { TProjectEnvDALFactory } from "./project-env-dal"; -import { TCreateEnvDTO, TDeleteEnvDTO, TUpdateEnvDTO } from "./project-env-types"; +import { TCreateEnvDTO, TDeleteEnvDTO, TGetEnvDTO, TUpdateEnvDTO } from "./project-env-types"; type TProjectEnvServiceFactoryDep = { projectEnvDAL: TProjectEnvDALFactory; @@ -139,9 +139,35 @@ export const projectEnvServiceFactory = ({ return env; }; + const getEnvironmentById = async ({ projectId, actor, actorId, actorOrgId, actorAuthMethod, id }: TGetEnvDTO) => { + const { permission } = await permissionService.getProjectPermission( + actor, + actorId, + projectId, + actorAuthMethod, + actorOrgId + ); + + ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Environments); + + const [env] = await projectEnvDAL.find({ + id, + projectId + }); + + if (!env) { + throw new NotFoundError({ + message: "Environment does not exist" + }); + } + + return env; + }; + return { createEnvironment, updateEnvironment, - deleteEnvironment + deleteEnvironment, + getEnvironmentById }; }; diff --git a/backend/src/services/project-env/project-env-types.ts b/backend/src/services/project-env/project-env-types.ts index 1cd8c8dd0..27d808a47 100644 --- a/backend/src/services/project-env/project-env-types.ts +++ b/backend/src/services/project-env/project-env-types.ts @@ -20,3 +20,7 @@ export type TReorderEnvDTO = { id: string; pos: number; } & TProjectPermission; + +export type TGetEnvDTO = { + id: string; +} & TProjectPermission;