import { ForbiddenError } from "@casl/ability"; import slugify from "@sindresorhus/slugify"; import { OrgMembershipRole, ProjectMembershipRole, ProjectVersion } from "@app/db/schemas"; import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { TKeyStoreFactory } from "@app/keystore/keystore"; import { isAtLeastAsPrivileged } from "@app/lib/casl"; import { infisicalSymmetricEncypt } from "@app/lib/crypto/encryption"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; import { groupBy } from "@app/lib/fn"; import { alphaNumericNanoId } from "@app/lib/nanoid"; import { TProjectPermission } from "@app/lib/types"; import { ActorType } from "../auth/auth-type"; import { TCertificateDALFactory } from "../certificate/certificate-dal"; import { TCertificateAuthorityDALFactory } from "../certificate-authority/certificate-authority-dal"; import { TCertificateTemplateDALFactory } from "../certificate-template/certificate-template-dal"; import { TIdentityOrgDALFactory } from "../identity/identity-org-dal"; import { TIdentityProjectDALFactory } from "../identity-project/identity-project-dal"; import { TIdentityProjectMembershipRoleDALFactory } from "../identity-project/identity-project-membership-role-dal"; import { TKmsServiceFactory } from "../kms/kms-service"; import { TOrgDALFactory } from "../org/org-dal"; import { TOrgServiceFactory } from "../org/org-service"; import { TPkiAlertDALFactory } from "../pki-alert/pki-alert-dal"; import { TPkiCollectionDALFactory } from "../pki-collection/pki-collection-dal"; import { TProjectBotDALFactory } from "../project-bot/project-bot-dal"; import { TProjectEnvDALFactory } from "../project-env/project-env-dal"; import { TProjectKeyDALFactory } from "../project-key/project-key-dal"; import { TProjectMembershipDALFactory } from "../project-membership/project-membership-dal"; import { TProjectUserMembershipRoleDALFactory } from "../project-membership/project-user-membership-role-dal"; import { TProjectRoleDALFactory } from "../project-role/project-role-dal"; import { getPredefinedRoles } from "../project-role/project-role-fns"; import { ROOT_FOLDER_NAME, TSecretFolderDALFactory } from "../secret-folder/secret-folder-dal"; import { TProjectSlackConfigDALFactory } from "../slack/project-slack-config-dal"; import { TSlackIntegrationDALFactory } from "../slack/slack-integration-dal"; import { TUserDALFactory } from "../user/user-dal"; import { TProjectDALFactory } from "./project-dal"; import { assignWorkspaceKeysToMembers, createProjectKey } from "./project-fns"; import { TProjectQueueFactory } from "./project-queue"; import { TCreateProjectDTO, TDeleteProjectDTO, TGetProjectDTO, TGetProjectKmsKey, TGetProjectSlackConfig, TListProjectAlertsDTO, TListProjectCasDTO, TListProjectCertificateTemplatesDTO, TListProjectCertsDTO, TListProjectsDTO, TLoadProjectKmsBackupDTO, TToggleProjectAutoCapitalizationDTO, TUpdateAuditLogsRetentionDTO, TUpdateProjectDTO, TUpdateProjectKmsDTO, TUpdateProjectNameDTO, TUpdateProjectSlackConfig, TUpdateProjectVersionLimitDTO, TUpgradeProjectDTO } from "./project-types"; export const DEFAULT_PROJECT_ENVS = [ { name: "Development", slug: "dev" }, { name: "Staging", slug: "staging" }, { name: "Production", slug: "prod" } ]; type TProjectServiceFactoryDep = { // TODO: Pick projectDAL: TProjectDALFactory; projectQueue: TProjectQueueFactory; userDAL: TUserDALFactory; folderDAL: TSecretFolderDALFactory; projectEnvDAL: Pick; identityOrgMembershipDAL: TIdentityOrgDALFactory; identityProjectDAL: TIdentityProjectDALFactory; identityProjectMembershipRoleDAL: Pick; projectKeyDAL: Pick; projectMembershipDAL: Pick; projectSlackConfigDAL: Pick; slackIntegrationDAL: Pick; projectUserMembershipRoleDAL: Pick; certificateAuthorityDAL: Pick; certificateDAL: Pick; certificateTemplateDAL: Pick; pkiAlertDAL: Pick; pkiCollectionDAL: Pick; permissionService: TPermissionServiceFactory; orgService: Pick; licenseService: Pick; orgDAL: Pick; keyStore: Pick; projectBotDAL: Pick; projectRoleDAL: Pick; kmsService: Pick< TKmsServiceFactory, | "updateProjectSecretManagerKmsKey" | "getProjectKeyBackup" | "loadProjectKeyBackup" | "getKmsById" | "getProjectSecretManagerKmsKeyId" | "deleteInternalKms" >; }; export type TProjectServiceFactory = ReturnType; export const projectServiceFactory = ({ projectDAL, projectQueue, projectKeyDAL, permissionService, orgDAL, userDAL, folderDAL, orgService, identityProjectDAL, identityOrgMembershipDAL, projectMembershipDAL, projectEnvDAL, licenseService, projectUserMembershipRoleDAL, projectRoleDAL, identityProjectMembershipRoleDAL, certificateAuthorityDAL, certificateDAL, certificateTemplateDAL, pkiCollectionDAL, pkiAlertDAL, keyStore, kmsService, projectBotDAL, projectSlackConfigDAL, slackIntegrationDAL }: TProjectServiceFactoryDep) => { /* * Create workspace. Make user the admin * */ const createProject = async ({ actor, actorId, actorOrgId, actorAuthMethod, workspaceName, slug: projectSlug, kmsKeyId }: TCreateProjectDTO) => { const organization = await orgDAL.findOne({ id: actorOrgId }); const { permission, membership: orgMembership } = await permissionService.getOrgPermission( actor, actorId, organization.id, actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Workspace); const plan = await licenseService.getPlan(organization.id); if (plan.workspaceLimit !== null && plan.workspacesUsed >= plan.workspaceLimit) { // case: limit imposed on number of workspaces allowed // case: number of workspaces used exceeds the number of workspaces allowed throw new BadRequestError({ message: "Failed to create workspace due to plan limit reached. Upgrade plan to add more workspaces." }); } const results = await projectDAL.transaction(async (tx) => { const ghostUser = await orgService.addGhostUser(organization.id, tx); if (kmsKeyId) { const kms = await kmsService.getKmsById(kmsKeyId, tx); if (kms.orgId !== organization.id) { throw new ForbiddenRequestError({ message: "KMS does not belong in the organization" }); } } const project = await projectDAL.create( { name: workspaceName, orgId: organization.id, slug: projectSlug || slugify(`${workspaceName}-${alphaNumericNanoId(4)}`), kmsSecretManagerKeyId: kmsKeyId, version: ProjectVersion.V3, pitVersionLimit: 10 }, tx ); // set ghost user as admin of project const projectMembership = await projectMembershipDAL.create( { userId: ghostUser.user.id, projectId: project.id }, tx ); await projectUserMembershipRoleDAL.create( { projectMembershipId: projectMembership.id, role: ProjectMembershipRole.Admin }, tx ); // set default environments and root folder for provided environments const envs = await projectEnvDAL.insertMany( DEFAULT_PROJECT_ENVS.map((el, i) => ({ ...el, projectId: project.id, position: i + 1 })), tx ); await folderDAL.insertMany( envs.map(({ id }) => ({ name: ROOT_FOLDER_NAME, envId: id, version: 1 })), tx ); // 3. Create a random key that we'll use as the project key. const { key: encryptedProjectKey, iv: encryptedProjectKeyIv } = createProjectKey({ publicKey: ghostUser.keys.publicKey, privateKey: ghostUser.keys.plainPrivateKey }); // 4. Save the project key for the ghost user. await projectKeyDAL.create( { projectId: project.id, receiverId: ghostUser.user.id, encryptedKey: encryptedProjectKey, nonce: encryptedProjectKeyIv, senderId: ghostUser.user.id }, tx ); const { iv, tag, ciphertext, encoding, algorithm } = infisicalSymmetricEncypt(ghostUser.keys.plainPrivateKey); // 5. Create & a bot for the project await projectBotDAL.create( { name: "Infisical Bot (Ghost)", projectId: project.id, tag, iv, encryptedProjectKey, encryptedProjectKeyNonce: encryptedProjectKeyIv, encryptedPrivateKey: ciphertext, isActive: true, publicKey: ghostUser.keys.publicKey, senderId: ghostUser.user.id, algorithm, keyEncoding: encoding }, tx ); // Find the ghost users latest key const latestKey = await projectKeyDAL.findLatestProjectKey(ghostUser.user.id, project.id, tx); if (!latestKey) { throw new Error("Latest key not found for user"); } // If the project is being created by a user, add the user to the project as an admin if (actor === ActorType.USER) { // Find public key of user const user = await userDAL.findUserEncKeyByUserId(actorId); if (!user) { throw new Error("User not found"); } const [projectAdmin] = assignWorkspaceKeysToMembers({ decryptKey: latestKey, userPrivateKey: ghostUser.keys.plainPrivateKey, members: [ { userPublicKey: user.publicKey, orgMembershipId: orgMembership.id } ] }); // Create a membership for the user const userProjectMembership = await projectMembershipDAL.create( { projectId: project.id, userId: user.id }, tx ); await projectUserMembershipRoleDAL.create( { projectMembershipId: userProjectMembership.id, role: ProjectMembershipRole.Admin }, tx ); // Create a project key for the user await projectKeyDAL.create( { encryptedKey: projectAdmin.workspaceEncryptedKey, nonce: projectAdmin.workspaceEncryptedNonce, senderId: ghostUser.user.id, receiverId: user.id, projectId: project.id }, tx ); } // If the project is being created by an identity, add the identity to the project as an admin else if (actor === ActorType.IDENTITY) { // Find identity org membership const identityOrgMembership = await identityOrgMembershipDAL.findOne( { identityId: actorId, orgId: project.orgId }, tx ); // If identity org membership not found, throw error if (!identityOrgMembership) { throw new NotFoundError({ message: `Failed to find identity with id ${actorId}` }); } // Get the role permission for the identity const { permission: rolePermission, role: customRole } = await permissionService.getOrgPermissionByRole( OrgMembershipRole.Member, organization.id ); // Identity has to be at least a member in order to create projects const hasPrivilege = isAtLeastAsPrivileged(permission, rolePermission); if (!hasPrivilege) throw new ForbiddenRequestError({ message: "Failed to add identity to project with more privileged role" }); const isCustomRole = Boolean(customRole); const identityProjectMembership = await identityProjectDAL.create( { identityId: actorId, projectId: project.id }, tx ); await identityProjectMembershipRoleDAL.create( { projectMembershipId: identityProjectMembership.id, role: isCustomRole ? ProjectMembershipRole.Custom : ProjectMembershipRole.Admin, customRoleId: customRole?.id }, tx ); } return { ...project, environments: envs, _id: project.id }; }); await keyStore.deleteItem(`infisical-cloud-plan-${actorOrgId}`); return results; }; const deleteProject = async ({ actor, actorId, actorOrgId, actorAuthMethod, filter }: TDeleteProjectDTO) => { const project = await projectDAL.findProjectByFilter(filter); const { permission } = await permissionService.getProjectPermission( actor, actorId, project.id, actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Project); const deletedProject = await projectDAL.transaction(async (tx) => { const delProject = await projectDAL.deleteById(project.id, tx); const projectGhostUser = await projectMembershipDAL.findProjectGhostUser(project.id, tx).catch(() => null); if (delProject.kmsCertificateKeyId) { await kmsService.deleteInternalKms(delProject.kmsCertificateKeyId, delProject.orgId, tx); } if (delProject.kmsSecretManagerKeyId) { await kmsService.deleteInternalKms(delProject.kmsSecretManagerKeyId, delProject.orgId, tx); } // Delete the org membership for the ghost user if it's found. if (projectGhostUser) { await userDAL.deleteById(projectGhostUser.id, tx); } return delProject; }); await keyStore.deleteItem(`infisical-cloud-plan-${actorOrgId}`); return deletedProject; }; const getProjects = async ({ actorId, includeRoles, actorAuthMethod, actorOrgId }: TListProjectsDTO) => { const workspaces = await projectDAL.findAllProjects(actorId); if (includeRoles) { const { permission } = await permissionService.getUserOrgPermission(actorId, actorOrgId, actorAuthMethod); // `includeRoles` is specifically used by organization admins when inviting new users to the organizations to avoid looping redundant api calls. ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Create, OrgPermissionSubjects.Member); const customRoles = await projectRoleDAL.find({ $in: { projectId: workspaces.map((workspace) => workspace.id) } }); const workspaceMappedToRoles = groupBy(customRoles, (role) => role.projectId); const workspacesWithRoles = await Promise.all( workspaces.map(async (workspace) => { return { ...workspace, roles: [...(workspaceMappedToRoles[workspace.id] || []), ...getPredefinedRoles(workspace.id)] }; }) ); return workspacesWithRoles; } return workspaces; }; const getAProject = async ({ actorId, actorOrgId, actorAuthMethod, filter, actor }: TGetProjectDTO) => { const project = await projectDAL.findProjectByFilter(filter); await permissionService.getProjectPermission(actor, actorId, project.id, actorAuthMethod, actorOrgId); return project; }; const updateProject = async ({ actor, actorId, actorOrgId, actorAuthMethod, update, filter }: TUpdateProjectDTO) => { const project = await projectDAL.findProjectByFilter(filter); const { permission } = await permissionService.getProjectPermission( actor, actorId, project.id, actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings); const updatedProject = await projectDAL.updateById(project.id, { name: update.name, autoCapitalization: update.autoCapitalization }); return updatedProject; }; const toggleAutoCapitalization = async ({ projectId, actor, actorId, actorOrgId, actorAuthMethod, autoCapitalization }: TToggleProjectAutoCapitalizationDTO) => { const { permission } = await permissionService.getProjectPermission( actor, actorId, projectId, actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings); const updatedProject = await projectDAL.updateById(projectId, { autoCapitalization }); return updatedProject; }; const updateVersionLimit = async ({ actor, actorId, actorOrgId, actorAuthMethod, pitVersionLimit, workspaceSlug }: TUpdateProjectVersionLimitDTO) => { const project = await projectDAL.findProjectBySlug(workspaceSlug, actorOrgId); if (!project) { throw new NotFoundError({ message: "Project not found" }); } const { hasRole } = await permissionService.getProjectPermission( actor, actorId, project.id, actorAuthMethod, actorOrgId ); if (!hasRole(ProjectMembershipRole.Admin)) throw new ForbiddenRequestError({ message: "Insufficient privileges, only admins are allowed to take this action" }); return projectDAL.updateById(project.id, { pitVersionLimit }); }; const updateAuditLogsRetention = async ({ actor, actorId, actorOrgId, actorAuthMethod, auditLogsRetentionDays, workspaceSlug }: TUpdateAuditLogsRetentionDTO) => { const project = await projectDAL.findProjectBySlug(workspaceSlug, actorOrgId); if (!project) { throw new NotFoundError({ message: "Project not found." }); } const { hasRole } = await permissionService.getProjectPermission( actor, actorId, project.id, actorAuthMethod, actorOrgId ); if (!hasRole(ProjectMembershipRole.Admin)) { throw new ForbiddenRequestError({ message: "Insufficient privileges, only admins are allowed to take this action" }); } const plan = await licenseService.getPlan(project.orgId); if (!plan.auditLogs || auditLogsRetentionDays > plan.auditLogsRetentionDays) { throw new BadRequestError({ message: "Failed to update audit logs retention due to plan limit reached. Upgrade plan to increase." }); } return projectDAL.updateById(project.id, { auditLogsRetentionDays }); }; const updateName = async ({ projectId, actor, actorId, actorOrgId, actorAuthMethod, name }: TUpdateProjectNameDTO) => { const { permission } = await permissionService.getProjectPermission( actor, actorId, projectId, actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings); const updatedProject = await projectDAL.updateById(projectId, { name }); return updatedProject; }; const upgradeProject = async ({ projectId, actor, actorId, actorAuthMethod, actorOrgId, userPrivateKey }: TUpgradeProjectDTO) => { const { permission, hasRole } = await permissionService.getProjectPermission( actor, actorId, projectId, actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Project); if (!hasRole(ProjectMembershipRole.Admin)) { throw new ForbiddenRequestError({ message: "User must be admin" }); } const encryptedPrivateKey = infisicalSymmetricEncypt(userPrivateKey); await projectQueue.upgradeProject({ projectId, startedByUserId: actorId, encryptedPrivateKey: { encryptedKey: encryptedPrivateKey.ciphertext, encryptedKeyIv: encryptedPrivateKey.iv, encryptedKeyTag: encryptedPrivateKey.tag, keyEncoding: encryptedPrivateKey.encoding } }); }; const getProjectUpgradeStatus = async ({ projectId, actor, actorAuthMethod, actorOrgId, actorId }: TProjectPermission) => { const { permission } = await permissionService.getProjectPermission( actor, actorId, projectId, actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Secrets); const project = await projectDAL.findProjectById(projectId); if (!project) { throw new NotFoundError({ message: `Project with id ${projectId} not found` }); } return project.upgradeStatus || null; }; /** * Return list of CAs for project */ const listProjectCas = async ({ status, friendlyName, commonName, limit = 25, offset = 0, actorId, actorOrgId, actorAuthMethod, filter, actor }: TListProjectCasDTO) => { const project = await projectDAL.findProjectByFilter(filter); const { permission } = await permissionService.getProjectPermission( actor, actorId, project.id, actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, ProjectPermissionSub.CertificateAuthorities ); const cas = await certificateAuthorityDAL.find( { projectId: project.id, ...(status && { status }), ...(friendlyName && { friendlyName }), ...(commonName && { commonName }) }, { offset, limit, sort: [["updatedAt", "desc"]] } ); return cas; }; /** * Return list of certificates for project */ const listProjectCertificates = async ({ limit = 25, offset = 0, friendlyName, commonName, actorId, actorOrgId, actorAuthMethod, filter, actor }: TListProjectCertsDTO) => { const project = await projectDAL.findProjectByFilter(filter); const { permission } = await permissionService.getProjectPermission( actor, actorId, project.id, actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Certificates); const cas = await certificateAuthorityDAL.find({ projectId: project.id }); const certificates = await certificateDAL.find( { $in: { caId: cas.map((ca) => ca.id) }, ...(friendlyName && { friendlyName }), ...(commonName && { commonName }) }, { offset, limit, sort: [["updatedAt", "desc"]] } ); const count = await certificateDAL.countCertificatesInProject({ projectId: project.id, friendlyName, commonName }); return { certificates, totalCount: count }; }; /** * Return list of (PKI) alerts configured for project */ const listProjectAlerts = async ({ projectId, actor, actorId, actorAuthMethod, actorOrgId }: TListProjectAlertsDTO) => { const { permission } = await permissionService.getProjectPermission( actor, actorId, projectId, actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.PkiAlerts); const alerts = await pkiAlertDAL.find({ projectId }); return { alerts }; }; /** * Return list of PKI collections for project */ const listProjectPkiCollections = async ({ projectId, actor, actorId, actorAuthMethod, actorOrgId }: TListProjectAlertsDTO) => { const { permission } = await permissionService.getProjectPermission( actor, actorId, projectId, actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.PkiCollections); const pkiCollections = await pkiCollectionDAL.find({ projectId }); return { pkiCollections }; }; /** * Return list of certificate templates for project */ const listProjectCertificateTemplates = async ({ projectId, actorId, actorOrgId, actorAuthMethod, actor }: TListProjectCertificateTemplatesDTO) => { const { permission } = await permissionService.getProjectPermission( actor, actorId, projectId, actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan( ProjectPermissionActions.Read, ProjectPermissionSub.CertificateTemplates ); const certificateTemplates = await certificateTemplateDAL.getCertTemplatesByProjectId(projectId); return { certificateTemplates }; }; const updateProjectKmsKey = async ({ projectId, kms, actor, actorId, actorAuthMethod, actorOrgId }: TUpdateProjectKmsDTO) => { const { permission } = await permissionService.getProjectPermission( actor, actorId, projectId, actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Kms); const secretManagerKmsKey = await kmsService.updateProjectSecretManagerKmsKey({ projectId, kms }); return { secretManagerKmsKey }; }; const getProjectKmsBackup = async ({ projectId, actor, actorId, actorAuthMethod, actorOrgId }: TProjectPermission) => { const { permission } = await permissionService.getProjectPermission( actor, actorId, projectId, actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Kms); const plan = await licenseService.getPlan(actorOrgId); if (!plan.externalKms) { throw new BadRequestError({ message: "Failed to get KMS backup due to plan restriction. Upgrade to the enterprise plan." }); } const kmsBackup = await kmsService.getProjectKeyBackup(projectId); return kmsBackup; }; const loadProjectKmsBackup = async ({ projectId, actor, actorId, actorAuthMethod, actorOrgId, backup }: TLoadProjectKmsBackupDTO) => { const { permission } = await permissionService.getProjectPermission( actor, actorId, projectId, actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Kms); const plan = await licenseService.getPlan(actorOrgId); if (!plan.externalKms) { throw new BadRequestError({ message: "Failed to load KMS backup due to plan restriction. Upgrade to the enterprise plan." }); } const kmsBackup = await kmsService.loadProjectKeyBackup(projectId, backup); return kmsBackup; }; const getProjectKmsKeys = async ({ projectId, actor, actorId, actorAuthMethod, actorOrgId }: TGetProjectKmsKey) => { const { membership } = await permissionService.getProjectPermission( actor, actorId, projectId, actorAuthMethod, actorOrgId ); if (!membership) { throw new ForbiddenRequestError({ message: "You are not a member of this project" }); } const kmsKeyId = await kmsService.getProjectSecretManagerKmsKeyId(projectId); const kmsKey = await kmsService.getKmsById(kmsKeyId); return { secretManagerKmsKey: kmsKey }; }; const getProjectSlackConfig = async ({ actorId, actor, actorOrgId, actorAuthMethod, projectId }: TGetProjectSlackConfig) => { const project = await projectDAL.findById(projectId); if (!project) { throw new NotFoundError({ message: "Project not found" }); } const { permission } = await permissionService.getProjectPermission( actor, actorId, projectId, actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Settings); return projectSlackConfigDAL.findOne({ projectId: project.id }); }; const updateProjectSlackConfig = async ({ actorId, actor, actorOrgId, actorAuthMethod, projectId, slackIntegrationId, isAccessRequestNotificationEnabled, accessRequestChannels, isSecretRequestNotificationEnabled, secretRequestChannels }: TUpdateProjectSlackConfig) => { const project = await projectDAL.findById(projectId); if (!project) { throw new NotFoundError({ message: "Project not found" }); } const slackIntegration = await slackIntegrationDAL.findByIdWithWorkflowIntegrationDetails(slackIntegrationId); if (!slackIntegration) { throw new NotFoundError({ message: "Slack integration not found" }); } const { permission } = await permissionService.getProjectPermission( actor, actorId, projectId, actorAuthMethod, actorOrgId ); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.Settings); if (slackIntegration.orgId !== project.orgId) { throw new ForbiddenRequestError({ message: "Selected slack integration is not in the same organization" }); } return projectSlackConfigDAL.transaction(async (tx) => { const slackConfig = await projectSlackConfigDAL.findOne( { projectId }, tx ); if (slackConfig) { return projectSlackConfigDAL.updateById( slackConfig.id, { slackIntegrationId, isAccessRequestNotificationEnabled, accessRequestChannels, isSecretRequestNotificationEnabled, secretRequestChannels }, tx ); } return projectSlackConfigDAL.create( { projectId, slackIntegrationId, isAccessRequestNotificationEnabled, accessRequestChannels, isSecretRequestNotificationEnabled, secretRequestChannels }, tx ); }); }; return { createProject, deleteProject, getProjects, updateProject, getProjectUpgradeStatus, getAProject, toggleAutoCapitalization, updateName, upgradeProject, listProjectCas, listProjectCertificates, listProjectAlerts, listProjectPkiCollections, listProjectCertificateTemplates, updateVersionLimit, updateAuditLogsRetention, updateProjectKmsKey, getProjectKmsBackup, loadProjectKmsBackup, getProjectKmsKeys, getProjectSlackConfig, updateProjectSlackConfig }; };