Resolve PR issues

This commit is contained in:
Tuan Dang
2024-06-12 13:11:21 -07:00
parent 868d0345d6
commit f5322abe85
26 changed files with 244 additions and 243 deletions
+7 -7
View File
@@ -44,9 +44,9 @@ import {
TCertificateAuthoritySecret, TCertificateAuthoritySecret,
TCertificateAuthoritySecretInsert, TCertificateAuthoritySecretInsert,
TCertificateAuthoritySecretUpdate, TCertificateAuthoritySecretUpdate,
TCertificateCerts, TCertificateBodies,
TCertificateCertsInsert, TCertificateBodiesInsert,
TCertificateCertsUpdate, TCertificateBodiesUpdate,
TCertificates, TCertificates,
TCertificateSecrets, TCertificateSecrets,
TCertificateSecretsInsert, TCertificateSecretsInsert,
@@ -299,10 +299,10 @@ declare module "knex/types/tables" {
TCertificateAuthorityCrlUpdate TCertificateAuthorityCrlUpdate
>; >;
[TableName.Certificate]: Knex.CompositeTableType<TCertificates, TCertificatesInsert, TCertificatesUpdate>; [TableName.Certificate]: Knex.CompositeTableType<TCertificates, TCertificatesInsert, TCertificatesUpdate>;
[TableName.CertificateCert]: Knex.CompositeTableType< [TableName.CertificateBody]: Knex.CompositeTableType<
TCertificateCerts, TCertificateBodies,
TCertificateCertsInsert, TCertificateBodiesInsert,
TCertificateCertsUpdate TCertificateBodiesUpdate
>; >;
[TableName.CertificateSecret]: Knex.CompositeTableType< [TableName.CertificateSecret]: Knex.CompositeTableType<
TCertificateSecrets, TCertificateSecrets,
@@ -24,6 +24,7 @@ export async function up(knex: Knex): Promise<void> {
t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE"); t.foreign("projectId").references("id").inTable(TableName.Project).onDelete("CASCADE");
t.string("type").notNullable(); // root / intermediate t.string("type").notNullable(); // root / intermediate
t.string("status").notNullable(); // active / pending-certificate t.string("status").notNullable(); // active / pending-certificate
t.string("friendlyName").notNullable();
t.string("organization").notNullable(); t.string("organization").notNullable();
t.string("ou").notNullable(); t.string("ou").notNullable();
t.string("country").notNullable(); t.string("country").notNullable();
@@ -31,7 +32,6 @@ export async function up(knex: Knex): Promise<void> {
t.string("locality").notNullable(); t.string("locality").notNullable();
t.string("commonName").notNullable(); t.string("commonName").notNullable();
t.string("dn").notNullable(); t.string("dn").notNullable();
t.unique(["dn", "projectId"]);
t.string("serialNumber").nullable().unique(); t.string("serialNumber").nullable().unique();
t.integer("maxPathLength").nullable(); t.integer("maxPathLength").nullable();
t.string("keyAlgorithm").notNullable(); t.string("keyAlgorithm").notNullable();
@@ -80,6 +80,7 @@ export async function up(knex: Knex): Promise<void> {
t.foreign("caId").references("id").inTable(TableName.CertificateAuthority).onDelete("CASCADE"); t.foreign("caId").references("id").inTable(TableName.CertificateAuthority).onDelete("CASCADE");
t.string("status").notNullable(); // active / pending-certificate t.string("status").notNullable(); // active / pending-certificate
t.string("serialNumber").notNullable().unique(); t.string("serialNumber").notNullable().unique();
t.string("friendlyName").notNullable();
t.string("commonName").notNullable(); t.string("commonName").notNullable();
t.datetime("notBefore").notNullable(); t.datetime("notBefore").notNullable();
t.datetime("notAfter").notNullable(); t.datetime("notAfter").notNullable();
@@ -88,8 +89,8 @@ export async function up(knex: Knex): Promise<void> {
}); });
} }
if (!(await knex.schema.hasTable(TableName.CertificateCert))) { if (!(await knex.schema.hasTable(TableName.CertificateBody))) {
await knex.schema.createTable(TableName.CertificateCert, (t) => { await knex.schema.createTable(TableName.CertificateBody, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid()); t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.timestamps(true, true, true); t.timestamps(true, true, true);
t.uuid("certId").notNullable().unique(); t.uuid("certId").notNullable().unique();
@@ -102,7 +103,7 @@ export async function up(knex: Knex): Promise<void> {
await createOnUpdateTrigger(knex, TableName.CertificateAuthorityCert); await createOnUpdateTrigger(knex, TableName.CertificateAuthorityCert);
await createOnUpdateTrigger(knex, TableName.CertificateAuthoritySecret); await createOnUpdateTrigger(knex, TableName.CertificateAuthoritySecret);
await createOnUpdateTrigger(knex, TableName.Certificate); await createOnUpdateTrigger(knex, TableName.Certificate);
await createOnUpdateTrigger(knex, TableName.CertificateCert); await createOnUpdateTrigger(knex, TableName.CertificateBody);
} }
export async function down(knex: Knex): Promise<void> { export async function down(knex: Knex): Promise<void> {
@@ -115,8 +116,8 @@ export async function down(knex: Knex): Promise<void> {
} }
// certificates // certificates
await knex.schema.dropTableIfExists(TableName.CertificateCert); await knex.schema.dropTableIfExists(TableName.CertificateBody);
await dropOnUpdateTrigger(knex, TableName.CertificateCert); await dropOnUpdateTrigger(knex, TableName.CertificateBody);
await knex.schema.dropTableIfExists(TableName.Certificate); await knex.schema.dropTableIfExists(TableName.Certificate);
await dropOnUpdateTrigger(knex, TableName.Certificate); await dropOnUpdateTrigger(knex, TableName.Certificate);
@@ -15,6 +15,7 @@ export const CertificateAuthoritiesSchema = z.object({
projectId: z.string(), projectId: z.string(),
type: z.string(), type: z.string(),
status: z.string(), status: z.string(),
friendlyName: z.string(),
organization: z.string(), organization: z.string(),
ou: z.string(), ou: z.string(),
country: z.string(), country: z.string(),
@@ -9,7 +9,7 @@ import { zodBuffer } from "@app/lib/zod";
import { TImmutableDBKeys } from "./models"; import { TImmutableDBKeys } from "./models";
export const CertificateCertsSchema = z.object({ export const CertificateBodiesSchema = z.object({
id: z.string().uuid(), id: z.string().uuid(),
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date(), updatedAt: z.date(),
@@ -17,6 +17,6 @@ export const CertificateCertsSchema = z.object({
encryptedCertificate: zodBuffer encryptedCertificate: zodBuffer
}); });
export type TCertificateCerts = z.infer<typeof CertificateCertsSchema>; export type TCertificateBodies = z.infer<typeof CertificateBodiesSchema>;
export type TCertificateCertsInsert = Omit<z.input<typeof CertificateCertsSchema>, TImmutableDBKeys>; export type TCertificateBodiesInsert = Omit<z.input<typeof CertificateBodiesSchema>, TImmutableDBKeys>;
export type TCertificateCertsUpdate = Partial<Omit<z.input<typeof CertificateCertsSchema>, TImmutableDBKeys>>; export type TCertificateBodiesUpdate = Partial<Omit<z.input<typeof CertificateBodiesSchema>, TImmutableDBKeys>>;
+1
View File
@@ -14,6 +14,7 @@ export const CertificatesSchema = z.object({
caId: z.string().uuid(), caId: z.string().uuid(),
status: z.string(), status: z.string(),
serialNumber: z.string(), serialNumber: z.string(),
friendlyName: z.string(),
commonName: z.string(), commonName: z.string(),
notBefore: z.date(), notBefore: z.date(),
notAfter: z.date(), notAfter: z.date(),
+1 -1
View File
@@ -12,7 +12,7 @@ export * from "./certificate-authorities";
export * from "./certificate-authority-certs"; export * from "./certificate-authority-certs";
export * from "./certificate-authority-crl"; export * from "./certificate-authority-crl";
export * from "./certificate-authority-secret"; export * from "./certificate-authority-secret";
export * from "./certificate-certs"; export * from "./certificate-bodies";
export * from "./certificate-secrets"; export * from "./certificate-secrets";
export * from "./certificates"; export * from "./certificates";
export * from "./dynamic-secret-leases"; export * from "./dynamic-secret-leases";
+1 -1
View File
@@ -7,7 +7,7 @@ export enum TableName {
CertificateAuthoritySecret = "certificate_authority_secret", CertificateAuthoritySecret = "certificate_authority_secret",
CertificateAuthorityCrl = "certificate_authority_crl", CertificateAuthorityCrl = "certificate_authority_crl",
Certificate = "certificates", Certificate = "certificates",
CertificateCert = "certificate_certs", CertificateBody = "certificate_bodies",
CertificateSecret = "certificate_secrets", CertificateSecret = "certificate_secrets",
Groups = "groups", Groups = "groups",
GroupProjectMembership = "group_project_memberships", GroupProjectMembership = "group_project_memberships",
+4 -4
View File
@@ -71,7 +71,7 @@ import { authPaswordServiceFactory } from "@app/services/auth/auth-password-serv
import { authSignupServiceFactory } from "@app/services/auth/auth-signup-service"; import { authSignupServiceFactory } from "@app/services/auth/auth-signup-service";
import { tokenDALFactory } from "@app/services/auth-token/auth-token-dal"; import { tokenDALFactory } from "@app/services/auth-token/auth-token-dal";
import { tokenServiceFactory } from "@app/services/auth-token/auth-token-service"; import { tokenServiceFactory } from "@app/services/auth-token/auth-token-service";
import { certificateCertDALFactory } from "@app/services/certificate/certificate-cert-dal"; import { certificateBodyDALFactory } from "@app/services/certificate/certificate-body-dal";
import { certificateDALFactory } from "@app/services/certificate/certificate-dal"; import { certificateDALFactory } from "@app/services/certificate/certificate-dal";
import { certificateServiceFactory } from "@app/services/certificate/certificate-service"; import { certificateServiceFactory } from "@app/services/certificate/certificate-service";
import { certificateAuthorityCertDALFactory } from "@app/services/certificate-authority/certificate-authority-cert-dal"; import { certificateAuthorityCertDALFactory } from "@app/services/certificate-authority/certificate-authority-cert-dal";
@@ -521,11 +521,11 @@ export const registerRoutes = async (
const certificateAuthorityCrlDAL = certificateAuthorityCrlDALFactory(db); const certificateAuthorityCrlDAL = certificateAuthorityCrlDALFactory(db);
const certificateDAL = certificateDALFactory(db); const certificateDAL = certificateDALFactory(db);
const certificateCertDAL = certificateCertDALFactory(db); const certificateBodyDAL = certificateBodyDALFactory(db);
const certificateService = certificateServiceFactory({ const certificateService = certificateServiceFactory({
certificateDAL, certificateDAL,
certificateCertDAL, certificateBodyDAL,
certificateAuthorityDAL, certificateAuthorityDAL,
certificateAuthorityCertDAL, certificateAuthorityCertDAL,
certificateAuthorityCrlDAL, certificateAuthorityCrlDAL,
@@ -552,7 +552,7 @@ export const registerRoutes = async (
certificateAuthorityCrlDAL, certificateAuthorityCrlDAL,
certificateAuthorityQueue, certificateAuthorityQueue,
certificateDAL, certificateDAL,
certificateCertDAL, certificateBodyDAL,
projectDAL, projectDAL,
kmsService, kmsService,
permissionService permissionService
@@ -1,3 +1,4 @@
import ms from "ms";
import { z } from "zod"; import { z } from "zod";
import { CertificateAuthoritiesSchema } from "@app/db/schemas"; import { CertificateAuthoritiesSchema } from "@app/db/schemas";
@@ -21,7 +22,8 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
body: z body: z
.object({ .object({
projectSlug: z.string().trim(), projectSlug: z.string().trim(),
type: z.enum([CaType.ROOT, CaType.INTERMEDIATE]), type: z.nativeEnum(CaType),
friendlyName: z.string().optional(),
commonName: z.string().trim(), commonName: z.string().trim(),
organization: z.string().trim(), organization: z.string().trim(),
ou: z.string().trim(), ou: z.string().trim(),
@@ -32,14 +34,7 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
notBefore: validateCaDateField.optional(), notBefore: validateCaDateField.optional(),
notAfter: validateCaDateField.optional(), notAfter: validateCaDateField.optional(),
maxPathLength: z.number().min(-1).default(-1), maxPathLength: z.number().min(-1).default(-1),
keyAlgorithm: z keyAlgorithm: z.nativeEnum(CertKeyAlgorithm).default(CertKeyAlgorithm.RSA_2048)
.enum([
CertKeyAlgorithm.RSA_2048,
CertKeyAlgorithm.RSA_4096,
CertKeyAlgorithm.ECDSA_P256,
CertKeyAlgorithm.ECDSA_P384
])
.default(CertKeyAlgorithm.RSA_2048)
}) })
.refine( .refine(
(data) => { (data) => {
@@ -342,8 +337,9 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
}), }),
body: z body: z
.object({ .object({
friendlyName: z.string().optional(),
commonName: z.string().trim().min(1), commonName: z.string().trim().min(1),
ttl: z.number().int().min(0).optional(), ttl: z.string().refine((val) => ms(val) > 0, "TTL must be a positive number"),
notBefore: validateCaDateField.optional(), notBefore: validateCaDateField.optional(),
notAfter: validateCaDateField.optional() notAfter: validateCaDateField.optional()
}) })
@@ -52,17 +52,7 @@ export const registerCertRouter = async (server: FastifyZodProvider) => {
serialNumber: z.string().trim() serialNumber: z.string().trim()
}), }),
body: z.object({ body: z.object({
revocationReason: z.enum([ revocationReason: z.nativeEnum(CrlReason)
CrlReason.UNSPECIFIED,
CrlReason.KEY_COMPROMISE,
CrlReason.CA_COMPROMISE,
CrlReason.AFFILIATION_CHANGED,
CrlReason.SUPERSEDED,
CrlReason.CESSATION_OF_OPERATION,
CrlReason.CERTIFICATE_HOLD,
CrlReason.PRIVILEGE_WITHDRAWN,
CrlReason.A_A_COMPROMISE
])
}), }),
response: { response: {
200: z.object({ 200: z.object({
@@ -2,11 +2,12 @@
import { ForbiddenError } from "@casl/ability"; import { ForbiddenError } from "@casl/ability";
import * as x509 from "@peculiar/x509"; import * as x509 from "@peculiar/x509";
import crypto, { KeyObject } from "crypto"; import crypto, { KeyObject } from "crypto";
import ms from "ms";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError } from "@app/lib/errors";
import { TCertificateCertDALFactory } from "@app/services/certificate/certificate-cert-dal"; import { TCertificateBodyDALFactory } from "@app/services/certificate/certificate-body-dal";
import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal"; import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal";
import { TKmsServiceFactory } from "@app/services/kms/kms-service"; import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { TProjectDALFactory } from "@app/services/project/project-dal"; import { TProjectDALFactory } from "@app/services/project/project-dal";
@@ -50,7 +51,7 @@ type TCertificateAuthorityServiceFactoryDep = {
certificateAuthorityCrlDAL: Pick<TCertificateAuthorityCrlDALFactory, "create" | "findOne" | "update">; certificateAuthorityCrlDAL: Pick<TCertificateAuthorityCrlDALFactory, "create" | "findOne" | "update">;
certificateAuthorityQueue: TCertificateAuthorityQueueFactory; // TODO: Pick certificateAuthorityQueue: TCertificateAuthorityQueueFactory; // TODO: Pick
certificateDAL: Pick<TCertificateDALFactory, "transaction" | "create" | "find">; certificateDAL: Pick<TCertificateDALFactory, "transaction" | "create" | "find">;
certificateCertDAL: Pick<TCertificateCertDALFactory, "create">; certificateBodyDAL: Pick<TCertificateBodyDALFactory, "create">;
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug" | "findOne" | "updateById" | "findById" | "transaction">; projectDAL: Pick<TProjectDALFactory, "findProjectBySlug" | "findOne" | "updateById" | "findById" | "transaction">;
kmsService: Pick<TKmsServiceFactory, "generateKmsKey" | "encrypt" | "decrypt">; kmsService: Pick<TKmsServiceFactory, "generateKmsKey" | "encrypt" | "decrypt">;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">; permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
@@ -64,7 +65,7 @@ export const certificateAuthorityServiceFactory = ({
certificateAuthoritySecretDAL, certificateAuthoritySecretDAL,
certificateAuthorityCrlDAL, certificateAuthorityCrlDAL,
certificateDAL, certificateDAL,
certificateCertDAL, certificateBodyDAL,
projectDAL, projectDAL,
kmsService, kmsService,
permissionService permissionService
@@ -75,6 +76,7 @@ export const certificateAuthorityServiceFactory = ({
const createCa = async ({ const createCa = async ({
projectSlug, projectSlug,
type, type,
friendlyName,
commonName, commonName,
organization, organization,
ou, ou,
@@ -127,6 +129,7 @@ export const certificateAuthorityServiceFactory = ({
: new Date(new Date().setFullYear(new Date().getFullYear() + 10)); : new Date(new Date().setFullYear(new Date().getFullYear() + 10));
const serialNumber = crypto.randomBytes(32).toString("hex"); const serialNumber = crypto.randomBytes(32).toString("hex");
const ca = await certificateAuthorityDAL.create( const ca = await certificateAuthorityDAL.create(
{ {
projectId: project.id, projectId: project.id,
@@ -136,6 +139,7 @@ export const certificateAuthorityServiceFactory = ({
country, country,
province, province,
locality, locality,
friendlyName: friendlyName || dn,
commonName, commonName,
status: type === CaType.ROOT ? CaStatus.ACTIVE : CaStatus.PENDING_CERTIFICATE, status: type === CaType.ROOT ? CaStatus.ACTIVE : CaStatus.PENDING_CERTIFICATE,
dn, dn,
@@ -642,6 +646,7 @@ export const certificateAuthorityServiceFactory = ({
*/ */
const issueCertFromCa = async ({ const issueCertFromCa = async ({
caId, caId,
friendlyName,
commonName, commonName,
ttl, ttl,
notBefore, notBefore,
@@ -688,8 +693,7 @@ export const certificateAuthorityServiceFactory = ({
if (notAfter) { if (notAfter) {
notAfterDate = new Date(notAfter); notAfterDate = new Date(notAfter);
} else if (ttl) { } else if (ttl) {
// ttl in seconds notAfterDate = new Date(new Date().getTime() + ms(ttl));
notAfterDate = new Date(new Date().getTime() + ttl * 1000);
} }
const caCertNotBeforeDate = new Date(caCertObj.notBefore); const caCertNotBeforeDate = new Date(caCertObj.notBefore);
@@ -760,6 +764,7 @@ export const certificateAuthorityServiceFactory = ({
{ {
caId: ca.id, caId: ca.id,
status: CertStatus.ACTIVE, status: CertStatus.ACTIVE,
friendlyName: friendlyName || commonName,
commonName, commonName,
serialNumber, serialNumber,
notBefore: notBeforeDate, notBefore: notBeforeDate,
@@ -768,7 +773,7 @@ export const certificateAuthorityServiceFactory = ({
tx tx
); );
await certificateCertDAL.create( await certificateBodyDAL.create(
{ {
certId: cert.id, certId: cert.id,
encryptedCertificate encryptedCertificate
@@ -23,6 +23,7 @@ export enum CaStatus {
export type TCreateCaDTO = { export type TCreateCaDTO = {
projectSlug: string; projectSlug: string;
type: CaType; type: CaType;
friendlyName?: string;
commonName: string; commonName: string;
organization: string; organization: string;
ou: string; ou: string;
@@ -72,8 +73,9 @@ export type TImportCertToCaDTO = {
export type TIssueCertFromCaDTO = { export type TIssueCertFromCaDTO = {
caId: string; caId: string;
friendlyName?: string;
commonName: string; commonName: string;
ttl?: number; ttl: string;
notBefore?: string; notBefore?: string;
notAfter?: string; notAfter?: string;
} & Omit<TProjectPermission, "projectId">; } & Omit<TProjectPermission, "projectId">;
@@ -0,0 +1,10 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify } from "@app/lib/knex";
export type TCertificateBodyDALFactory = ReturnType<typeof certificateBodyDALFactory>;
export const certificateBodyDALFactory = (db: TDbClient) => {
const certificateBodyOrm = ormify(db, TableName.CertificateBody);
return certificateBodyOrm;
};
@@ -1,10 +0,0 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify } from "@app/lib/knex";
export type TCertificateCertDALFactory = ReturnType<typeof certificateCertDALFactory>;
export const certificateCertDALFactory = (db: TDbClient) => {
const certificateCertOrm = ormify(db, TableName.CertificateCert);
return certificateCertOrm;
};
@@ -3,7 +3,7 @@ import * as x509 from "@peculiar/x509";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service"; import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { TCertificateCertDALFactory } from "@app/services/certificate/certificate-cert-dal"; import { TCertificateBodyDALFactory } from "@app/services/certificate/certificate-body-dal";
import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal"; import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal";
import { TCertificateAuthorityCertDALFactory } from "@app/services/certificate-authority/certificate-authority-cert-dal"; import { TCertificateAuthorityCertDALFactory } from "@app/services/certificate-authority/certificate-authority-cert-dal";
import { TCertificateAuthorityCrlDALFactory } from "@app/services/certificate-authority/certificate-authority-crl-dal"; import { TCertificateAuthorityCrlDALFactory } from "@app/services/certificate-authority/certificate-authority-crl-dal";
@@ -19,7 +19,7 @@ import { CertStatus, TDeleteCertDTO, TGetCertCertDTO, TGetCertDTO, TRevokeCertDT
type TCertificateServiceFactoryDep = { type TCertificateServiceFactoryDep = {
certificateDAL: Pick<TCertificateDALFactory, "findOne" | "deleteById" | "update" | "find">; certificateDAL: Pick<TCertificateDALFactory, "findOne" | "deleteById" | "update" | "find">;
certificateCertDAL: Pick<TCertificateCertDALFactory, "findOne">; certificateBodyDAL: Pick<TCertificateBodyDALFactory, "findOne">;
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findById">; certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findById">;
certificateAuthorityCertDAL: Pick<TCertificateAuthorityCertDALFactory, "findOne">; certificateAuthorityCertDAL: Pick<TCertificateAuthorityCertDALFactory, "findOne">;
certificateAuthorityCrlDAL: Pick<TCertificateAuthorityCrlDALFactory, "update">; certificateAuthorityCrlDAL: Pick<TCertificateAuthorityCrlDALFactory, "update">;
@@ -33,7 +33,7 @@ export type TCertificateServiceFactory = ReturnType<typeof certificateServiceFac
export const certificateServiceFactory = ({ export const certificateServiceFactory = ({
certificateDAL, certificateDAL,
certificateCertDAL, certificateBodyDAL,
certificateAuthorityDAL, certificateAuthorityDAL,
certificateAuthorityCertDAL, certificateAuthorityCertDAL,
certificateAuthorityCrlDAL, certificateAuthorityCrlDAL,
@@ -155,7 +155,7 @@ export const certificateServiceFactory = ({
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Certificates); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Certificates);
const certCert = await certificateCertDAL.findOne({ certId: cert.id }); const certCert = await certificateBodyDAL.findOne({ certId: cert.id });
const keyId = await getProjectKmsCertificateKeyId({ const keyId = await getProjectKmsCertificateKeyId({
projectId: ca.projectId, projectId: ca.projectId,
+4 -2
View File
@@ -7,6 +7,7 @@ export type TCertificateAuthority = {
projectId: string; projectId: string;
type: CaType; type: CaType;
status: CaStatus; status: CaStatus;
friendlyName: string;
organization: string; organization: string;
ou: string; ou: string;
country: string; country: string;
@@ -25,6 +26,7 @@ export type TCertificateAuthority = {
export type TCreateCaDTO = { export type TCreateCaDTO = {
projectSlug: string; projectSlug: string;
type: string; type: string;
friendlyName?: string;
organization: string; organization: string;
ou: string; ou: string;
country: string; country: string;
@@ -74,12 +76,12 @@ export type TImportCaCertificateResponse = {
caId: string; caId: string;
}; };
// TODO: add TTL
export type TCreateCertificateDTO = { export type TCreateCertificateDTO = {
projectSlug: string; projectSlug: string;
caId: string; caId: string;
friendlyName?: string;
commonName: string; commonName: string;
ttl?: number; ttl: string; // string compatible with ms
notBefore?: string; notBefore?: string;
notAfter?: string; notAfter?: string;
}; };
@@ -4,6 +4,7 @@ export type TCertificate = {
id: string; id: string;
caId: string; caId: string;
status: CertStatus; status: CertStatus;
friendlyName: string;
commonName: string; commonName: string;
serialNumber: string; serialNumber: string;
notBefore: string; notBefore: string;
@@ -14,7 +14,7 @@ export const CertificatesPage = withProjectPermission(
return ( return (
<div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white"> <div className="container mx-auto flex flex-col justify-between bg-bunker-800 text-white">
<div className="mx-auto mb-6 w-full max-w-7xl py-6 px-6"> <div className="mx-auto mb-6 w-full max-w-7xl py-6 px-6">
<p className="mr-4 mb-4 text-3xl font-semibold text-white">Certificates</p> <p className="mr-4 mb-4 text-3xl font-semibold text-white">Internal PKI</p>
<Tabs defaultValue={TabSections.Certificates}> <Tabs defaultValue={TabSections.Certificates}>
<TabList> <TabList>
<Tab value={TabSections.Certificates}>Certificates</Tab> <Tab value={TabSections.Certificates}>Certificates</Tab>
@@ -35,6 +35,7 @@ const getDateTenYearsFromToday = () => {
const schema = z const schema = z
.object({ .object({
type: z.enum([CaType.ROOT, CaType.INTERMEDIATE]), type: z.enum([CaType.ROOT, CaType.INTERMEDIATE]),
friendlyName: z.string(),
organization: z.string(), organization: z.string(),
ou: z.string(), ou: z.string(),
country: z.string(), country: z.string(),
@@ -81,6 +82,7 @@ export const CaModal = ({ popUp, handlePopUpToggle }: Props) => {
resolver: zodResolver(schema), resolver: zodResolver(schema),
defaultValues: { defaultValues: {
type: CaType.ROOT, type: CaType.ROOT,
friendlyName: "",
organization: "", organization: "",
ou: "", ou: "",
country: "", country: "",
@@ -99,6 +101,7 @@ export const CaModal = ({ popUp, handlePopUpToggle }: Props) => {
if (ca) { if (ca) {
reset({ reset({
type: ca.type, type: ca.type,
friendlyName: ca.friendlyName,
organization: ca.organization, organization: ca.organization,
ou: ca.ou, ou: ca.ou,
country: ca.country, country: ca.country,
@@ -112,6 +115,7 @@ export const CaModal = ({ popUp, handlePopUpToggle }: Props) => {
} else { } else {
reset({ reset({
type: CaType.ROOT, type: CaType.ROOT,
friendlyName: "",
organization: "", organization: "",
ou: "", ou: "",
country: "", country: "",
@@ -127,6 +131,7 @@ export const CaModal = ({ popUp, handlePopUpToggle }: Props) => {
const onFormSubmit = async ({ const onFormSubmit = async ({
type, type,
friendlyName,
commonName, commonName,
organization, organization,
ou, ou,
@@ -143,6 +148,7 @@ export const CaModal = ({ popUp, handlePopUpToggle }: Props) => {
await createMutateAsync({ await createMutateAsync({
projectSlug: currentWorkspace.slug, projectSlug: currentWorkspace.slug,
type, type,
friendlyName,
commonName, commonName,
organization, organization,
ou, ou,
@@ -170,12 +176,6 @@ export const CaModal = ({ popUp, handlePopUpToggle }: Props) => {
} }
}; };
// const getDefaultNotAfterDate = () => {
// const date = new Date();
// date.setFullYear(date.getFullYear() + 10);
// return date;
// };
return ( return (
<Modal <Modal
isOpen={popUp?.ca?.isOpen} isOpen={popUp?.ca?.isOpen}
@@ -308,6 +308,20 @@ export const CaModal = ({ popUp, handlePopUpToggle }: Props) => {
</FormControl> </FormControl>
)} )}
/> />
<Controller
control={control}
defaultValue=""
name="friendlyName"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Friendly Name"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="My CA" isDisabled={Boolean(ca)} />
</FormControl>
)}
/>
<Controller <Controller
control={control} control={control}
defaultValue="" defaultValue=""
@@ -56,7 +56,7 @@ export const CaTable = ({ handlePopUpOpen }: Props) => {
<Table> <Table>
<THead> <THead>
<Tr> <Tr>
<Th>Subject</Th> <Th>Friendly Name</Th>
<Th>Status</Th> <Th>Status</Th>
<Th>Type</Th> <Th>Type</Th>
<Th>Valid Until</Th> <Th>Valid Until</Th>
@@ -71,7 +71,7 @@ export const CaTable = ({ handlePopUpOpen }: Props) => {
data.map((ca) => { data.map((ca) => {
return ( return (
<Tr className="h-10" key={`ca-${ca.id}`}> <Tr className="h-10" key={`ca-${ca.id}`}>
<Td>{ca.dn}</Td> <Td>{ca.friendlyName}</Td>
<Td>{caStatusToNameMap[ca.status]}</Td> <Td>{caStatusToNameMap[ca.status]}</Td>
<Td>{caTypeToNameMap[ca.type]}</Td> <Td>{caTypeToNameMap[ca.type]}</Td>
<Td>{ca.notAfter ? format(new Date(ca.notAfter), "yyyy-MM-dd") : "-"}</Td> <Td>{ca.notAfter ? format(new Date(ca.notAfter), "yyyy-MM-dd") : "-"}</Td>
@@ -1,9 +1,9 @@
import { useEffect } from "react";
import { faCheck, faCopy, faDownload } from "@fortawesome/free-solid-svg-icons"; import { faCheck, faCopy, faDownload } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import FileSaver from "file-saver";
import { IconButton } from "@app/components/v2"; import { IconButton, Tooltip } from "@app/components/v2";
import { useToggle } from "@app/hooks"; import { useTimedReset } from "@app/hooks";
type Props = { type Props = {
serialNumber: string; serialNumber: string;
@@ -18,42 +18,28 @@ export const CertificateContent = ({
certificateChain, certificateChain,
privateKey privateKey
}: Props) => { }: Props) => {
const [isSerialNumberCopied, setIsSerialNumberCopied] = useToggle(false); const [copyTextSerialNumber, isCopyingSerialNumber, setCopyTextSerialNumber] =
const [isCertificateCopied, setIsCertificateCopied] = useToggle(false); useTimedReset<string>({
const [isCertificateChainCopied, setIsCertificateChainCopied] = useToggle(false); initialState: "Copy to clipboard"
const [isCertificateSkCopied, setIsCertificateSkCopied] = useToggle(false); });
const [copyTextCertificate, isCopyingCertificate, setCopyTextCertificate] = useTimedReset<string>(
useEffect(() => { {
let timer: NodeJS.Timeout; initialState: "Copy to clipboard"
if (isSerialNumberCopied) {
timer = setTimeout(() => setIsSerialNumberCopied.off(), 2000);
} }
);
const [copyTextCertificateChain, isCopyingCertificateChain, setCopyTextCertificateChain] =
useTimedReset<string>({
initialState: "Copy to clipboard"
});
if (isCertificateCopied) { const [copyTextCertificateSk, isCopyingCertificateSk, setCopyTextCertificateSk] =
timer = setTimeout(() => setIsCertificateCopied.off(), 2000); useTimedReset<string>({
} initialState: "Copy to clipboard"
});
if (isCertificateChainCopied) {
timer = setTimeout(() => setIsCertificateChainCopied.off(), 2000);
}
if (isCertificateSkCopied) {
timer = setTimeout(() => setIsCertificateSkCopied.off(), 2000);
}
return () => clearTimeout(timer);
}, [isSerialNumberCopied, isCertificateCopied, isCertificateChainCopied, isCertificateSkCopied]);
const downloadTxtFile = (filename: string, content: string) => { const downloadTxtFile = (filename: string, content: string) => {
const blob = new Blob([content], { type: "text/plain" }); const blob = new Blob([content], { type: "text/plain;charset=utf-8" });
const url = URL.createObjectURL(blob); FileSaver.saveAs(blob, filename);
const a = document.createElement("a");
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}; };
return ( return (
@@ -61,51 +47,48 @@ export const CertificateContent = ({
<h2 className="mb-4">Serial Number</h2> <h2 className="mb-4">Serial Number</h2>
<div className="mb-8 flex items-center justify-between rounded-md bg-white/[0.07] p-2 text-base text-gray-400"> <div className="mb-8 flex items-center justify-between rounded-md bg-white/[0.07] p-2 text-base text-gray-400">
<p className="mr-4 break-all">{serialNumber}</p> <p className="mr-4 break-all">{serialNumber}</p>
<IconButton <Tooltip content={copyTextSerialNumber}>
ariaLabel="copy icon"
colorSchema="secondary"
className="group relative"
onClick={() => {
navigator.clipboard.writeText(serialNumber);
setIsSerialNumberCopied.on();
}}
>
<FontAwesomeIcon icon={isSerialNumberCopied ? faCheck : faCopy} />
<span className="absolute -left-8 -top-20 hidden w-28 translate-y-full rounded-md bg-bunker-800 py-2 pl-3 text-center text-sm text-gray-400 group-hover:flex group-hover:animate-fadeIn">
Click to copy
</span>
</IconButton>
</div>
<div className="mb-4 flex items-center justify-between">
<h2>Certificate Body</h2>
<div className="flex">
<IconButton <IconButton
ariaLabel="copy icon" ariaLabel="copy icon"
colorSchema="secondary" colorSchema="secondary"
className="group relative" className="group relative"
onClick={() => { onClick={() => {
navigator.clipboard.writeText(certificate); navigator.clipboard.writeText(serialNumber);
setIsCertificateCopied.on(); setCopyTextSerialNumber("Copied");
}} }}
> >
<FontAwesomeIcon icon={isCertificateCopied ? faCheck : faCopy} /> <FontAwesomeIcon icon={isCopyingSerialNumber ? faCheck : faCopy} />
<span className="absolute -left-8 -top-20 hidden w-28 translate-y-full rounded-md bg-bunker-800 py-2 pl-3 text-center text-sm text-gray-400 group-hover:flex group-hover:animate-fadeIn">
Copy
</span>
</IconButton>
<IconButton
ariaLabel="copy icon"
colorSchema="secondary"
className="group relative ml-2"
onClick={() => {
downloadTxtFile("cert.pem", certificate);
}}
>
<FontAwesomeIcon icon={faDownload} />
<span className="absolute -left-8 -top-20 hidden w-28 translate-y-full rounded-md bg-bunker-800 py-2 pl-3 text-center text-sm text-gray-400 group-hover:flex group-hover:animate-fadeIn">
Download
</span>
</IconButton> </IconButton>
</Tooltip>
</div>
<div className="mb-4 flex items-center justify-between">
<h2>Certificate Body</h2>
<div className="flex">
<Tooltip content={copyTextCertificate}>
<IconButton
ariaLabel="copy icon"
colorSchema="secondary"
className="group relative"
onClick={() => {
navigator.clipboard.writeText(certificate);
setCopyTextCertificate("Copied");
}}
>
<FontAwesomeIcon icon={isCopyingCertificate ? faCheck : faCopy} />
</IconButton>
</Tooltip>
<Tooltip content="Download">
<IconButton
ariaLabel="copy icon"
colorSchema="secondary"
className="group relative ml-2"
onClick={() => {
downloadTxtFile("cert.pem", certificate);
}}
>
<FontAwesomeIcon icon={faDownload} />
</IconButton>
</Tooltip>
</div> </div>
</div> </div>
<div className="mb-8 flex items-center justify-between rounded-md bg-white/[0.07] p-2 text-base text-gray-400"> <div className="mb-8 flex items-center justify-between rounded-md bg-white/[0.07] p-2 text-base text-gray-400">
@@ -116,33 +99,31 @@ export const CertificateContent = ({
<div className="mb-4 flex items-center justify-between"> <div className="mb-4 flex items-center justify-between">
<h2>Certificate Chain</h2> <h2>Certificate Chain</h2>
<div className="flex"> <div className="flex">
<IconButton <Tooltip content={copyTextCertificateChain}>
ariaLabel="copy icon" <IconButton
colorSchema="secondary" ariaLabel="copy icon"
className="group relative" colorSchema="secondary"
onClick={() => { className="group relative"
navigator.clipboard.writeText(certificateChain); onClick={() => {
setIsCertificateChainCopied.on(); navigator.clipboard.writeText(certificateChain);
}} setCopyTextCertificateChain("Copied");
> }}
<FontAwesomeIcon icon={isCertificateChainCopied ? faCheck : faCopy} /> >
<span className="absolute -left-8 -top-20 hidden w-28 translate-y-full rounded-md bg-bunker-800 py-2 pl-3 text-center text-sm text-gray-400 group-hover:flex group-hover:animate-fadeIn"> <FontAwesomeIcon icon={isCopyingCertificateChain ? faCheck : faCopy} />
Copy </IconButton>
</span> </Tooltip>
</IconButton> <Tooltip content="Download">
<IconButton <IconButton
ariaLabel="copy icon" ariaLabel="copy icon"
colorSchema="secondary" colorSchema="secondary"
className="group relative ml-2" className="group relative ml-2"
onClick={() => { onClick={() => {
downloadTxtFile("chain.pem", certificateChain); downloadTxtFile("chain.pem", certificateChain);
}} }}
> >
<FontAwesomeIcon icon={faDownload} /> <FontAwesomeIcon icon={faDownload} />
<span className="absolute -left-8 -top-20 hidden w-28 translate-y-full rounded-md bg-bunker-800 py-2 pl-3 text-center text-sm text-gray-400 group-hover:flex group-hover:animate-fadeIn"> </IconButton>
Download </Tooltip>
</span>
</IconButton>
</div> </div>
</div> </div>
<div className="mb-8 flex items-center justify-between rounded-md bg-white/[0.07] p-2 text-base text-gray-400"> <div className="mb-8 flex items-center justify-between rounded-md bg-white/[0.07] p-2 text-base text-gray-400">
@@ -155,33 +136,31 @@ export const CertificateContent = ({
<div className="mb-4 flex items-center justify-between"> <div className="mb-4 flex items-center justify-between">
<h2>Certificate Private Key</h2> <h2>Certificate Private Key</h2>
<div className="flex"> <div className="flex">
<IconButton <Tooltip content={copyTextCertificateSk}>
ariaLabel="copy icon" <IconButton
colorSchema="secondary" ariaLabel="copy icon"
className="group relative" colorSchema="secondary"
onClick={() => { className="group relative"
navigator.clipboard.writeText(privateKey); onClick={() => {
setIsCertificateSkCopied.on(); navigator.clipboard.writeText(privateKey);
}} setCopyTextCertificateSk("Copied");
> }}
<FontAwesomeIcon icon={isCertificateSkCopied ? faCheck : faCopy} /> >
<span className="absolute -left-8 -top-20 hidden w-28 translate-y-full rounded-md bg-bunker-800 py-2 pl-3 text-center text-sm text-gray-400 group-hover:flex group-hover:animate-fadeIn"> <FontAwesomeIcon icon={isCopyingCertificateSk ? faCheck : faCopy} />
Copy </IconButton>
</span> </Tooltip>
</IconButton> <Tooltip content={copyTextCertificateSk}>
<IconButton <IconButton
ariaLabel="copy icon" ariaLabel="copy icon"
colorSchema="secondary" colorSchema="secondary"
className="group relative ml-2" className="group relative ml-2"
onClick={() => { onClick={() => {
downloadTxtFile("private_key.txt", privateKey); downloadTxtFile("private_key.txt", privateKey);
}} }}
> >
<FontAwesomeIcon icon={faDownload} /> <FontAwesomeIcon icon={faDownload} />
<span className="absolute -left-8 -top-20 hidden w-28 translate-y-full rounded-md bg-bunker-800 py-2 pl-3 text-center text-sm text-gray-400 group-hover:flex group-hover:animate-fadeIn"> </IconButton>
Download </Tooltip>
</span>
</IconButton>
</div> </div>
</div> </div>
<div className="mb-8 flex items-center justify-between rounded-md bg-white/[0.07] p-2 text-base text-gray-400"> <div className="mb-8 flex items-center justify-between rounded-md bg-white/[0.07] p-2 text-base text-gray-400">
@@ -1,7 +1,6 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { Controller, useForm } from "react-hook-form"; import { Controller, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { format } from "date-fns";
import { z } from "zod"; import { z } from "zod";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
@@ -21,21 +20,11 @@ import { UsePopUpState } from "@app/hooks/usePopUp";
import { CertificateContent } from "./CertificateContent"; import { CertificateContent } from "./CertificateContent";
const isValidDate = (dateString: string) => {
if (dateString === "") return true;
const date = new Date(dateString);
return !Number.isNaN(date.getTime());
};
const schema = z.object({ const schema = z.object({
caId: z.string(), caId: z.string(),
friendlyName: z.string(),
commonName: z.string().trim().min(1), commonName: z.string().trim().min(1),
ttl: z.string().trim().optional(), ttl: z.string().trim()
notAfter: z
.string()
.trim()
.refine(isValidDate, { message: "Invalid date format" })
.transform((val) => (val === "" ? undefined : val))
}); });
export type FormData = z.infer<typeof schema>; export type FormData = z.infer<typeof schema>;
@@ -80,31 +69,30 @@ export const CertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
if (cert) { if (cert) {
reset({ reset({
caId: cert.caId, caId: cert.caId,
friendlyName: cert.friendlyName,
commonName: cert.commonName, commonName: cert.commonName,
ttl: "", ttl: ""
notAfter: format(new Date(cert.notAfter), "yyyy-MM-dd")
}); });
} else { } else {
reset({ reset({
caId: "", caId: "",
friendlyName: "",
commonName: "", commonName: "",
ttl: "", ttl: ""
notAfter: ""
}); });
} }
}, [cert]); }, [cert]);
const onFormSubmit = async ({ caId, commonName, ttl, notAfter }: FormData) => { const onFormSubmit = async ({ caId, friendlyName, commonName, ttl }: FormData) => {
try { try {
if (!currentWorkspace?.slug) return; if (!currentWorkspace?.slug) return;
const { serialNumber, certificate, certificateChain, privateKey } = await createCertificate({ const { serialNumber, certificate, certificateChain, privateKey } = await createCertificate({
projectSlug: currentWorkspace.slug, projectSlug: currentWorkspace.slug,
caId, caId,
friendlyName,
commonName, commonName,
ttl: ttl ? Number(ttl) : undefined, ttl
notBefore: new Date().toISOString(),
notAfter
}); });
reset(); reset();
@@ -175,6 +163,20 @@ export const CertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
</FormControl> </FormControl>
)} )}
/> />
<Controller
control={control}
defaultValue=""
name="friendlyName"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Friendly Name"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="My Certificate" isDisabled={Boolean(cert)} />
</FormControl>
)}
/>
<Controller <Controller
control={control} control={control}
defaultValue="" defaultValue=""
@@ -195,25 +197,16 @@ export const CertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
name="ttl" name="ttl"
render={({ field, fieldState: { error } }) => ( render={({ field, fieldState: { error } }) => (
<FormControl <FormControl
label="TTL (seconds)" label="TTL"
isError={Boolean(error)} isError={Boolean(error)}
errorText={error?.message} errorText={error?.message}
isRequired
> >
<Input {...field} placeholder="86400" isDisabled={Boolean(cert)} /> <Input
</FormControl> {...field}
)} placeholder="2 days, 1d, 2h, 1y, ..."
/> isDisabled={Boolean(cert)}
<Controller />
control={control}
defaultValue=""
name="notAfter"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Valid Until"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="YYYY-MM-DD" isDisabled={Boolean(cert)} />
</FormControl> </FormControl>
)} )}
/> />
@@ -53,7 +53,7 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => {
<Table> <Table>
<THead> <THead>
<Tr> <Tr>
<Th>Common Name</Th> <Th>Friendly Name</Th>
<Th>Status</Th> <Th>Status</Th>
<Th>Valid Until</Th> <Th>Valid Until</Th>
<Th /> <Th />
@@ -67,7 +67,7 @@ export const CertificatesTable = ({ handlePopUpOpen }: Props) => {
data.map((certificate) => { data.map((certificate) => {
return ( return (
<Tr className="h-10" key={`certificate-${certificate.id}`}> <Tr className="h-10" key={`certificate-${certificate.id}`}>
<Td>{certificate.commonName}</Td> <Td>{certificate.friendlyName}</Td>
<Td>{certStatusToNameMap[certificate.status]}</Td> <Td>{certStatusToNameMap[certificate.status]}</Td>
<Td> <Td>
{certificate.notAfter {certificate.notAfter
@@ -4,6 +4,7 @@ import {
faAnchorLock, faAnchorLock,
faArrowLeft, faArrowLeft,
faBook, faBook,
faCertificate,
faCog, faCog,
faKey, faKey,
faLock, faLock,
@@ -13,8 +14,7 @@ import {
faShield, faShield,
faTags, faTags,
faUser, faUser,
faUsers faUsers} from "@fortawesome/free-solid-svg-icons";
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
@@ -117,6 +117,18 @@ const SINGLE_PERMISSION_LIST = [
subtitle: "IP allowlist management control", subtitle: "IP allowlist management control",
icon: faNetworkWired, icon: faNetworkWired,
formName: "ip-allowlist" formName: "ip-allowlist"
},
{
title: "Certificate Authorities",
subtitle: "CA management control",
icon: faCertificate,
formName: "certificate-authorities"
},
{
title: "Certificates",
subtitle: "Certificate management control",
icon: faCertificate,
formName: "certificates"
} }
] as const; ] as const;
@@ -48,6 +48,8 @@ export const formSchema = z.object({
tags: generalPermissionSchema, tags: generalPermissionSchema,
"audit-logs": generalPermissionSchema, "audit-logs": generalPermissionSchema,
"ip-allowlist": generalPermissionSchema, "ip-allowlist": generalPermissionSchema,
"certificate-authorities": generalPermissionSchema,
certificates: generalPermissionSchema,
// akhilmhdh: refactor all keys like below // akhilmhdh: refactor all keys like below
[ProjectPermissionSub.SecretApproval]: generalPermissionSchema, [ProjectPermissionSub.SecretApproval]: generalPermissionSchema,
workspace: z workspace: z
@@ -25,6 +25,8 @@ type Props = {
| "audit-logs" | "audit-logs"
| "ip-allowlist" | "ip-allowlist"
| "identity" | "identity"
| "certificate-authorities"
| "certificates"
| ProjectPermissionSub.SecretApproval; | ProjectPermissionSub.SecretApproval;
isNonEditable?: boolean; isNonEditable?: boolean;
setValue: UseFormSetValue<TFormSchema>; setValue: UseFormSetValue<TFormSchema>;