From bb6416acb7efe1f2c8dea9f1cd6fc85e2441ed70 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Tue, 13 Aug 2024 11:22:45 -0700 Subject: [PATCH 1/5] Update CA certificate tracking impl to use foreign ref instead of number --- .../20240802181855_ca-cert-version.ts | 21 +++--- .../src/db/schemas/certificate-authorities.ts | 2 +- backend/src/db/schemas/dynamic-secrets.ts | 10 ++- backend/src/db/schemas/webhooks.ts | 15 ++-- .../routes/v1/certificate-authority-router.ts | 2 +- .../certificate-authority-fns.ts | 3 +- .../certificate-authority-service.ts | 73 ++++++++++--------- .../certificate-authority-types.ts | 2 +- frontend/src/hooks/api/ca/mutations.tsx | 4 +- frontend/src/hooks/api/ca/types.ts | 3 +- 10 files changed, 77 insertions(+), 58 deletions(-) diff --git a/backend/src/db/migrations/20240802181855_ca-cert-version.ts b/backend/src/db/migrations/20240802181855_ca-cert-version.ts index 7c7d956fc..805cf0a53 100644 --- a/backend/src/db/migrations/20240802181855_ca-cert-version.ts +++ b/backend/src/db/migrations/20240802181855_ca-cert-version.ts @@ -4,16 +4,19 @@ import { TableName } from "../schemas"; export async function up(knex: Knex): Promise { if (await knex.schema.hasTable(TableName.CertificateAuthority)) { - const hasActiveCaCertVersionColumn = await knex.schema.hasColumn( - TableName.CertificateAuthority, - "activeCaCertVersion" - ); - if (!hasActiveCaCertVersionColumn) { + const hasActiveCaCertIdColumn = await knex.schema.hasColumn(TableName.CertificateAuthority, "activeCaCertId"); + if (!hasActiveCaCertIdColumn) { await knex.schema.alterTable(TableName.CertificateAuthority, (t) => { - t.integer("activeCaCertVersion").nullable(); + t.uuid("activeCaCertId").nullable(); + t.foreign("activeCaCertId").references("id").inTable(TableName.CertificateAuthorityCert); }); - await knex(TableName.CertificateAuthority).where("status", "active").update({ activeCaCertVersion: 1 }); + await knex.raw(` + UPDATE "${TableName.CertificateAuthority}" ca + SET "activeCaCertId" = cac.id + FROM "${TableName.CertificateAuthorityCert}" cac + WHERE ca.id = cac."caId" + `); } } @@ -63,9 +66,9 @@ export async function up(knex: Knex): Promise { export async function down(knex: Knex): Promise { if (await knex.schema.hasTable(TableName.CertificateAuthority)) { - if (await knex.schema.hasColumn(TableName.CertificateAuthority, "activeCaCertVersion")) { + if (await knex.schema.hasColumn(TableName.CertificateAuthority, "activeCaCertId")) { await knex.schema.alterTable(TableName.CertificateAuthority, (t) => { - t.dropColumn("activeCaCertVersion"); + t.dropColumn("activeCaCertId"); }); } } diff --git a/backend/src/db/schemas/certificate-authorities.ts b/backend/src/db/schemas/certificate-authorities.ts index 9255b037e..e59a9225c 100644 --- a/backend/src/db/schemas/certificate-authorities.ts +++ b/backend/src/db/schemas/certificate-authorities.ts @@ -28,7 +28,7 @@ export const CertificateAuthoritiesSchema = z.object({ keyAlgorithm: z.string(), notBefore: z.date().nullable().optional(), notAfter: z.date().nullable().optional(), - activeCaCertVersion: z.number().nullable().optional() + activeCaCertId: z.string().uuid().nullable().optional() }); export type TCertificateAuthorities = z.infer; diff --git a/backend/src/db/schemas/dynamic-secrets.ts b/backend/src/db/schemas/dynamic-secrets.ts index d90f1f7d2..b27da396c 100644 --- a/backend/src/db/schemas/dynamic-secrets.ts +++ b/backend/src/db/schemas/dynamic-secrets.ts @@ -5,8 +5,6 @@ import { z } from "zod"; -import { zodBuffer } from "@app/lib/zod"; - import { TImmutableDBKeys } from "./models"; export const DynamicSecretsSchema = z.object({ @@ -16,12 +14,16 @@ export const DynamicSecretsSchema = z.object({ type: z.string(), defaultTTL: z.string(), maxTTL: z.string().nullable().optional(), + inputIV: z.string(), + inputCiphertext: z.string(), + inputTag: z.string(), + algorithm: z.string().default("aes-256-gcm"), + keyEncoding: z.string().default("utf8"), folderId: z.string().uuid(), status: z.string().nullable().optional(), statusDetails: z.string().nullable().optional(), createdAt: z.date(), - updatedAt: z.date(), - encryptedConfig: zodBuffer + updatedAt: z.date() }); export type TDynamicSecrets = z.infer; diff --git a/backend/src/db/schemas/webhooks.ts b/backend/src/db/schemas/webhooks.ts index 3f670497f..a7aac2933 100644 --- a/backend/src/db/schemas/webhooks.ts +++ b/backend/src/db/schemas/webhooks.ts @@ -5,22 +5,27 @@ import { z } from "zod"; -import { zodBuffer } from "@app/lib/zod"; - import { TImmutableDBKeys } from "./models"; export const WebhooksSchema = z.object({ id: z.string().uuid(), secretPath: z.string().default("/"), + url: z.string(), lastStatus: z.string().nullable().optional(), lastRunErrorMessage: z.string().nullable().optional(), isDisabled: z.boolean().default(false), + encryptedSecretKey: z.string().nullable().optional(), + iv: z.string().nullable().optional(), + tag: z.string().nullable().optional(), + algorithm: z.string().nullable().optional(), + keyEncoding: z.string().nullable().optional(), createdAt: z.date(), updatedAt: z.date(), envId: z.string().uuid(), - type: z.string().default("general").nullable().optional(), - encryptedSecretKeyWithKms: zodBuffer.nullable().optional(), - encryptedUrl: zodBuffer + urlCipherText: z.string().nullable().optional(), + urlIV: z.string().nullable().optional(), + urlTag: z.string().nullable().optional(), + type: z.string().default("general").nullable().optional() }); export type TWebhooks = z.infer; diff --git a/backend/src/server/routes/v1/certificate-authority-router.ts b/backend/src/server/routes/v1/certificate-authority-router.ts index bc5294f05..103d430c0 100644 --- a/backend/src/server/routes/v1/certificate-authority-router.ts +++ b/backend/src/server/routes/v1/certificate-authority-router.ts @@ -283,7 +283,7 @@ export const registerCaRouter = async (server: FastifyZodProvider) => { }, onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), schema: { - description: "Renew CA certificate for CA", + description: "Perform CA certificate renewal", params: z.object({ caId: z.string().trim().describe(CERTIFICATE_AUTHORITIES.RENEW_CA_CERT.caId) }), diff --git a/backend/src/services/certificate-authority/certificate-authority-fns.ts b/backend/src/services/certificate-authority/certificate-authority-fns.ts index 84d8d263d..bf9f639e8 100644 --- a/backend/src/services/certificate-authority/certificate-authority-fns.ts +++ b/backend/src/services/certificate-authority/certificate-authority-fns.ts @@ -206,8 +206,9 @@ export const getCaCertChain = async ({ }: TGetCaCertChainDTO) => { const ca = await certificateAuthorityDAL.findById(caId); if (!ca) throw new BadRequestError({ message: "CA not found" }); + if (!ca.activeCaCertId) throw new BadRequestError({ message: "CA does not have a certificate installed" }); - const caCert = await certificateAuthorityCertDAL.findOne({ caId: ca.id }); + const caCert = await certificateAuthorityCertDAL.findById(ca.activeCaCertId); const keyId = await getProjectKmsCertificateKeyId({ projectId: ca.projectId, diff --git a/backend/src/services/certificate-authority/certificate-authority-service.ts b/backend/src/services/certificate-authority/certificate-authority-service.ts index 52cfa8039..68d9bbf01 100644 --- a/backend/src/services/certificate-authority/certificate-authority-service.ts +++ b/backend/src/services/certificate-authority/certificate-authority-service.ts @@ -51,7 +51,10 @@ type TCertificateAuthorityServiceFactoryDep = { TCertificateAuthorityDALFactory, "transaction" | "create" | "findById" | "updateById" | "deleteById" | "findOne" >; - certificateAuthorityCertDAL: Pick; + certificateAuthorityCertDAL: Pick< + TCertificateAuthorityCertDALFactory, + "create" | "findOne" | "transaction" | "find" | "findById" + >; certificateAuthoritySecretDAL: Pick; certificateAuthorityCrlDAL: Pick; certificateAuthorityQueue: TCertificateAuthorityQueueFactory; // TODO: Pick @@ -153,8 +156,7 @@ export const certificateAuthorityServiceFactory = ({ maxPathLength, notBefore: notBeforeDate, notAfter: notAfterDate, - serialNumber, - activeCaCertVersion: 1 + serialNumber }) }, tx @@ -213,7 +215,7 @@ export const certificateAuthorityServiceFactory = ({ plainText: Buffer.alloc(0) }); - await certificateAuthorityCertDAL.create( + const caCert = await certificateAuthorityCertDAL.create( { caId: ca.id, encryptedCertificate, @@ -223,6 +225,14 @@ export const certificateAuthorityServiceFactory = ({ }, tx ); + + await certificateAuthorityDAL.updateById( + ca.id, + { + activeCaCertId: caCert.id + }, + tx + ); } // create empty CRL @@ -347,9 +357,7 @@ export const certificateAuthorityServiceFactory = ({ ); if (ca.type === CaType.ROOT) throw new BadRequestError({ message: "Root CA cannot generate CSR" }); - - const [caCert] = await certificateAuthorityCertDAL.find({ caId: ca.id }, { sort: [["version", "desc"]] }); - if (caCert) throw new BadRequestError({ message: "CA already has a certificate installed" }); + if (ca.activeCaCertId) throw new BadRequestError({ message: "CA already has a certificate installed" }); const { caPrivateKey, caPublicKey } = await getCaCredentials({ caId, @@ -394,6 +402,8 @@ export const certificateAuthorityServiceFactory = ({ const ca = await certificateAuthorityDAL.findById(caId); if (!ca) throw new BadRequestError({ message: "CA not found" }); + if (!ca.activeCaCertId) throw new BadRequestError({ message: "CA does not have a certificate installed" }); + const { permission } = await permissionService.getProjectPermission( actor, actorId, @@ -410,8 +420,7 @@ export const certificateAuthorityServiceFactory = ({ if (ca.status === CaStatus.DISABLED) throw new BadRequestError({ message: "CA is disabled" }); // get latest CA certificate - const [caCert] = await certificateAuthorityCertDAL.find({ caId: ca.id }, { sort: [["version", "desc"]] }); - if (!caCert) throw new BadRequestError({ message: "CA does not have a certificate installed" }); + const caCert = await certificateAuthorityCertDAL.findById(ca.activeCaCertId); const serialNumber = crypto.randomBytes(32).toString("hex"); @@ -489,13 +498,12 @@ export const certificateAuthorityServiceFactory = ({ }); await certificateAuthorityDAL.transaction(async (tx) => { - const newActiveCaCertVersion = caCert.version + 1; - await certificateAuthorityCertDAL.create( + const newCaCert = await certificateAuthorityCertDAL.create( { caId: ca.id, encryptedCertificate, encryptedCertificateChain, - version: newActiveCaCertVersion, + version: caCert.version + 1, caSecretId: caSecret.id }, tx @@ -504,7 +512,7 @@ export const certificateAuthorityServiceFactory = ({ await certificateAuthorityDAL.updateById( ca.id, { - activeCaCertVersion: newActiveCaCertVersion, + activeCaCertId: newCaCert.id, notBefore: notBeforeDate, notAfter: new Date(notAfter) }, @@ -533,10 +541,9 @@ export const certificateAuthorityServiceFactory = ({ }); // get latest parent CA certificate - const [parentCaCert] = await certificateAuthorityCertDAL.find( - { caId: parentCa.id }, - { sort: [["version", "desc"]] } - ); + if (!parentCa.activeCaCertId) + throw new BadRequestError({ message: "Parent CA does not have a certificate installed" }); + const parentCaCert = await certificateAuthorityCertDAL.findById(parentCa.activeCaCertId); const decryptedParentCaCert = await kmsDecryptor({ cipherTextBlob: parentCaCert.encryptedCertificate @@ -581,7 +588,7 @@ export const certificateAuthorityServiceFactory = ({ const intermediateCert = await x509.X509CertificateGenerator.create({ serialNumber, subject: csrObj.subject, - issuer: caCertObj.subject, + issuer: parentCaCertObj.subject, notBefore: notBeforeDate, notAfter: new Date(notAfter), signingKey: parentCaPrivateKey, @@ -600,7 +607,7 @@ export const certificateAuthorityServiceFactory = ({ ca.maxPathLength === -1 || !ca.maxPathLength ? undefined : ca.maxPathLength, true ), - await x509.AuthorityKeyIdentifierExtension.create(caCertObj, false), + await x509.AuthorityKeyIdentifierExtension.create(parentCaCertObj, false), await x509.SubjectKeyIdentifierExtension.create(csrObj.publicKey) ] }); @@ -624,13 +631,12 @@ export const certificateAuthorityServiceFactory = ({ }); await certificateAuthorityDAL.transaction(async (tx) => { - const newActiveCaCertVersion = caCert.version + 1; - await certificateAuthorityCertDAL.create( + const newCaCert = await certificateAuthorityCertDAL.create( { caId: ca.id, encryptedCertificate, encryptedCertificateChain, - version: newActiveCaCertVersion, + version: caCert.version + 1, caSecretId: caSecret.id }, tx @@ -639,7 +645,7 @@ export const certificateAuthorityServiceFactory = ({ await certificateAuthorityDAL.updateById( ca.id, { - activeCaCertVersion: newActiveCaCertVersion, + activeCaCertId: newCaCert.id, notBefore: notBeforeDate, notAfter: new Date(notAfter) }, @@ -764,9 +770,9 @@ export const certificateAuthorityServiceFactory = ({ ); if (ca.status === CaStatus.DISABLED) throw new BadRequestError({ message: "CA is disabled" }); + if (!ca.activeCaCertId) throw new BadRequestError({ message: "CA does not have a certificate installed" }); - const [caCert] = await certificateAuthorityCertDAL.find({ caId: ca.id }, { sort: [["version", "desc"]] }); - if (!caCert) throw new BadRequestError({ message: "CA does not have a certificate installed" }); + const caCert = await certificateAuthorityCertDAL.findById(ca.activeCaCertId); if (ca.notAfter && new Date() > new Date(ca.notAfter)) { throw new BadRequestError({ message: "CA is expired" }); @@ -900,8 +906,7 @@ export const certificateAuthorityServiceFactory = ({ ProjectPermissionSub.CertificateAuthorities ); - const [caCert] = await certificateAuthorityCertDAL.find({ caId: ca.id }, { sort: [["version", "desc"]] }); - if (caCert) throw new BadRequestError({ message: "CA has already imported a certificate" }); + if (ca.activeCaCertId) throw new BadRequestError({ message: "CA has already imported a certificate" }); const certObj = new x509.X509Certificate(certificate); const maxPathLength = certObj.getExtension(x509.BasicConstraintsExtension)?.pathLength; @@ -967,7 +972,7 @@ export const certificateAuthorityServiceFactory = ({ } await certificateAuthorityCertDAL.transaction(async (tx) => { - await certificateAuthorityCertDAL.create( + const newCaCert = await certificateAuthorityCertDAL.create( { caId: ca.id, encryptedCertificate, @@ -986,7 +991,8 @@ export const certificateAuthorityServiceFactory = ({ notBefore: new Date(certObj.notBefore), notAfter: new Date(certObj.notAfter), serialNumber: certObj.serialNumber, - parentCaId: parentCa?.id + parentCaId: parentCa?.id, + activeCaCertId: newCaCert.id }, tx ); @@ -1026,9 +1032,8 @@ export const certificateAuthorityServiceFactory = ({ ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Certificates); if (ca.status === CaStatus.DISABLED) throw new BadRequestError({ message: "CA is disabled" }); - - const [caCert] = await certificateAuthorityCertDAL.find({ caId: ca.id }, { sort: [["version", "desc"]] }); - if (!caCert) throw new BadRequestError({ message: "CA does not have a certificate installed" }); + if (!ca.activeCaCertId) throw new BadRequestError({ message: "CA does not have a certificate installed" }); + const caCert = await certificateAuthorityCertDAL.findById(ca.activeCaCertId); if (ca.notAfter && new Date() > new Date(ca.notAfter)) { throw new BadRequestError({ message: "CA is expired" }); @@ -1233,9 +1238,9 @@ export const certificateAuthorityServiceFactory = ({ ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Certificates); if (ca.status === CaStatus.DISABLED) throw new BadRequestError({ message: "CA is disabled" }); + if (!ca.activeCaCertId) throw new BadRequestError({ message: "CA does not have a certificate installed" }); - const [caCert] = await certificateAuthorityCertDAL.find({ caId: ca.id }, { sort: [["version", "desc"]] }); - if (!caCert) throw new BadRequestError({ message: "CA does not have a certificate installed" }); + const caCert = await certificateAuthorityCertDAL.findById(ca.activeCaCertId); if (ca.notAfter && new Date() > new Date(ca.notAfter)) { throw new BadRequestError({ message: "CA is expired" }); diff --git a/backend/src/services/certificate-authority/certificate-authority-types.ts b/backend/src/services/certificate-authority/certificate-authority-types.ts index f97844870..91e76e49c 100644 --- a/backend/src/services/certificate-authority/certificate-authority-types.ts +++ b/backend/src/services/certificate-authority/certificate-authority-types.ts @@ -134,7 +134,7 @@ export type TGetCaCertChainsDTO = { export type TGetCaCertChainDTO = { caId: string; certificateAuthorityDAL: Pick; - certificateAuthorityCertDAL: Pick; + certificateAuthorityCertDAL: Pick; projectDAL: Pick; kmsService: Pick; }; diff --git a/frontend/src/hooks/api/ca/mutations.tsx b/frontend/src/hooks/api/ca/mutations.tsx index 54109adfc..27d03248f 100644 --- a/frontend/src/hooks/api/ca/mutations.tsx +++ b/frontend/src/hooks/api/ca/mutations.tsx @@ -16,7 +16,8 @@ import { TRenewCaResponse, TSignIntermediateDTO, TSignIntermediateResponse, - TUpdateCaDTO} from "./types"; + TUpdateCaDTO +} from "./types"; export const useCreateCa = () => { const queryClient = useQueryClient(); @@ -123,6 +124,7 @@ export const useRenewCa = () => { }, onSuccess: (_, { caId, projectSlug }) => { queryClient.invalidateQueries(workspaceKeys.getWorkspaceCas({ projectSlug })); + queryClient.invalidateQueries(caKeys.getCaById(caId)); queryClient.invalidateQueries(caKeys.getCaCert(caId)); queryClient.invalidateQueries(caKeys.getCaCerts(caId)); queryClient.invalidateQueries(caKeys.getCaCsr(caId)); diff --git a/frontend/src/hooks/api/ca/types.ts b/frontend/src/hooks/api/ca/types.ts index bba7aa699..7513070c8 100644 --- a/frontend/src/hooks/api/ca/types.ts +++ b/frontend/src/hooks/api/ca/types.ts @@ -1,5 +1,5 @@ import { CertKeyAlgorithm } from "../certificates/enums"; -import { CaRenewalType,CaStatus, CaType } from "./enums"; +import { CaRenewalType, CaStatus, CaType } from "./enums"; export type TCertificateAuthority = { id: string; @@ -19,6 +19,7 @@ export type TCertificateAuthority = { notAfter?: string; notBefore?: string; keyAlgorithm: CertKeyAlgorithm; + activeCaCertId?: string; createdAt: string; updatedAt: string; }; From f993e4aa5c4f3389fadf3a8e8b05cb5906d65b7f Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Tue, 13 Aug 2024 11:31:57 -0700 Subject: [PATCH 2/5] Clear type check issue --- backend/src/services/certificate/certificate-service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/services/certificate/certificate-service.ts b/backend/src/services/certificate/certificate-service.ts index f05ed8a87..01f736c29 100644 --- a/backend/src/services/certificate/certificate-service.ts +++ b/backend/src/services/certificate/certificate-service.ts @@ -21,7 +21,7 @@ type TCertificateServiceFactoryDep = { certificateDAL: Pick; certificateBodyDAL: Pick; certificateAuthorityDAL: Pick; - certificateAuthorityCertDAL: Pick; + certificateAuthorityCertDAL: Pick; certificateAuthorityCrlDAL: Pick; certificateAuthoritySecretDAL: Pick; projectDAL: Pick; From 856c2423be0e83a7dd33c1f98825ef086530df38 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Wed, 14 Aug 2024 15:43:49 -0700 Subject: [PATCH 3/5] Update cert structure to ref correct ca cert in cert chain retrieval --- .../20240802181855_ca-cert-version.ts | 28 +++++++++++++++++++ backend/src/db/schemas/certificates.ts | 3 +- .../certificate-authority-fns.ts | 12 ++++---- .../certificate-authority-service.ts | 14 ++++++---- .../certificate-authority-types.ts | 2 +- .../certificate/certificate-service.ts | 2 +- 6 files changed, 45 insertions(+), 16 deletions(-) diff --git a/backend/src/db/migrations/20240802181855_ca-cert-version.ts b/backend/src/db/migrations/20240802181855_ca-cert-version.ts index 805cf0a53..24eca185d 100644 --- a/backend/src/db/migrations/20240802181855_ca-cert-version.ts +++ b/backend/src/db/migrations/20240802181855_ca-cert-version.ts @@ -62,6 +62,26 @@ export async function up(knex: Knex): Promise { t.dropUnique(["caId"]); }); } + + if (await knex.schema.hasTable(TableName.Certificate)) { + await knex.schema.alterTable(TableName.Certificate, (t) => { + t.uuid("caCertId").nullable(); + t.foreign("caCertId").references("id").inTable(TableName.CertificateAuthorityCert); + }); + + await knex.raw(` + UPDATE "${TableName.Certificate}" cert + SET "caCertId" = ( + SELECT caCert.id + FROM "${TableName.CertificateAuthorityCert}" caCert + WHERE caCert."caId" = cert."caId" + ) + `); + + await knex.schema.alterTable(TableName.Certificate, (t) => { + t.uuid("caCertId").notNullable().alter(); + }); + } } export async function down(knex: Knex): Promise { @@ -86,4 +106,12 @@ export async function down(knex: Knex): Promise { }); } } + + if (await knex.schema.hasTable(TableName.Certificate)) { + if (await knex.schema.hasColumn(TableName.Certificate, "caCertId")) { + await knex.schema.alterTable(TableName.Certificate, (t) => { + t.dropColumn("caCertId"); + }); + } + } } diff --git a/backend/src/db/schemas/certificates.ts b/backend/src/db/schemas/certificates.ts index 833396fb1..cb14d05f9 100644 --- a/backend/src/db/schemas/certificates.ts +++ b/backend/src/db/schemas/certificates.ts @@ -20,7 +20,8 @@ export const CertificatesSchema = z.object({ notAfter: z.date(), revokedAt: z.date().nullable().optional(), revocationReason: z.number().nullable().optional(), - altNames: z.string().default("").nullable().optional() + altNames: z.string().default("").nullable().optional(), + caCertId: z.string().uuid() }); export type TCertificates = z.infer; diff --git a/backend/src/services/certificate-authority/certificate-authority-fns.ts b/backend/src/services/certificate-authority/certificate-authority-fns.ts index bf9f639e8..4e3df1173 100644 --- a/backend/src/services/certificate-authority/certificate-authority-fns.ts +++ b/backend/src/services/certificate-authority/certificate-authority-fns.ts @@ -195,20 +195,18 @@ export const getCaCertChains = async ({ /** * Return the decrypted pem-encoded certificate and certificate chain - * for CA with id [caId]. + * corresponding to CA certificate with id [caCertId]. */ export const getCaCertChain = async ({ - caId, + caCertId, certificateAuthorityDAL, certificateAuthorityCertDAL, projectDAL, kmsService }: TGetCaCertChainDTO) => { - const ca = await certificateAuthorityDAL.findById(caId); - if (!ca) throw new BadRequestError({ message: "CA not found" }); - if (!ca.activeCaCertId) throw new BadRequestError({ message: "CA does not have a certificate installed" }); - - const caCert = await certificateAuthorityCertDAL.findById(ca.activeCaCertId); + const caCert = await certificateAuthorityCertDAL.findById(caCertId); + if (!caCert) throw new BadRequestError({ message: "CA certificate not found" }); + const ca = await certificateAuthorityDAL.findById(caCert.caId); const keyId = await getProjectKmsCertificateKeyId({ projectId: ca.projectId, diff --git a/backend/src/services/certificate-authority/certificate-authority-service.ts b/backend/src/services/certificate-authority/certificate-authority-service.ts index 68d9bbf01..b63ec9ba9 100644 --- a/backend/src/services/certificate-authority/certificate-authority-service.ts +++ b/backend/src/services/certificate-authority/certificate-authority-service.ts @@ -617,7 +617,7 @@ export const certificateAuthorityServiceFactory = ({ }); const { caCert: parentCaCertificate, caCertChain: parentCaCertChain } = await getCaCertChain({ - caId: parentCa.id, + caCertId: parentCa.activeCaCertId, certificateAuthorityDAL, certificateAuthorityCertDAL, projectDAL, @@ -704,11 +704,11 @@ export const certificateAuthorityServiceFactory = ({ /** * Return current certificate and certificate chain for CA - * get latest?? ca cert */ const getCaCert = async ({ caId, actorId, actorAuthMethod, actor, actorOrgId }: TGetCaCertDTO) => { const ca = await certificateAuthorityDAL.findById(caId); if (!ca) throw new BadRequestError({ message: "CA not found" }); + if (!ca.activeCaCertId) throw new BadRequestError({ message: "CA does not have a certificate installed" }); const { permission } = await permissionService.getProjectPermission( actor, @@ -724,7 +724,7 @@ export const certificateAuthorityServiceFactory = ({ ); const { caCert, caCertChain, serialNumber } = await getCaCertChain({ - caId, + caCertId: ca.activeCaCertId, certificateAuthorityDAL, certificateAuthorityCertDAL, projectDAL, @@ -860,7 +860,7 @@ export const certificateAuthorityServiceFactory = ({ }); const { caCert: issuingCaCertificate, caCertChain } = await getCaCertChain({ - caId, + caCertId: ca.activeCaCertId, certificateAuthorityDAL, certificateAuthorityCertDAL, projectDAL, @@ -1166,6 +1166,7 @@ export const certificateAuthorityServiceFactory = ({ const cert = await certificateDAL.create( { caId: ca.id, + caCertId: caCert.id, status: CertStatus.ACTIVE, friendlyName: friendlyName || commonName, commonName, @@ -1189,7 +1190,7 @@ export const certificateAuthorityServiceFactory = ({ }); const { caCert: issuingCaCertificate, caCertChain } = await getCaCertChain({ - caId: ca.id, + caCertId: caCert.id, certificateAuthorityDAL, certificateAuthorityCertDAL, projectDAL, @@ -1369,6 +1370,7 @@ export const certificateAuthorityServiceFactory = ({ const cert = await certificateDAL.create( { caId: ca.id, + caCertId: caCert.id, status: CertStatus.ACTIVE, friendlyName: friendlyName || csrObj.subject, commonName: cn, @@ -1392,7 +1394,7 @@ export const certificateAuthorityServiceFactory = ({ }); const { caCert: issuingCaCertificate, caCertChain } = await getCaCertChain({ - caId: ca.id, + caCertId: ca.activeCaCertId, certificateAuthorityDAL, certificateAuthorityCertDAL, projectDAL, diff --git a/backend/src/services/certificate-authority/certificate-authority-types.ts b/backend/src/services/certificate-authority/certificate-authority-types.ts index 91e76e49c..31a6e1629 100644 --- a/backend/src/services/certificate-authority/certificate-authority-types.ts +++ b/backend/src/services/certificate-authority/certificate-authority-types.ts @@ -132,7 +132,7 @@ export type TGetCaCertChainsDTO = { }; export type TGetCaCertChainDTO = { - caId: string; + caCertId: string; certificateAuthorityDAL: Pick; certificateAuthorityCertDAL: Pick; projectDAL: Pick; diff --git a/backend/src/services/certificate/certificate-service.ts b/backend/src/services/certificate/certificate-service.ts index 01f736c29..8dc2de901 100644 --- a/backend/src/services/certificate/certificate-service.ts +++ b/backend/src/services/certificate/certificate-service.ts @@ -180,7 +180,7 @@ export const certificateServiceFactory = ({ const certObj = new x509.X509Certificate(decryptedCert); const { caCert, caCertChain } = await getCaCertChain({ - caId: ca.id, + caCertId: cert.caCertId, certificateAuthorityDAL, certificateAuthorityCertDAL, projectDAL, From 7baa3b4cbe7ad123e79f59612b49992ee5197dd4 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Thu, 15 Aug 2024 10:41:37 -0700 Subject: [PATCH 4/5] Add PKI collection to issue cert modal --- backend/src/lib/api-docs/constants.ts | 2 + backend/src/server/routes/index.ts | 2 + .../routes/v1/certificate-authority-router.ts | 2 + .../certificate-authority-service.ts | 44 ++++++++++++++++- .../certificate-authority-types.ts | 2 + frontend/src/hooks/api/ca/types.ts | 1 + .../components/CertificateModal.tsx | 49 ++++++++++++++++++- 7 files changed, 99 insertions(+), 3 deletions(-) diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index 336776dbf..d79faa647 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -1088,6 +1088,7 @@ export const CERTIFICATE_AUTHORITIES = { }, ISSUE_CERT: { caId: "The ID of the CA to issue the certificate from", + pkiCollectionId: "The ID of the PKI collection to add the certificate to", friendlyName: "A friendly name for the certificate", commonName: "The common name (CN) for the certificate", altNames: @@ -1103,6 +1104,7 @@ export const CERTIFICATE_AUTHORITIES = { }, SIGN_CERT: { caId: "The ID of the CA to issue the certificate from", + pkiCollectionId: "The ID of the PKI collection to add the certificate to", csr: "The pem-encoded CSR to sign with the CA to be used for certificate issuance", friendlyName: "A friendly name for the certificate", commonName: "The common name (CN) for the certificate", diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index dbd223d7b..41d5f3249 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -622,6 +622,8 @@ export const registerRoutes = async ( certificateAuthorityQueue, certificateDAL, certificateBodyDAL, + pkiCollectionDAL, + pkiCollectionItemDAL, projectDAL, kmsService, permissionService diff --git a/backend/src/server/routes/v1/certificate-authority-router.ts b/backend/src/server/routes/v1/certificate-authority-router.ts index 103d430c0..35de2b953 100644 --- a/backend/src/server/routes/v1/certificate-authority-router.ts +++ b/backend/src/server/routes/v1/certificate-authority-router.ts @@ -556,6 +556,7 @@ export const registerCaRouter = async (server: FastifyZodProvider) => { }), body: z .object({ + pkiCollectionId: z.string().trim().optional().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.pkiCollectionId), friendlyName: z.string().trim().optional().describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.friendlyName), commonName: z.string().trim().min(1).describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.commonName), altNames: validateAltNamesField.describe(CERTIFICATE_AUTHORITIES.ISSUE_CERT.altNames), @@ -635,6 +636,7 @@ export const registerCaRouter = async (server: FastifyZodProvider) => { body: z .object({ csr: z.string().trim().min(1).describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.csr), + pkiCollectionId: z.string().trim().optional().describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.pkiCollectionId), friendlyName: z.string().trim().optional().describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.friendlyName), commonName: z.string().trim().min(1).optional().describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.commonName), altNames: validateAltNamesField.describe(CERTIFICATE_AUTHORITIES.SIGN_CERT.altNames), diff --git a/backend/src/services/certificate-authority/certificate-authority-service.ts b/backend/src/services/certificate-authority/certificate-authority-service.ts index b63ec9ba9..88f686089 100644 --- a/backend/src/services/certificate-authority/certificate-authority-service.ts +++ b/backend/src/services/certificate-authority/certificate-authority-service.ts @@ -7,10 +7,12 @@ import { z } from "zod"; 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 { TCertificateBodyDALFactory } from "@app/services/certificate/certificate-body-dal"; import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal"; import { TKmsServiceFactory } from "@app/services/kms/kms-service"; +import { TPkiCollectionDALFactory } from "@app/services/pki-collection/pki-collection-dal"; +import { TPkiCollectionItemDALFactory } from "@app/services/pki-collection/pki-collection-item-dal"; import { TProjectDALFactory } from "@app/services/project/project-dal"; import { getProjectKmsCertificateKeyId } from "@app/services/project/project-fns"; @@ -60,6 +62,8 @@ type TCertificateAuthorityServiceFactoryDep = { certificateAuthorityQueue: TCertificateAuthorityQueueFactory; // TODO: Pick certificateDAL: Pick; certificateBodyDAL: Pick; + pkiCollectionDAL: Pick; + pkiCollectionItemDAL: Pick; projectDAL: Pick; kmsService: Pick; permissionService: Pick; @@ -74,6 +78,8 @@ export const certificateAuthorityServiceFactory = ({ certificateAuthorityCrlDAL, certificateDAL, certificateBodyDAL, + pkiCollectionDAL, + pkiCollectionItemDAL, projectDAL, kmsService, permissionService @@ -1007,6 +1013,7 @@ export const certificateAuthorityServiceFactory = ({ */ const issueCertFromCa = async ({ caId, + pkiCollectionId, friendlyName, commonName, altNames, @@ -1039,6 +1046,13 @@ export const certificateAuthorityServiceFactory = ({ throw new BadRequestError({ message: "CA is expired" }); } + // check PKI collection + if (pkiCollectionId) { + const pkiCollection = await pkiCollectionDAL.findById(pkiCollectionId); + if (!pkiCollection) throw new NotFoundError({ message: "PKI collection not found" }); + if (pkiCollection.projectId !== ca.projectId) throw new BadRequestError({ message: "Invalid PKI collection" }); + } + const certificateManagerKmsId = await getProjectKmsCertificateKeyId({ projectId: ca.projectId, projectDAL, @@ -1186,6 +1200,16 @@ export const certificateAuthorityServiceFactory = ({ tx ); + if (pkiCollectionId) { + await pkiCollectionItemDAL.create( + { + pkiCollectionId, + certId: cert.id + }, + tx + ); + } + return cert; }); @@ -1214,6 +1238,7 @@ export const certificateAuthorityServiceFactory = ({ const signCertFromCa = async ({ caId, csr, + pkiCollectionId, friendlyName, commonName, altNames, @@ -1247,6 +1272,13 @@ export const certificateAuthorityServiceFactory = ({ throw new BadRequestError({ message: "CA is expired" }); } + // check PKI collection + if (pkiCollectionId) { + const pkiCollection = await pkiCollectionDAL.findById(pkiCollectionId); + if (!pkiCollection) throw new NotFoundError({ message: "PKI collection not found" }); + if (pkiCollection.projectId !== ca.projectId) throw new BadRequestError({ message: "Invalid PKI collection" }); + } + const certificateManagerKmsId = await getProjectKmsCertificateKeyId({ projectId: ca.projectId, projectDAL, @@ -1390,6 +1422,16 @@ export const certificateAuthorityServiceFactory = ({ tx ); + if (pkiCollectionId) { + await pkiCollectionItemDAL.create( + { + pkiCollectionId, + certId: cert.id + }, + tx + ); + } + return cert; }); diff --git a/backend/src/services/certificate-authority/certificate-authority-types.ts b/backend/src/services/certificate-authority/certificate-authority-types.ts index 31a6e1629..3fef204bf 100644 --- a/backend/src/services/certificate-authority/certificate-authority-types.ts +++ b/backend/src/services/certificate-authority/certificate-authority-types.ts @@ -87,6 +87,7 @@ export type TImportCertToCaDTO = { export type TIssueCertFromCaDTO = { caId: string; + pkiCollectionId?: string; friendlyName?: string; commonName: string; altNames: string; @@ -98,6 +99,7 @@ export type TIssueCertFromCaDTO = { export type TSignCertFromCaDTO = { caId: string; csr: string; + pkiCollectionId?: string; friendlyName?: string; commonName?: string; altNames: string; diff --git a/frontend/src/hooks/api/ca/types.ts b/frontend/src/hooks/api/ca/types.ts index 7513070c8..692714871 100644 --- a/frontend/src/hooks/api/ca/types.ts +++ b/frontend/src/hooks/api/ca/types.ts @@ -80,6 +80,7 @@ export type TImportCaCertificateResponse = { export type TCreateCertificateDTO = { projectSlug: string; caId: string; + pkiCollectionId?: string; friendlyName?: string; commonName: string; altNames: string; // sans diff --git a/frontend/src/views/Project/CertificatesPage/components/CertificatesTab/components/CertificateModal.tsx b/frontend/src/views/Project/CertificatesPage/components/CertificatesTab/components/CertificateModal.tsx index 12bd8c47c..227abe02a 100644 --- a/frontend/src/views/Project/CertificatesPage/components/CertificatesTab/components/CertificateModal.tsx +++ b/frontend/src/views/Project/CertificatesPage/components/CertificatesTab/components/CertificateModal.tsx @@ -14,7 +14,13 @@ import { SelectItem } from "@app/components/v2"; import { useWorkspace } from "@app/context"; -import { CaStatus, useCreateCertificate, useGetCert, useListWorkspaceCas } from "@app/hooks/api"; +import { + CaStatus, + useCreateCertificate, + useGetCert, + useListWorkspaceCas, + useListWorkspacePkiCollections +} from "@app/hooks/api"; import { caTypeToNameMap } from "@app/hooks/api/ca/constants"; import { UsePopUpState } from "@app/hooks/usePopUp"; @@ -22,6 +28,7 @@ import { CertificateContent } from "./CertificateContent"; const schema = z.object({ caId: z.string(), + collectionId: z.string().optional(), friendlyName: z.string(), commonName: z.string().trim().min(1), altNames: z.string(), @@ -54,6 +61,10 @@ export const CertificateModal = ({ popUp, handlePopUpToggle }: Props) => { status: CaStatus.ACTIVE }); + const { data } = useListWorkspacePkiCollections({ + workspaceId: currentWorkspace?.id || "" + }); + const { mutateAsync: createCertificate } = useCreateCertificate(); const { @@ -86,13 +97,21 @@ export const CertificateModal = ({ popUp, handlePopUpToggle }: Props) => { } }, [cert]); - const onFormSubmit = async ({ caId, friendlyName, commonName, altNames, ttl }: FormData) => { + const onFormSubmit = async ({ + caId, + collectionId, + friendlyName, + commonName, + altNames, + ttl + }: FormData) => { try { if (!currentWorkspace?.slug) return; const { serialNumber, certificate, certificateChain, privateKey } = await createCertificate({ projectSlug: currentWorkspace.slug, caId, + pkiCollectionId: collectionId, friendlyName, commonName, altNames, @@ -167,6 +186,32 @@ export const CertificateModal = ({ popUp, handlePopUpToggle }: Props) => { )} /> + ( + + + + )} + /> Date: Thu, 15 Aug 2024 11:03:58 -0700 Subject: [PATCH 5/5] Add description to PKI collection --- .../20240806173521_cert-alerting.ts | 1 + backend/src/db/schemas/pki-collections.ts | 3 ++- backend/src/lib/api-docs/constants.ts | 6 +++-- .../server/routes/v1/pki-collection-router.ts | 6 +++-- .../pki-collection/pki-collection-service.ts | 8 ++++-- .../pki-collection/pki-collection-types.ts | 2 ++ .../src/hooks/api/pkiCollections/types.ts | 3 +++ .../CertificatesTab/CertificatesTab.tsx | 2 ++ .../components/PkiCollectionModal.tsx | 26 ++++++++++++++++--- .../components/PkiCollectionSection.tsx | 2 +- .../components/PkiCollectionTable.tsx | 4 ++- .../PkiCollectionDetailsSection.tsx | 6 ++++- 12 files changed, 55 insertions(+), 14 deletions(-) diff --git a/backend/src/db/migrations/20240806173521_cert-alerting.ts b/backend/src/db/migrations/20240806173521_cert-alerting.ts index a946eb5ce..5ea98cda9 100644 --- a/backend/src/db/migrations/20240806173521_cert-alerting.ts +++ b/backend/src/db/migrations/20240806173521_cert-alerting.ts @@ -11,6 +11,7 @@ export async function up(knex: Knex): Promise { t.string("projectId").notNullable(); t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE"); t.string("name").notNullable(); + t.string("description").notNullable(); }); } diff --git a/backend/src/db/schemas/pki-collections.ts b/backend/src/db/schemas/pki-collections.ts index 4470c513c..0f9a5675f 100644 --- a/backend/src/db/schemas/pki-collections.ts +++ b/backend/src/db/schemas/pki-collections.ts @@ -12,7 +12,8 @@ export const PkiCollectionsSchema = z.object({ createdAt: z.date(), updatedAt: z.date(), projectId: z.string(), - name: z.string() + name: z.string(), + description: z.string() }); export type TPkiCollections = z.infer; diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index d79faa647..90eacb9a7 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -1172,14 +1172,16 @@ export const ALERTS = { export const PKI_COLLECTIONS = { CREATE: { projectId: "The ID of the project to create the PKI collection in", - name: "The name of the PKI collection" + name: "The name of the PKI collection", + description: "A description for the PKI collection" }, GET: { collectionId: "The ID of the PKI collection to get" }, UPDATE: { collectionId: "The ID of the PKI collection to update", - name: "The name of the PKI collection to update to" + name: "The name of the PKI collection to update to", + description: "The description for the PKI collection to update to" }, DELETE: { collectionId: "The ID of the PKI collection to delete" diff --git a/backend/src/server/routes/v1/pki-collection-router.ts b/backend/src/server/routes/v1/pki-collection-router.ts index 6a48b5e76..2f2add5c1 100644 --- a/backend/src/server/routes/v1/pki-collection-router.ts +++ b/backend/src/server/routes/v1/pki-collection-router.ts @@ -20,7 +20,8 @@ export const registerPkiCollectionRouter = async (server: FastifyZodProvider) => description: "Create PKI collection", body: z.object({ projectId: z.string().trim().describe(PKI_COLLECTIONS.CREATE.projectId), - name: z.string().trim().describe(PKI_COLLECTIONS.CREATE.name) + name: z.string().trim().describe(PKI_COLLECTIONS.CREATE.name), + description: z.string().trim().default("").describe(PKI_COLLECTIONS.CREATE.description) }), response: { 200: PkiCollectionsSchema @@ -104,7 +105,8 @@ export const registerPkiCollectionRouter = async (server: FastifyZodProvider) => collectionId: z.string().trim().describe(PKI_COLLECTIONS.UPDATE.collectionId) }), body: z.object({ - name: z.string().trim().optional().describe(PKI_COLLECTIONS.UPDATE.name) + name: z.string().trim().optional().describe(PKI_COLLECTIONS.UPDATE.name), + description: z.string().trim().optional().describe(PKI_COLLECTIONS.UPDATE.description) }), response: { 200: PkiCollectionsSchema diff --git a/backend/src/services/pki-collection/pki-collection-service.ts b/backend/src/services/pki-collection/pki-collection-service.ts index 87b1be2f9..f7c7ba399 100644 --- a/backend/src/services/pki-collection/pki-collection-service.ts +++ b/backend/src/services/pki-collection/pki-collection-service.ts @@ -40,6 +40,7 @@ export const pkiCollectionServiceFactory = ({ }: TPkiCollectionServiceFactoryDep) => { const createPkiCollection = async ({ name, + description, projectId, actorId, actorAuthMethod, @@ -61,7 +62,8 @@ export const pkiCollectionServiceFactory = ({ const pkiCollection = await pkiCollectionDAL.create({ projectId, - name + name, + description }); return pkiCollection; @@ -92,6 +94,7 @@ export const pkiCollectionServiceFactory = ({ const updatePkiCollection = async ({ collectionId, name, + description, actorId, actorAuthMethod, actor, @@ -110,7 +113,8 @@ export const pkiCollectionServiceFactory = ({ ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Edit, ProjectPermissionSub.PkiCollections); pkiCollection = await pkiCollectionDAL.updateById(collectionId, { - name + name, + description }); return pkiCollection; diff --git a/backend/src/services/pki-collection/pki-collection-types.ts b/backend/src/services/pki-collection/pki-collection-types.ts index 52d489eb2..e82ca31d0 100644 --- a/backend/src/services/pki-collection/pki-collection-types.ts +++ b/backend/src/services/pki-collection/pki-collection-types.ts @@ -2,6 +2,7 @@ import { TProjectPermission } from "@app/lib/types"; export type TCreatePkiCollectionDTO = { name: string; + description: string; } & TProjectPermission; export type TGetPkiCollectionByIdDTO = { @@ -11,6 +12,7 @@ export type TGetPkiCollectionByIdDTO = { export type TUpdatePkiCollectionDTO = { collectionId: string; name?: string; + description?: string; } & Omit; export type TDeletePkiCollectionDTO = { diff --git a/frontend/src/hooks/api/pkiCollections/types.ts b/frontend/src/hooks/api/pkiCollections/types.ts index 86c2471e9..98a98d520 100644 --- a/frontend/src/hooks/api/pkiCollections/types.ts +++ b/frontend/src/hooks/api/pkiCollections/types.ts @@ -1,6 +1,7 @@ export type TPkiCollection = { id: string; name: string; + description: string; projectId: string; createdAt: string; updatedAt: string; @@ -9,12 +10,14 @@ export type TPkiCollection = { export type TCreatePkiCollectionDTO = { projectId: string; name: string; + description: string; }; export type TUpdatePkiCollectionTO = { collectionId: string; projectId: string; name?: string; + description?: string; }; export type TDeletePkiCollectionDTO = { diff --git a/frontend/src/views/Project/CertificatesPage/components/CertificatesTab/CertificatesTab.tsx b/frontend/src/views/Project/CertificatesPage/components/CertificatesTab/CertificatesTab.tsx index f054e2546..8dcd74348 100644 --- a/frontend/src/views/Project/CertificatesPage/components/CertificatesTab/CertificatesTab.tsx +++ b/frontend/src/views/Project/CertificatesPage/components/CertificatesTab/CertificatesTab.tsx @@ -1,5 +1,6 @@ import { motion } from "framer-motion"; +import { PkiCollectionSection } from "../PkiAlertsTab/components"; import { CertificatesSection } from "./components"; export const CertificatesTab = () => { @@ -11,6 +12,7 @@ export const CertificatesTab = () => { animate={{ opacity: 1, translateX: 0 }} exit={{ opacity: 0, translateX: 30 }} > + ); diff --git a/frontend/src/views/Project/CertificatesPage/components/PkiAlertsTab/components/PkiCollectionModal.tsx b/frontend/src/views/Project/CertificatesPage/components/PkiAlertsTab/components/PkiCollectionModal.tsx index 89fd51c92..926d1cec9 100644 --- a/frontend/src/views/Project/CertificatesPage/components/PkiAlertsTab/components/PkiCollectionModal.tsx +++ b/frontend/src/views/Project/CertificatesPage/components/PkiAlertsTab/components/PkiCollectionModal.tsx @@ -15,7 +15,8 @@ import { import { UsePopUpState } from "@app/hooks/usePopUp"; const schema = z.object({ - name: z.string().trim().min(1) + name: z.string().trim().min(1), + description: z.string() }); export type FormData = z.infer; @@ -49,16 +50,18 @@ export const PkiCollectionModal = ({ popUp, handlePopUpToggle }: Props) => { useEffect(() => { if (pkiCollection) { reset({ - name: pkiCollection.name + name: pkiCollection.name, + description: pkiCollection.description }); } else { reset({ - name: "" + name: "", + description: "" }); } }, [pkiCollection]); - const onFormSubmit = async ({ name }: FormData) => { + const onFormSubmit = async ({ name, description }: FormData) => { try { if (!projectId) return; @@ -67,12 +70,14 @@ export const PkiCollectionModal = ({ popUp, handlePopUpToggle }: Props) => { await updatePkiCollection({ collectionId: pkiCollection.id, name, + description, projectId }); } else { // create const { id: createdId } = await createPkiCollection({ name, + description, projectId }); @@ -121,6 +126,19 @@ export const PkiCollectionModal = ({ popUp, handlePopUpToggle }: Props) => { )} /> + ( + + + + )} + />
-
+

Name

{pkiCollection.name}

+
+

Description

+

{pkiCollection.description}

+
) : (