From a424954d7e67c92be453ffbdcfcd91ee9d732145 Mon Sep 17 00:00:00 2001 From: Fang-Pen Lin Date: Wed, 12 Nov 2025 11:53:32 -0800 Subject: [PATCH] Check profile creation as well --- .../certificate-profile-service.test.ts | 44 +++++++++++++++++-- .../certificate-profile-service.ts | 20 +++++++-- 2 files changed, 58 insertions(+), 6 deletions(-) diff --git a/backend/src/services/certificate-profile/certificate-profile-service.test.ts b/backend/src/services/certificate-profile/certificate-profile-service.test.ts index 9d9ab5947..8865ffb9b 100644 --- a/backend/src/services/certificate-profile/certificate-profile-service.test.ts +++ b/backend/src/services/certificate-profile/certificate-profile-service.test.ts @@ -6,14 +6,15 @@ import { ForbiddenError } from "@casl/ability"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import type { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types"; -import { ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; +import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; +import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { ActorType, AuthMethod } from "../auth/auth-type"; -import type { TCertificateBodyDALFactory } from "../certificate/certificate-body-dal"; -import type { TCertificateSecretDALFactory } from "../certificate/certificate-secret-dal"; import type { TCertificateAuthorityCertDALFactory } from "../certificate-authority/certificate-authority-cert-dal"; import type { TCertificateAuthorityDALFactory } from "../certificate-authority/certificate-authority-dal"; import type { TCertificateTemplateV2DALFactory } from "../certificate-template-v2/certificate-template-v2-dal"; +import type { TCertificateBodyDALFactory } from "../certificate/certificate-body-dal"; +import type { TCertificateSecretDALFactory } from "../certificate/certificate-secret-dal"; import { TAcmeEnrollmentConfigDALFactory } from "../enrollment-config/acme-enrollment-config-dal"; import type { TApiEnrollmentConfigDALFactory } from "../enrollment-config/api-enrollment-config-dal"; import type { TEstEnrollmentConfigDALFactory } from "../enrollment-config/est-enrollment-config-dal"; @@ -166,6 +167,10 @@ describe("CertificateProfileService", () => { }) } as unknown as Pick; + const mockLicenseService = { + getPlan: vi.fn() + } as unknown as Pick; + const mockKmsService = { encryptWithKmsKey: vi .fn() @@ -252,6 +257,7 @@ describe("CertificateProfileService", () => { certificateAuthorityDAL: mockCertificateAuthorityDAL, certificateAuthorityCertDAL: mockCertificateAuthorityCertDAL, permissionService: mockPermissionService, + licenseService: mockLicenseService, kmsService: mockKmsService, projectDAL: mockProjectDAL }); @@ -275,6 +281,13 @@ describe("CertificateProfileService", () => { }; beforeEach(() => { + (mockProjectDAL.findById as any).mockResolvedValue({ + id: "project-123", + orgId: "org-123" + }); + (mockLicenseService.getPlan as any).mockResolvedValue({ + pkiAcme: true + }); (mockCertificateTemplateV2DAL.findById as any).mockResolvedValue(sampleTemplate); (mockCertificateProfileDAL.findByNameAndProjectId as any).mockResolvedValue(null); (mockCertificateProfileDAL.findBySlugAndProjectId as any).mockResolvedValue(null); @@ -405,6 +418,24 @@ describe("CertificateProfileService", () => { expect(result).toEqual(sampleProfile); expect(mockCertificateTemplateV2DAL.findById).toHaveBeenCalledWith("template-123"); }); + + it("should throw BadRequestError when plan does not support ACME", async () => { + (mockLicenseService.getPlan as any).mockResolvedValue({ + pkiAcme: false + }); + + await expect( + service.createProfile({ + ...mockActor, + projectId: "project-123", + data: validProfileData + }) + ).rejects.toThrowError( + new BadRequestError({ + message: "Failed to create certificate profile: Plan restriction. Upgrade plan to continue" + }) + ); + }); }); describe("updateProfile", () => { @@ -699,6 +730,13 @@ describe("CertificateProfileService", () => { } }; + (mockProjectDAL.findById as any).mockResolvedValue({ + id: "project-123", + orgId: "org-123" + }); + (mockLicenseService.getPlan as any).mockResolvedValue({ + pkiAcme: true + }); (mockCertificateTemplateV2DAL.findById as any).mockResolvedValue(sampleTemplate); (mockCertificateProfileDAL.findByNameAndProjectId as any).mockResolvedValue(null); (mockCertificateProfileDAL.findBySlugAndProjectId as any).mockResolvedValue(null); diff --git a/backend/src/services/certificate-profile/certificate-profile-service.ts b/backend/src/services/certificate-profile/certificate-profile-service.ts index 66a23a0e7..fe58f958f 100644 --- a/backend/src/services/certificate-profile/certificate-profile-service.ts +++ b/backend/src/services/certificate-profile/certificate-profile-service.ts @@ -14,13 +14,14 @@ import { getConfig } from "@app/lib/config/env"; import { crypto } from "@app/lib/crypto/cryptography"; import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors"; +import { TLicenseServiceFactory } from "@app/ee/services/license/license-service"; import { ActorAuthMethod, ActorType } from "../auth/auth-type"; -import { TCertificateBodyDALFactory } from "../certificate/certificate-body-dal"; -import { getCertificateCredentials, isCertChainValid } from "../certificate/certificate-fns"; -import { TCertificateSecretDALFactory } from "../certificate/certificate-secret-dal"; import { TCertificateAuthorityCertDALFactory } from "../certificate-authority/certificate-authority-cert-dal"; import { TCertificateAuthorityDALFactory } from "../certificate-authority/certificate-authority-dal"; import { TCertificateTemplateV2DALFactory } from "../certificate-template-v2/certificate-template-v2-dal"; +import { TCertificateBodyDALFactory } from "../certificate/certificate-body-dal"; +import { getCertificateCredentials, isCertChainValid } from "../certificate/certificate-fns"; +import { TCertificateSecretDALFactory } from "../certificate/certificate-secret-dal"; import { TAcmeEnrollmentConfigDALFactory } from "../enrollment-config/acme-enrollment-config-dal"; import { TApiEnrollmentConfigDALFactory } from "../enrollment-config/api-enrollment-config-dal"; import { TAcmeConfigData, TApiConfigData, TEstConfigData } from "../enrollment-config/enrollment-config-types"; @@ -152,6 +153,7 @@ type TCertificateProfileServiceFactoryDep = { certificateAuthorityDAL: Pick; certificateAuthorityCertDAL: Pick; permissionService: Pick; + licenseService: Pick; kmsService: Pick; projectDAL: Pick; }; @@ -174,6 +176,7 @@ export const certificateProfileServiceFactory = ({ certificateBodyDAL, certificateSecretDAL, permissionService, + licenseService, kmsService, projectDAL }: TCertificateProfileServiceFactoryDep) => { @@ -205,6 +208,17 @@ export const certificateProfileServiceFactory = ({ ProjectPermissionSub.CertificateProfiles ); + const project = await projectDAL.findById(projectId); + if (!project) { + throw new NotFoundError({ message: "Project not found" }); + } + const plan = await licenseService.getPlan(project.orgId); + if (!plan.pkiAcme) { + throw new BadRequestError({ + message: "Failed to create certificate profile: Plan restriction. Upgrade plan to continue" + }); + } + // Validate that certificate template exists and belongs to the same project if (data.certificateTemplateId) { const template = await certificateTemplateV2DAL.findById(data.certificateTemplateId);