mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
Merge pull request #4848 from Infisical/PKI-34-pki-acme-license-check
[PKI-34] Add license check for PKI ACME feature
This commit is contained in:
5
.github/workflows/run-backend-bdd-tests.yml
vendored
5
.github/workflows/run-backend-bdd-tests.yml
vendored
@@ -45,12 +45,13 @@ jobs:
|
||||
run: npm install
|
||||
working-directory: backend
|
||||
|
||||
- name: Output .env file
|
||||
- name: Output .env file and enable feature flags for BDD tests
|
||||
run: |
|
||||
cp .env.example .env
|
||||
echo "ACME_DEVELOPMENT_MODE=true" >> .env
|
||||
echo "ACME_FEATURE_ENABLED=true" >> .env
|
||||
echo "ACME_DEVELOPMENT_HTTP01_CHALLENGE_HOST_OVERRIDES={\"localhost\": \"host.docker.internal:8087\"}" >> .env
|
||||
# Enable ACME feature in license for BDD tests
|
||||
sed -i 's/pkiAcme: .*/pkiAcme: true,/g' backend/src/ee/services/license/license-fns.ts
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { registerProjectTemplateRouter } from "@app/ee/routes/v1/project-template-router";
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
|
||||
import { registerAccessApprovalPolicyRouter } from "./access-approval-policy-router";
|
||||
import { registerAccessApprovalRequestRouter } from "./access-approval-request-router";
|
||||
@@ -109,10 +108,7 @@ export const registerV1EERoutes = async (server: FastifyZodProvider) => {
|
||||
await server.register(
|
||||
async (pkiRouter) => {
|
||||
await pkiRouter.register(registerCaCrlRouter, { prefix: "/crl" });
|
||||
// Notice: current this feature is still in development and is not yet ready for production.
|
||||
if (getConfig().isAcmeFeatureEnabled === true) {
|
||||
await pkiRouter.register(registerPkiAcmeRouter, { prefix: "/acme" });
|
||||
}
|
||||
await pkiRouter.register(registerPkiAcmeRouter, { prefix: "/acme" });
|
||||
},
|
||||
{ prefix: "/pki" }
|
||||
);
|
||||
|
||||
@@ -56,6 +56,7 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
|
||||
secretsLimit: 40
|
||||
},
|
||||
pkiEst: false,
|
||||
pkiAcme: false,
|
||||
enforceMfa: false,
|
||||
projectTemplates: false,
|
||||
kmip: false,
|
||||
|
||||
@@ -78,6 +78,7 @@ export type TFeatureSet = {
|
||||
secretsLimit: number;
|
||||
};
|
||||
pkiEst: boolean;
|
||||
pkiAcme: false;
|
||||
enforceMfa: boolean;
|
||||
projectTemplates: false;
|
||||
kmip: false;
|
||||
|
||||
@@ -29,6 +29,7 @@ import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||
import { getProjectKmsCertificateKeyId } from "@app/services/project/project-fns";
|
||||
|
||||
import { getConfig } from "@app/lib/config/env";
|
||||
import { TLicenseServiceFactory } from "../license/license-service";
|
||||
import { TPkiAcmeAccountDALFactory } from "./pki-acme-account-dal";
|
||||
import { TPkiAcmeAuthDALFactory } from "./pki-acme-auth-dal";
|
||||
import { TPkiAcmeChallengeDALFactory } from "./pki-acme-challenge-dal";
|
||||
@@ -42,6 +43,7 @@ import {
|
||||
AcmeMalformedError,
|
||||
AcmeOrderNotReadyError,
|
||||
AcmeServerInternalError,
|
||||
AcmeUnauthorizedError,
|
||||
AcmeUnsupportedIdentifierError
|
||||
} from "./pki-acme-errors";
|
||||
import { buildUrl, extractAccountIdFromKid, validateDnsIdentifier } from "./pki-acme-fns";
|
||||
@@ -101,6 +103,7 @@ type TPkiAcmeServiceFactoryDep = {
|
||||
>;
|
||||
keyStore: Pick<TKeyStoreFactory, "getItem" | "setItemWithExpiry" | "deleteItem">;
|
||||
kmsService: Pick<TKmsServiceFactory, "decryptWithKmsKey" | "generateKmsKey">;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
certificateV3Service: Pick<TCertificateV3ServiceFactory, "signCertificateFromProfile">;
|
||||
acmeChallengeService: TPkiAcmeChallengeServiceFactory;
|
||||
};
|
||||
@@ -116,6 +119,7 @@ export const pkiAcmeServiceFactory = ({
|
||||
acmeChallengeDAL,
|
||||
keyStore,
|
||||
kmsService,
|
||||
licenseService,
|
||||
certificateV3Service,
|
||||
acmeChallengeService
|
||||
}: TPkiAcmeServiceFactoryDep): TPkiAcmeServiceFactory => {
|
||||
@@ -127,6 +131,12 @@ export const pkiAcmeServiceFactory = ({
|
||||
if (profile.enrollmentType !== EnrollmentType.ACME) {
|
||||
throw new NotFoundError({ message: "Certificate profile is not configured for ACME enrollment" });
|
||||
}
|
||||
const orgLicensePlan = await licenseService.getPlan(profile.project!.orgId);
|
||||
if (!orgLicensePlan.pkiAcme) {
|
||||
throw new AcmeUnauthorizedError({
|
||||
message: "Failed to validate ACME profile: Plan restriction. Upgrade plan to continue"
|
||||
});
|
||||
}
|
||||
return profile;
|
||||
};
|
||||
|
||||
|
||||
@@ -106,10 +106,6 @@ const envSchema = z
|
||||
HTTPS_ENABLED: zodStrBool,
|
||||
ROTATION_DEVELOPMENT_MODE: zodStrBool.default("false").optional(),
|
||||
DAILY_RESOURCE_CLEAN_UP_DEVELOPMENT_MODE: zodStrBool.default("false").optional(),
|
||||
// Note: The ACME feature is still in development and is not yet ready for production.
|
||||
// This is the feature flag to enable/disable the ACME feature.
|
||||
// It's not intended to be used by users outside of the development team yet.
|
||||
ACME_FEATURE_ENABLED: zodStrBool.default("false").optional(),
|
||||
ACME_DEVELOPMENT_MODE: zodStrBool.default("false").optional(),
|
||||
ACME_DEVELOPMENT_HTTP01_CHALLENGE_HOST_OVERRIDES: zpStr(
|
||||
z
|
||||
@@ -399,7 +395,6 @@ const envSchema = z
|
||||
(data.NODE_ENV === "development" && data.ROTATION_DEVELOPMENT_MODE) || data.NODE_ENV === "test",
|
||||
isDailyResourceCleanUpDevelopmentMode:
|
||||
data.NODE_ENV === "development" && data.DAILY_RESOURCE_CLEAN_UP_DEVELOPMENT_MODE,
|
||||
isAcmeFeatureEnabled: data.NODE_ENV === "development" && data.ACME_FEATURE_ENABLED === true,
|
||||
isAcmeDevelopmentMode: data.NODE_ENV === "development" && data.ACME_DEVELOPMENT_MODE,
|
||||
isProductionMode: data.NODE_ENV === "production" || IS_PACKAGED,
|
||||
isRedisSentinelMode: Boolean(data.REDIS_SENTINEL_HOSTS),
|
||||
|
||||
@@ -31,10 +31,7 @@ export const registerServeUI = async (
|
||||
CAPTCHA_SITE_KEY: appCfg.CAPTCHA_SITE_KEY,
|
||||
POSTHOG_API_KEY: appCfg.POSTHOG_PROJECT_API_KEY,
|
||||
INTERCOM_ID: appCfg.INTERCOM_ID,
|
||||
TELEMETRY_CAPTURING_ENABLED: appCfg.TELEMETRY_ENABLED,
|
||||
// The feature flag to enable/disable the ACME feature.
|
||||
// Will be removed once the feature is ready for production.
|
||||
ACME_FEATURE_ENABLED: appCfg.isAcmeFeatureEnabled
|
||||
TELEMETRY_CAPTURING_ENABLED: appCfg.TELEMETRY_ENABLED
|
||||
};
|
||||
const js = `window.__INFISICAL_RUNTIME_ENV__ = Object.freeze(${JSON.stringify(config)});`;
|
||||
return res.send(js);
|
||||
|
||||
@@ -1184,6 +1184,7 @@ export const registerRoutes = async (
|
||||
certificateAuthorityDAL,
|
||||
certificateAuthorityCertDAL,
|
||||
permissionService,
|
||||
licenseService,
|
||||
kmsService,
|
||||
projectDAL
|
||||
});
|
||||
@@ -2238,6 +2239,7 @@ export const registerRoutes = async (
|
||||
acmeChallengeDAL,
|
||||
keyStore,
|
||||
kmsService,
|
||||
licenseService,
|
||||
certificateV3Service,
|
||||
acmeChallengeService
|
||||
});
|
||||
|
||||
@@ -85,6 +85,7 @@ export const certificateProfileDALFactory = (db: TDbClient) => {
|
||||
const findByIdWithConfigs = async (id: string, tx?: Knex): Promise<TCertificateProfileWithConfigs | undefined> => {
|
||||
try {
|
||||
const query = (tx || db)(TableName.PkiCertificateProfile)
|
||||
.leftJoin(TableName.Project, `${TableName.PkiCertificateProfile}.projectId`, `${TableName.Project}.id`)
|
||||
.leftJoin(
|
||||
TableName.CertificateAuthority,
|
||||
`${TableName.PkiCertificateProfile}.caId`,
|
||||
@@ -112,6 +113,8 @@ export const certificateProfileDALFactory = (db: TDbClient) => {
|
||||
)
|
||||
.select(selectAllTableCols(TableName.PkiCertificateProfile))
|
||||
.select(
|
||||
db.ref("id").withSchema(TableName.Project).as("projectId"),
|
||||
db.ref("orgId").withSchema(TableName.Project).as("orgId"),
|
||||
db.ref("id").withSchema(TableName.CertificateAuthority).as("caId"),
|
||||
db.ref("projectId").withSchema(TableName.CertificateAuthority).as("caProjectId"),
|
||||
db.ref("status").withSchema(TableName.CertificateAuthority).as("caStatus"),
|
||||
@@ -185,6 +188,11 @@ export const certificateProfileDALFactory = (db: TDbClient) => {
|
||||
} as TCertificateProfileWithConfigs["certificateTemplate"])
|
||||
: undefined;
|
||||
|
||||
const project = {
|
||||
id: result.projectId,
|
||||
orgId: result.orgId
|
||||
} as TCertificateProfileWithConfigs["project"];
|
||||
|
||||
const transformedResult: TCertificateProfileWithConfigs = {
|
||||
id: result.id,
|
||||
projectId: result.projectId,
|
||||
@@ -201,6 +209,7 @@ export const certificateProfileDALFactory = (db: TDbClient) => {
|
||||
estConfig,
|
||||
apiConfig,
|
||||
acmeConfig,
|
||||
project,
|
||||
certificateAuthority,
|
||||
certificateTemplate
|
||||
};
|
||||
|
||||
@@ -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<TPermissionServiceFactory, "getProjectPermission">;
|
||||
|
||||
const mockLicenseService = {
|
||||
getPlan: vi.fn()
|
||||
} as unknown as Pick<TLicenseServiceFactory, "getPlan">;
|
||||
|
||||
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);
|
||||
|
||||
@@ -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<TCertificateAuthorityDALFactory, "findById">;
|
||||
certificateAuthorityCertDAL: Pick<TCertificateAuthorityCertDALFactory, "findById">;
|
||||
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||
licenseService: Pick<TLicenseServiceFactory, "getPlan">;
|
||||
kmsService: Pick<TKmsServiceFactory, "generateKmsKey" | "encryptWithKmsKey" | "decryptWithKmsKey">;
|
||||
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug" | "findOne" | "updateById" | "findById" | "transaction">;
|
||||
};
|
||||
@@ -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);
|
||||
|
||||
@@ -33,6 +33,10 @@ export type TCertificateProfileUpdate = Omit<TPkiCertificateProfilesUpdate, "enr
|
||||
};
|
||||
|
||||
export type TCertificateProfileWithConfigs = TCertificateProfile & {
|
||||
project?: {
|
||||
id: string;
|
||||
orgId: string;
|
||||
};
|
||||
certificateAuthority?: {
|
||||
id: string;
|
||||
projectId: string;
|
||||
|
||||
@@ -26,9 +26,6 @@ export const envConfig = {
|
||||
import.meta.env.VITE_TELEMETRY_CAPTURING_ENABLED === true
|
||||
);
|
||||
},
|
||||
get ACME_FEATURE_ENABLED() {
|
||||
return window?.__INFISICAL_RUNTIME_ENV__?.ACME_FEATURE_ENABLED ?? false;
|
||||
},
|
||||
|
||||
get PLATFORM_VERSION() {
|
||||
return import.meta.env.VITE_INFISICAL_PLATFORM_VERSION;
|
||||
|
||||
1
frontend/src/global.d.ts
vendored
1
frontend/src/global.d.ts
vendored
@@ -7,7 +7,6 @@ declare global {
|
||||
POSTHOG_API_KEY?: string;
|
||||
INTERCOM_ID?: string;
|
||||
TELEMETRY_CAPTURING_ENABLED: string;
|
||||
ACME_FEATURE_ENABLED?: boolean;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ export type SubscriptionPlan = {
|
||||
gateway: boolean;
|
||||
externalKms: boolean;
|
||||
pkiEst: boolean;
|
||||
pkiAcme: boolean;
|
||||
pkiLegacyTemplates: boolean;
|
||||
enforceMfa: boolean;
|
||||
enforceGoogleSSO: boolean;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useState } from "react";
|
||||
import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { useState } from "react";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { Button, DeleteActionModal } from "@app/components/v2";
|
||||
@@ -14,6 +14,8 @@ import {
|
||||
useDeleteCertificateProfile
|
||||
} from "@app/hooks/api/certificateProfiles";
|
||||
|
||||
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { CreateProfileModal } from "./CreateProfileModal";
|
||||
import { ProfileList } from "./ProfileList";
|
||||
import { RevealAcmeEabSecretModal } from "./RevealAcmeEabSecretModal";
|
||||
@@ -29,6 +31,7 @@ export const CertificateProfilesTab = () => {
|
||||
const [selectedProfile, setSelectedProfile] = useState<TCertificateProfileWithDetails | null>(
|
||||
null
|
||||
);
|
||||
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["upgradePlan"] as const);
|
||||
|
||||
const deleteProfile = useDeleteCertificateProfile();
|
||||
|
||||
@@ -99,7 +102,17 @@ export const CertificateProfilesTab = () => {
|
||||
onDeleteProfile={handleDeleteProfile}
|
||||
/>
|
||||
|
||||
<CreateProfileModal isOpen={isCreateModalOpen} onClose={() => setIsCreateModalOpen(false)} />
|
||||
<CreateProfileModal
|
||||
isOpen={isCreateModalOpen}
|
||||
onClose={() => setIsCreateModalOpen(false)}
|
||||
handlePopUpOpen={handlePopUpOpen}
|
||||
/>
|
||||
<UpgradePlanModal
|
||||
isOpen={popUp.upgradePlan.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||
isEnterpriseFeature={popUp.upgradePlan.data?.isEnterpriseFeature}
|
||||
text="Your current plan does not include access to managing template enrollment options for ACME. To unlock this feature, please upgrade to Infisical Enterprise plan."
|
||||
/>
|
||||
|
||||
{selectedProfile && (
|
||||
<>
|
||||
@@ -109,6 +122,7 @@ export const CertificateProfilesTab = () => {
|
||||
setIsEditModalOpen(false);
|
||||
setSelectedProfile(null);
|
||||
}}
|
||||
handlePopUpOpen={handlePopUpOpen}
|
||||
profile={selectedProfile}
|
||||
mode="edit"
|
||||
/>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useEffect } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { faQuestionCircle } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useEffect } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
@@ -18,8 +18,7 @@ import {
|
||||
TextArea,
|
||||
Tooltip
|
||||
} from "@app/components/v2";
|
||||
import { envConfig } from "@app/config/env";
|
||||
import { useProject } from "@app/context";
|
||||
import { useProject, useSubscription } from "@app/context";
|
||||
import { useListCasByProjectId } from "@app/hooks/api/ca/queries";
|
||||
import {
|
||||
TCertificateProfileWithDetails,
|
||||
@@ -29,6 +28,7 @@ import {
|
||||
useUpdateCertificateProfile
|
||||
} from "@app/hooks/api/certificateProfiles";
|
||||
import { useListCertificateTemplatesV2 } from "@app/hooks/api/certificateTemplates/queries";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
const createSchema = z
|
||||
.object({
|
||||
@@ -151,12 +151,25 @@ export type FormData = z.infer<typeof createSchema>;
|
||||
interface Props {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
handlePopUpOpen: (
|
||||
popUpName: keyof UsePopUpState<["upgradePlan"]>,
|
||||
data?: {
|
||||
isEnterpriseFeature?: boolean;
|
||||
}
|
||||
) => void;
|
||||
profile?: TCertificateProfileWithDetails;
|
||||
mode?: "create" | "edit";
|
||||
}
|
||||
|
||||
export const CreateProfileModal = ({ isOpen, onClose, profile, mode = "create" }: Props) => {
|
||||
export const CreateProfileModal = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
handlePopUpOpen,
|
||||
profile,
|
||||
mode = "create"
|
||||
}: Props) => {
|
||||
const { currentProject } = useProject();
|
||||
const { subscription } = useSubscription();
|
||||
|
||||
const { data: caData } = useListCasByProjectId(currentProject?.id || "");
|
||||
const { data: templateData } = useListCertificateTemplatesV2({
|
||||
@@ -248,6 +261,15 @@ export const CreateProfileModal = ({ isOpen, onClose, profile, mode = "create" }
|
||||
}, [isEdit, profile, reset]);
|
||||
|
||||
const onFormSubmit = async (data: FormData) => {
|
||||
if (!isEdit && !subscription?.pkiAcme && data.enrollmentType === "acme") {
|
||||
reset();
|
||||
onClose();
|
||||
handlePopUpOpen("upgradePlan", {
|
||||
isEnterpriseFeature: true
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!currentProject?.id && !isEdit) return;
|
||||
|
||||
if (isEdit) {
|
||||
@@ -467,7 +489,7 @@ export const CreateProfileModal = ({ isOpen, onClose, profile, mode = "create" }
|
||||
>
|
||||
<SelectItem value="api">API</SelectItem>
|
||||
<SelectItem value="est">EST</SelectItem>
|
||||
{envConfig.ACME_FEATURE_ENABLED && <SelectItem value="acme">ACME</SelectItem>}
|
||||
<SelectItem value="acme">ACME</SelectItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user