mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 20:27:12 +00:00
Remove cert secret structure, show cert sk once upon issuance
This commit is contained in:
@@ -5,7 +5,7 @@ import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
|
|||||||
|
|
||||||
export async function up(knex: Knex): Promise<void> {
|
export async function up(knex: Knex): Promise<void> {
|
||||||
if (!(await knex.schema.hasTable(TableName.CertificateAuthority))) {
|
if (!(await knex.schema.hasTable(TableName.CertificateAuthority))) {
|
||||||
// TODO: consider adding algo details
|
// TODO: add algo deets
|
||||||
await knex.schema.createTable(TableName.CertificateAuthority, (t) => {
|
await knex.schema.createTable(TableName.CertificateAuthority, (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);
|
||||||
@@ -77,30 +77,15 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!(await knex.schema.hasTable(TableName.CertificateSecret))) {
|
|
||||||
await knex.schema.createTable(TableName.CertificateSecret, (t) => {
|
|
||||||
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
|
||||||
t.timestamps(true, true, true);
|
|
||||||
t.uuid("certId").notNullable().unique();
|
|
||||||
t.foreign("certId").references("id").inTable(TableName.Certificate).onDelete("CASCADE");
|
|
||||||
t.text("pk").notNullable(); // TODO: encrypt
|
|
||||||
t.text("sk").notNullable(); // TODO: encrypt
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await createOnUpdateTrigger(knex, TableName.CertificateAuthority);
|
await createOnUpdateTrigger(knex, TableName.CertificateAuthority);
|
||||||
await createOnUpdateTrigger(knex, TableName.CertificateAuthorityCert);
|
await createOnUpdateTrigger(knex, TableName.CertificateAuthorityCert);
|
||||||
await createOnUpdateTrigger(knex, TableName.CertificateAuthoritySk);
|
await createOnUpdateTrigger(knex, TableName.CertificateAuthoritySk);
|
||||||
await createOnUpdateTrigger(knex, TableName.Certificate);
|
await createOnUpdateTrigger(knex, TableName.Certificate);
|
||||||
await createOnUpdateTrigger(knex, TableName.CertificateCert);
|
await createOnUpdateTrigger(knex, TableName.CertificateCert);
|
||||||
await createOnUpdateTrigger(knex, TableName.CertificateSecret);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function down(knex: Knex): Promise<void> {
|
export async function down(knex: Knex): Promise<void> {
|
||||||
// certificates
|
// certificates
|
||||||
await knex.schema.dropTableIfExists(TableName.CertificateSecret);
|
|
||||||
await dropOnUpdateTrigger(knex, TableName.CertificateSecret);
|
|
||||||
|
|
||||||
await knex.schema.dropTableIfExists(TableName.CertificateCert);
|
await knex.schema.dropTableIfExists(TableName.CertificateCert);
|
||||||
await dropOnUpdateTrigger(knex, TableName.CertificateCert);
|
await dropOnUpdateTrigger(knex, TableName.CertificateCert);
|
||||||
|
|
||||||
|
|||||||
@@ -73,7 +73,6 @@ 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 { certificateCertDALFactory } from "@app/services/certificate/certificate-cert-dal";
|
||||||
import { certificateDALFactory } from "@app/services/certificate/certificate-dal";
|
import { certificateDALFactory } from "@app/services/certificate/certificate-dal";
|
||||||
import { certificateSecretDALFactory } from "@app/services/certificate/certificate-secret-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";
|
||||||
import { certificateAuthorityDALFactory } from "@app/services/certificate-authority/certificate-authority-dal";
|
import { certificateAuthorityDALFactory } from "@app/services/certificate-authority/certificate-authority-dal";
|
||||||
@@ -502,14 +501,11 @@ export const registerRoutes = async (
|
|||||||
|
|
||||||
const certificateDAL = certificateDALFactory(db);
|
const certificateDAL = certificateDALFactory(db);
|
||||||
const certificateCertDAL = certificateCertDALFactory(db);
|
const certificateCertDAL = certificateCertDALFactory(db);
|
||||||
const certificateSecretDAL = certificateSecretDALFactory(db);
|
|
||||||
|
|
||||||
const certificateService = certificateServiceFactory({
|
const certificateService = certificateServiceFactory({
|
||||||
certificateDAL,
|
certificateDAL,
|
||||||
certificateCertDAL,
|
certificateCertDAL,
|
||||||
certificateSecretDAL,
|
|
||||||
certificateAuthorityDAL,
|
certificateAuthorityDAL,
|
||||||
certificateAuthorityCertDAL,
|
|
||||||
permissionService
|
permissionService
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -519,7 +515,6 @@ export const registerRoutes = async (
|
|||||||
certificateAuthoritySkDAL,
|
certificateAuthoritySkDAL,
|
||||||
certificateDAL,
|
certificateDAL,
|
||||||
certificateCertDAL,
|
certificateCertDAL,
|
||||||
certificateSecretDAL,
|
|
||||||
projectDAL,
|
projectDAL,
|
||||||
permissionService
|
permissionService
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -86,26 +86,21 @@ export const registerCertRouter = async (server: FastifyZodProvider) => {
|
|||||||
200: z.object({
|
200: z.object({
|
||||||
certificate: z.string().trim(),
|
certificate: z.string().trim(),
|
||||||
certificateChain: z.string().trim(),
|
certificateChain: z.string().trim(),
|
||||||
issuingCaCertificate: z.string().trim(),
|
|
||||||
privateKey: z.string().trim(),
|
|
||||||
serialNumber: z.string().trim()
|
serialNumber: z.string().trim()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
handler: async (req) => {
|
handler: async (req) => {
|
||||||
const { certificate, certificateChain, issuingCaCertificate, privateKey, serialNumber } =
|
const { certificate, certificateChain, serialNumber } = await server.services.certificate.getCertCert({
|
||||||
await server.services.certificate.getCertCert({
|
certId: req.params.certId,
|
||||||
certId: req.params.certId,
|
actor: req.permission.type,
|
||||||
actor: req.permission.type,
|
actorId: req.permission.id,
|
||||||
actorId: req.permission.id,
|
actorAuthMethod: req.permission.authMethod,
|
||||||
actorAuthMethod: req.permission.authMethod,
|
actorOrgId: req.permission.orgId
|
||||||
actorOrgId: req.permission.orgId
|
});
|
||||||
});
|
|
||||||
return {
|
return {
|
||||||
certificate,
|
certificate,
|
||||||
certificateChain,
|
certificateChain,
|
||||||
issuingCaCertificate,
|
|
||||||
privateKey,
|
|
||||||
serialNumber
|
serialNumber
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services
|
|||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
import { TCertificateCertDALFactory } from "@app/services/certificate/certificate-cert-dal";
|
import { TCertificateCertDALFactory } from "@app/services/certificate/certificate-cert-dal";
|
||||||
import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal";
|
import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal";
|
||||||
import { TCertificateSecretDALFactory } from "@app/services/certificate/certificate-secret-dal";
|
|
||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
|
|
||||||
import { TCertificateAuthorityCertDALFactory } from "./certificate-authority-cert-dal";
|
import { TCertificateAuthorityCertDALFactory } from "./certificate-authority-cert-dal";
|
||||||
@@ -29,15 +28,16 @@ import {
|
|||||||
} from "./certificate-authority-types";
|
} from "./certificate-authority-types";
|
||||||
|
|
||||||
type TCertificateAuthorityServiceFactoryDep = {
|
type TCertificateAuthorityServiceFactoryDep = {
|
||||||
// TODO: Pick
|
certificateAuthorityDAL: Pick<
|
||||||
certificateAuthorityDAL: TCertificateAuthorityDALFactory;
|
TCertificateAuthorityDALFactory,
|
||||||
certificateAuthorityCertDAL: TCertificateAuthorityCertDALFactory;
|
"transaction" | "create" | "findById" | "updateById" | "deleteById" | "findOne" | "buildCertificateChain"
|
||||||
certificateAuthoritySkDAL: TCertificateAuthoritySkDALFactory;
|
>;
|
||||||
certificateDAL: TCertificateDALFactory;
|
certificateAuthorityCertDAL: Pick<TCertificateAuthorityCertDALFactory, "create" | "findOne" | "transaction">;
|
||||||
certificateCertDAL: TCertificateCertDALFactory;
|
certificateAuthoritySkDAL: Pick<TCertificateAuthoritySkDALFactory, "create" | "findOne">;
|
||||||
certificateSecretDAL: TCertificateSecretDALFactory;
|
certificateDAL: Pick<TCertificateDALFactory, "transaction" | "create">;
|
||||||
projectDAL: TProjectDALFactory;
|
certificateCertDAL: Pick<TCertificateCertDALFactory, "create">;
|
||||||
permissionService: TPermissionServiceFactory;
|
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug">;
|
||||||
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TCertificateAuthorityServiceFactory = ReturnType<typeof certificateAuthorityServiceFactory>;
|
export type TCertificateAuthorityServiceFactory = ReturnType<typeof certificateAuthorityServiceFactory>;
|
||||||
@@ -48,7 +48,6 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
certificateAuthoritySkDAL,
|
certificateAuthoritySkDAL,
|
||||||
certificateDAL,
|
certificateDAL,
|
||||||
certificateCertDAL,
|
certificateCertDAL,
|
||||||
certificateSecretDAL,
|
|
||||||
projectDAL,
|
projectDAL,
|
||||||
permissionService
|
permissionService
|
||||||
}: TCertificateAuthorityServiceFactoryDep) => {
|
}: TCertificateAuthorityServiceFactoryDep) => {
|
||||||
@@ -624,12 +623,6 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
|
|
||||||
const chain = await certificateAuthorityDAL.buildCertificateChain(caId);
|
const chain = await certificateAuthorityDAL.buildCertificateChain(caId);
|
||||||
|
|
||||||
// https://nodejs.org/api/crypto.html#static-method-keyobjectfromkey
|
|
||||||
const skObj = KeyObject.from(leafKeys.privateKey);
|
|
||||||
const sk = skObj.export({ format: "pem", type: "pkcs8" }) as string;
|
|
||||||
const pkObj = KeyObject.from(leafKeys.publicKey);
|
|
||||||
const pk = pkObj.export({ format: "pem", type: "spki" }) as string;
|
|
||||||
|
|
||||||
await certificateDAL.transaction(async (tx) => {
|
await certificateDAL.transaction(async (tx) => {
|
||||||
const cert = await certificateDAL.create(
|
const cert = await certificateDAL.create(
|
||||||
{
|
{
|
||||||
@@ -650,15 +643,6 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
|
|
||||||
await certificateSecretDAL.create(
|
|
||||||
{
|
|
||||||
certId: cert.id,
|
|
||||||
pk, // TODO: encrypt
|
|
||||||
sk // TODO: encrypt
|
|
||||||
},
|
|
||||||
tx
|
|
||||||
);
|
|
||||||
|
|
||||||
return cert;
|
return cert;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +0,0 @@
|
|||||||
import { TDbClient } from "@app/db";
|
|
||||||
import { TableName } from "@app/db/schemas";
|
|
||||||
import { ormify } from "@app/lib/knex";
|
|
||||||
|
|
||||||
export type TCertificateSecretDALFactory = ReturnType<typeof certificateSecretDALFactory>;
|
|
||||||
|
|
||||||
export const certificateSecretDALFactory = (db: TDbClient) => {
|
|
||||||
const certificateSecretOrm = ormify(db, TableName.CertificateSecret);
|
|
||||||
return certificateSecretOrm;
|
|
||||||
};
|
|
||||||
@@ -5,20 +5,15 @@ import { TPermissionServiceFactory } from "@app/ee/services/permission/permissio
|
|||||||
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 { TCertificateCertDALFactory } from "@app/services/certificate/certificate-cert-dal";
|
||||||
import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal";
|
import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal";
|
||||||
import { TCertificateSecretDALFactory } from "@app/services/certificate/certificate-secret-dal";
|
|
||||||
import { TCertificateAuthorityCertDALFactory } from "@app/services/certificate-authority/certificate-authority-cert-dal";
|
|
||||||
import { TCertificateAuthorityDALFactory } from "@app/services/certificate-authority/certificate-authority-dal";
|
import { TCertificateAuthorityDALFactory } from "@app/services/certificate-authority/certificate-authority-dal";
|
||||||
|
|
||||||
import { TDeleteCertDTO, TGetCertCertDTO, TGetCertDTO } from "./certificate-types";
|
import { TDeleteCertDTO, TGetCertCertDTO, TGetCertDTO } from "./certificate-types";
|
||||||
|
|
||||||
type TCertificateServiceFactoryDep = {
|
type TCertificateServiceFactoryDep = {
|
||||||
// TODO: Pick
|
certificateDAL: Pick<TCertificateDALFactory, "findById" | "deleteById">;
|
||||||
certificateDAL: TCertificateDALFactory;
|
certificateCertDAL: Pick<TCertificateCertDALFactory, "findOne">;
|
||||||
certificateCertDAL: TCertificateCertDALFactory;
|
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findById">;
|
||||||
certificateSecretDAL: TCertificateSecretDALFactory;
|
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
|
||||||
certificateAuthorityDAL: TCertificateAuthorityDALFactory;
|
|
||||||
certificateAuthorityCertDAL: TCertificateAuthorityCertDALFactory;
|
|
||||||
permissionService: TPermissionServiceFactory;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TCertificateServiceFactory = ReturnType<typeof certificateServiceFactory>;
|
export type TCertificateServiceFactory = ReturnType<typeof certificateServiceFactory>;
|
||||||
@@ -26,9 +21,7 @@ export type TCertificateServiceFactory = ReturnType<typeof certificateServiceFac
|
|||||||
export const certificateServiceFactory = ({
|
export const certificateServiceFactory = ({
|
||||||
certificateDAL,
|
certificateDAL,
|
||||||
certificateCertDAL,
|
certificateCertDAL,
|
||||||
certificateSecretDAL,
|
|
||||||
certificateAuthorityDAL,
|
certificateAuthorityDAL,
|
||||||
certificateAuthorityCertDAL,
|
|
||||||
permissionService
|
permissionService
|
||||||
}: TCertificateServiceFactoryDep) => {
|
}: TCertificateServiceFactoryDep) => {
|
||||||
const getCertById = async ({ certId, actorId, actorAuthMethod, actor, actorOrgId }: TGetCertDTO) => {
|
const getCertById = async ({ certId, actorId, actorAuthMethod, actor, actorOrgId }: TGetCertDTO) => {
|
||||||
@@ -77,19 +70,15 @@ export const certificateServiceFactory = ({
|
|||||||
actorAuthMethod,
|
actorAuthMethod,
|
||||||
actorOrgId
|
actorOrgId
|
||||||
);
|
);
|
||||||
// TODO: re-evaluate this permission
|
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Certificates);
|
|
||||||
|
|
||||||
const caCert = await certificateAuthorityCertDAL.findOne({ caId: ca.id });
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Certificates);
|
||||||
|
|
||||||
const certCert = await certificateCertDAL.findOne({ certId });
|
const certCert = await certificateCertDAL.findOne({ certId });
|
||||||
const certSecret = await certificateSecretDAL.findOne({ certId });
|
|
||||||
const certObj = new x509.X509Certificate(certCert.certificate);
|
const certObj = new x509.X509Certificate(certCert.certificate);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
certificate: certCert.certificate,
|
certificate: certCert.certificate,
|
||||||
certificateChain: certCert.certificateChain,
|
certificateChain: certCert.certificateChain,
|
||||||
issuingCaCertificate: caCert.certificate,
|
|
||||||
privateKey: certSecret.sk,
|
|
||||||
serialNumber: certObj.serialNumber
|
serialNumber: certObj.serialNumber
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { CaStatus,CaType } from "./enums";
|
import { CaStatus, CaType } from "./enums";
|
||||||
|
|
||||||
export type TCertificateAuthority = {
|
export type TCertificateAuthority = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -85,6 +85,6 @@ export type TCreateCertificateResponse = {
|
|||||||
certificate: string;
|
certificate: string;
|
||||||
issuingCertificate: string;
|
issuingCertificate: string;
|
||||||
certificateChain: string;
|
certificateChain: string;
|
||||||
sk: string;
|
privateKey: string;
|
||||||
serialNumber: string;
|
serialNumber: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -29,8 +29,6 @@ export const useGetCertCert = (certId: string) => {
|
|||||||
const { data } = await apiRequest.get<{
|
const { data } = await apiRequest.get<{
|
||||||
certificate: string;
|
certificate: string;
|
||||||
certificateChain: string;
|
certificateChain: string;
|
||||||
issuingCaCertificate: string;
|
|
||||||
privateKey: string;
|
|
||||||
serialNumber: string;
|
serialNumber: string;
|
||||||
}>(`/api/v1/certificates/${certId}/certificate`);
|
}>(`/api/v1/certificates/${certId}/certificate`);
|
||||||
return data;
|
return data;
|
||||||
|
|||||||
+8
-134
@@ -1,12 +1,9 @@
|
|||||||
import { useEffect } from "react";
|
import { Modal, ModalContent } from "@app/components/v2";
|
||||||
import { faCheck, faCopy, faDownload } from "@fortawesome/free-solid-svg-icons";
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
||||||
|
|
||||||
import { IconButton, Modal, ModalContent } from "@app/components/v2";
|
|
||||||
import { useToggle } from "@app/hooks";
|
|
||||||
import { useGetCaCert } from "@app/hooks/api";
|
import { useGetCaCert } from "@app/hooks/api";
|
||||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
|
import { CertificateContent } from "../../CertificatesTab/components/CertificateContent";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
popUp: UsePopUpState<["caCert"]>;
|
popUp: UsePopUpState<["caCert"]>;
|
||||||
handlePopUpToggle: (popUpName: keyof UsePopUpState<["caCert"]>, state?: boolean) => void;
|
handlePopUpToggle: (popUpName: keyof UsePopUpState<["caCert"]>, state?: boolean) => void;
|
||||||
@@ -14,39 +11,6 @@ type Props = {
|
|||||||
|
|
||||||
export const CaCertModal = ({ popUp, handlePopUpToggle }: Props) => {
|
export const CaCertModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||||
const { data } = useGetCaCert((popUp?.caCert?.data as { caId: string })?.caId || "");
|
const { data } = useGetCaCert((popUp?.caCert?.data as { caId: string })?.caId || "");
|
||||||
const [isSerialNumberCopied, setIsSerialNumberCopied] = useToggle(false);
|
|
||||||
const [isCertificateCopied, setIsCertificateCopied] = useToggle(false);
|
|
||||||
const [isCertificateChainCopied, setIsCertificateChainCopied] = useToggle(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
let timer: NodeJS.Timeout;
|
|
||||||
if (isSerialNumberCopied) {
|
|
||||||
timer = setTimeout(() => setIsSerialNumberCopied.off(), 2000);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isCertificateCopied) {
|
|
||||||
timer = setTimeout(() => setIsCertificateCopied.off(), 2000);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isCertificateChainCopied) {
|
|
||||||
timer = setTimeout(() => setIsCertificateChainCopied.off(), 2000);
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => clearTimeout(timer);
|
|
||||||
}, [isSerialNumberCopied, isCertificateCopied, isCertificateChainCopied]);
|
|
||||||
|
|
||||||
const downloadTxtFile = (filename: string, content: string) => {
|
|
||||||
const blob = new Blob([content], { type: "text/plain" });
|
|
||||||
const url = URL.createObjectURL(blob);
|
|
||||||
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 (
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={popUp?.caCert?.isOpen}
|
isOpen={popUp?.caCert?.isOpen}
|
||||||
@@ -56,101 +20,11 @@ export const CaCertModal = ({ popUp, handlePopUpToggle }: Props) => {
|
|||||||
>
|
>
|
||||||
<ModalContent title="CA Certificate">
|
<ModalContent title="CA Certificate">
|
||||||
{data ? (
|
{data ? (
|
||||||
<div>
|
<CertificateContent
|
||||||
<h2 className="mb-4">Serial Number</h2>
|
serialNumber={data.serialNumber}
|
||||||
<div className="mb-8 flex items-center justify-between rounded-md bg-white/[0.07] p-2 text-base text-gray-400">
|
certificate={data.certificate}
|
||||||
<p className="mr-4 break-all">{data.serialNumber}</p>
|
certificateChain={data.certificateChain}
|
||||||
<IconButton
|
/>
|
||||||
ariaLabel="copy icon"
|
|
||||||
colorSchema="secondary"
|
|
||||||
className="group relative"
|
|
||||||
onClick={() => {
|
|
||||||
navigator.clipboard.writeText(data.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
|
|
||||||
ariaLabel="copy icon"
|
|
||||||
colorSchema="secondary"
|
|
||||||
className="group relative"
|
|
||||||
onClick={() => {
|
|
||||||
navigator.clipboard.writeText(data.certificate);
|
|
||||||
setIsCertificateCopied.on();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon icon={isCertificateCopied ? 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("certificate.txt", data.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>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<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 whitespace-pre-wrap break-all">{data.certificate}</p>
|
|
||||||
</div>
|
|
||||||
{data.certificateChain && (
|
|
||||||
<>
|
|
||||||
<div className="mb-4 flex items-center justify-between">
|
|
||||||
<h2>Certificate Chain</h2>
|
|
||||||
<div className="flex">
|
|
||||||
<IconButton
|
|
||||||
ariaLabel="copy icon"
|
|
||||||
colorSchema="secondary"
|
|
||||||
className="group relative"
|
|
||||||
onClick={() => {
|
|
||||||
navigator.clipboard.writeText(data.certificateChain);
|
|
||||||
setIsCertificateChainCopied.on();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<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">
|
|
||||||
Copy
|
|
||||||
</span>
|
|
||||||
</IconButton>
|
|
||||||
<IconButton
|
|
||||||
ariaLabel="copy icon"
|
|
||||||
colorSchema="secondary"
|
|
||||||
className="group relative ml-2"
|
|
||||||
onClick={() => {
|
|
||||||
downloadTxtFile("certificate_chain.txt", data.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>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<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 whitespace-pre-wrap break-all">{data.certificateChain}</p>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
) : (
|
) : (
|
||||||
<div />
|
<div />
|
||||||
)}
|
)}
|
||||||
|
|||||||
+8
-169
@@ -1,12 +1,9 @@
|
|||||||
import { useEffect } from "react";
|
import { Modal, ModalContent } from "@app/components/v2";
|
||||||
import { faCheck, faCopy, faDownload } from "@fortawesome/free-solid-svg-icons";
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
||||||
|
|
||||||
import { IconButton, Modal, ModalContent } from "@app/components/v2";
|
|
||||||
import { useToggle } from "@app/hooks";
|
|
||||||
import { useGetCertCert } from "@app/hooks/api";
|
import { useGetCertCert } from "@app/hooks/api";
|
||||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
|
import { CertificateContent } from "./CertificateContent";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
popUp: UsePopUpState<["certificateCert"]>;
|
popUp: UsePopUpState<["certificateCert"]>;
|
||||||
handlePopUpToggle: (popUpName: keyof UsePopUpState<["certificateCert"]>, state?: boolean) => void;
|
handlePopUpToggle: (popUpName: keyof UsePopUpState<["certificateCert"]>, state?: boolean) => void;
|
||||||
@@ -17,44 +14,6 @@ export const CertificateCertModal = ({ popUp, handlePopUpToggle }: Props) => {
|
|||||||
(popUp?.certificateCert?.data as { certId: string })?.certId || ""
|
(popUp?.certificateCert?.data as { certId: string })?.certId || ""
|
||||||
);
|
);
|
||||||
|
|
||||||
const [isSerialNumberCopied, setIsSerialNumberCopied] = useToggle(false);
|
|
||||||
const [isCertificateCopied, setIsCertificateCopied] = useToggle(false);
|
|
||||||
const [isCertificateChainCopied, setIsCertificateChainCopied] = useToggle(false);
|
|
||||||
const [isCertificateSkCopied, setIsCertificateSkCopied] = useToggle(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
let timer: NodeJS.Timeout;
|
|
||||||
if (isSerialNumberCopied) {
|
|
||||||
timer = setTimeout(() => setIsSerialNumberCopied.off(), 2000);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isCertificateCopied) {
|
|
||||||
timer = setTimeout(() => setIsCertificateCopied.off(), 2000);
|
|
||||||
}
|
|
||||||
|
|
||||||
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 blob = new Blob([content], { type: "text/plain" });
|
|
||||||
const url = URL.createObjectURL(blob);
|
|
||||||
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 (
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={popUp?.certificateCert?.isOpen}
|
isOpen={popUp?.certificateCert?.isOpen}
|
||||||
@@ -64,131 +23,11 @@ export const CertificateCertModal = ({ popUp, handlePopUpToggle }: Props) => {
|
|||||||
>
|
>
|
||||||
<ModalContent title="Export Certificate">
|
<ModalContent title="Export Certificate">
|
||||||
{data ? (
|
{data ? (
|
||||||
<div>
|
<CertificateContent
|
||||||
<h2 className="mb-4">Serial Number</h2>
|
serialNumber={data.serialNumber}
|
||||||
<div className="mb-8 flex items-center justify-between rounded-md bg-white/[0.07] p-2 text-base text-gray-400">
|
certificate={data.certificate}
|
||||||
<p className="mr-4 break-all">{data.serialNumber}</p>
|
certificateChain={data.certificateChain}
|
||||||
<IconButton
|
/>
|
||||||
ariaLabel="copy icon"
|
|
||||||
colorSchema="secondary"
|
|
||||||
className="group relative"
|
|
||||||
onClick={() => {
|
|
||||||
navigator.clipboard.writeText(data.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
|
|
||||||
ariaLabel="copy icon"
|
|
||||||
colorSchema="secondary"
|
|
||||||
className="group relative"
|
|
||||||
onClick={() => {
|
|
||||||
navigator.clipboard.writeText(data.certificate);
|
|
||||||
setIsCertificateCopied.on();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon icon={isCertificateCopied ? 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("certificate.txt", data.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>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<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 whitespace-pre-wrap break-all">{data.certificate}</p>
|
|
||||||
</div>
|
|
||||||
<div className="mb-4 flex items-center justify-between">
|
|
||||||
<h2>Certificate Chain</h2>
|
|
||||||
<div className="flex">
|
|
||||||
<IconButton
|
|
||||||
ariaLabel="copy icon"
|
|
||||||
colorSchema="secondary"
|
|
||||||
className="group relative"
|
|
||||||
onClick={() => {
|
|
||||||
navigator.clipboard.writeText(data.certificateChain);
|
|
||||||
setIsCertificateChainCopied.on();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<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">
|
|
||||||
Copy
|
|
||||||
</span>
|
|
||||||
</IconButton>
|
|
||||||
<IconButton
|
|
||||||
ariaLabel="copy icon"
|
|
||||||
colorSchema="secondary"
|
|
||||||
className="group relative ml-2"
|
|
||||||
onClick={() => {
|
|
||||||
downloadTxtFile("certificate_chain.txt", data.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>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<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 whitespace-pre-wrap break-all">{data.certificateChain}</p>
|
|
||||||
</div>
|
|
||||||
<div className="mb-4 flex items-center justify-between">
|
|
||||||
<h2>Certificate Private Key</h2>
|
|
||||||
<div className="flex">
|
|
||||||
<IconButton
|
|
||||||
ariaLabel="copy icon"
|
|
||||||
colorSchema="secondary"
|
|
||||||
className="group relative"
|
|
||||||
onClick={() => {
|
|
||||||
navigator.clipboard.writeText(data.privateKey);
|
|
||||||
setIsCertificateSkCopied.on();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<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">
|
|
||||||
Copy
|
|
||||||
</span>
|
|
||||||
</IconButton>
|
|
||||||
<IconButton
|
|
||||||
ariaLabel="copy icon"
|
|
||||||
colorSchema="secondary"
|
|
||||||
className="group relative ml-2"
|
|
||||||
onClick={() => {
|
|
||||||
downloadTxtFile("private_key.txt", data.privateKey);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<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>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<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 whitespace-pre-wrap break-all">{data.privateKey}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
) : (
|
) : (
|
||||||
<div />
|
<div />
|
||||||
)}
|
)}
|
||||||
|
|||||||
+190
@@ -0,0 +1,190 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import { faCheck, faCopy, faDownload } from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
|
|
||||||
|
import { IconButton } from "@app/components/v2";
|
||||||
|
import { useToggle } from "@app/hooks";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
serialNumber: string;
|
||||||
|
certificate: string;
|
||||||
|
certificateChain: string;
|
||||||
|
privateKey?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CertificateContent = ({
|
||||||
|
serialNumber,
|
||||||
|
certificate,
|
||||||
|
certificateChain,
|
||||||
|
privateKey
|
||||||
|
}: Props) => {
|
||||||
|
const [isSerialNumberCopied, setIsSerialNumberCopied] = useToggle(false);
|
||||||
|
const [isCertificateCopied, setIsCertificateCopied] = useToggle(false);
|
||||||
|
const [isCertificateChainCopied, setIsCertificateChainCopied] = useToggle(false);
|
||||||
|
const [isCertificateSkCopied, setIsCertificateSkCopied] = useToggle(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let timer: NodeJS.Timeout;
|
||||||
|
if (isSerialNumberCopied) {
|
||||||
|
timer = setTimeout(() => setIsSerialNumberCopied.off(), 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isCertificateCopied) {
|
||||||
|
timer = setTimeout(() => setIsCertificateCopied.off(), 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 blob = new Blob([content], { type: "text/plain" });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
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 (
|
||||||
|
<div>
|
||||||
|
<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">
|
||||||
|
<p className="mr-4 break-all">{serialNumber}</p>
|
||||||
|
<IconButton
|
||||||
|
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
|
||||||
|
ariaLabel="copy icon"
|
||||||
|
colorSchema="secondary"
|
||||||
|
className="group relative"
|
||||||
|
onClick={() => {
|
||||||
|
navigator.clipboard.writeText(certificate);
|
||||||
|
setIsCertificateCopied.on();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FontAwesomeIcon icon={isCertificateCopied ? 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("certificate.txt", 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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<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 whitespace-pre-wrap break-all">{certificate}</p>
|
||||||
|
</div>
|
||||||
|
<div className="mb-4 flex items-center justify-between">
|
||||||
|
<h2>Certificate Chain</h2>
|
||||||
|
<div className="flex">
|
||||||
|
<IconButton
|
||||||
|
ariaLabel="copy icon"
|
||||||
|
colorSchema="secondary"
|
||||||
|
className="group relative"
|
||||||
|
onClick={() => {
|
||||||
|
navigator.clipboard.writeText(certificateChain);
|
||||||
|
setIsCertificateChainCopied.on();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<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">
|
||||||
|
Copy
|
||||||
|
</span>
|
||||||
|
</IconButton>
|
||||||
|
<IconButton
|
||||||
|
ariaLabel="copy icon"
|
||||||
|
colorSchema="secondary"
|
||||||
|
className="group relative ml-2"
|
||||||
|
onClick={() => {
|
||||||
|
downloadTxtFile("certificate_chain.txt", 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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<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 whitespace-pre-wrap break-all">{certificateChain}</p>
|
||||||
|
</div>
|
||||||
|
{privateKey && (
|
||||||
|
<>
|
||||||
|
<div className="mb-4 flex items-center justify-between">
|
||||||
|
<h2>Certificate Private Key</h2>
|
||||||
|
<div className="flex">
|
||||||
|
<IconButton
|
||||||
|
ariaLabel="copy icon"
|
||||||
|
colorSchema="secondary"
|
||||||
|
className="group relative"
|
||||||
|
onClick={() => {
|
||||||
|
navigator.clipboard.writeText(privateKey);
|
||||||
|
setIsCertificateSkCopied.on();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<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">
|
||||||
|
Copy
|
||||||
|
</span>
|
||||||
|
</IconButton>
|
||||||
|
<IconButton
|
||||||
|
ariaLabel="copy icon"
|
||||||
|
colorSchema="secondary"
|
||||||
|
className="group relative ml-2"
|
||||||
|
onClick={() => {
|
||||||
|
downloadTxtFile("private_key.txt", privateKey);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<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 whitespace-pre-wrap break-all">{privateKey}</p>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
+115
-85
@@ -1,4 +1,4 @@
|
|||||||
import { useEffect } 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 { format } from "date-fns";
|
||||||
@@ -24,6 +24,8 @@ import {
|
|||||||
import { caTypeToNameMap } from "@app/hooks/api/ca/constants";
|
import { caTypeToNameMap } from "@app/hooks/api/ca/constants";
|
||||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
|
import { CertificateContent } from "./CertificateContent";
|
||||||
|
|
||||||
const isValidDate = (dateString: string) => {
|
const isValidDate = (dateString: string) => {
|
||||||
if (dateString === "") return true;
|
if (dateString === "") return true;
|
||||||
const date = new Date(dateString);
|
const date = new Date(dateString);
|
||||||
@@ -48,7 +50,15 @@ type Props = {
|
|||||||
handlePopUpToggle: (popUpName: keyof UsePopUpState<["certificate"]>, state?: boolean) => void;
|
handlePopUpToggle: (popUpName: keyof UsePopUpState<["certificate"]>, state?: boolean) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type TCertificateDetails = {
|
||||||
|
serialNumber: string;
|
||||||
|
certificate: string;
|
||||||
|
certificateChain: string;
|
||||||
|
privateKey: string;
|
||||||
|
};
|
||||||
|
|
||||||
export const CertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
|
export const CertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||||
|
const [certificateDetails, setCertificateDetails] = useState<TCertificateDetails | null>(null);
|
||||||
const { currentWorkspace } = useWorkspace();
|
const { currentWorkspace } = useWorkspace();
|
||||||
const { data: cert } = useGetCertById(
|
const { data: cert } = useGetCertById(
|
||||||
(popUp?.certificate?.data as { certId: string })?.certId || ""
|
(popUp?.certificate?.data as { certId: string })?.certId || ""
|
||||||
@@ -93,7 +103,7 @@ export const CertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
|
|||||||
try {
|
try {
|
||||||
if (!currentWorkspace?.slug) return;
|
if (!currentWorkspace?.slug) return;
|
||||||
|
|
||||||
await createCertificate({
|
const { serialNumber, certificate, certificateChain, privateKey } = await createCertificate({
|
||||||
projectSlug: currentWorkspace.slug,
|
projectSlug: currentWorkspace.slug,
|
||||||
caId,
|
caId,
|
||||||
commonName,
|
commonName,
|
||||||
@@ -103,7 +113,13 @@ export const CertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
reset();
|
reset();
|
||||||
handlePopUpToggle("certificate", false);
|
|
||||||
|
setCertificateDetails({
|
||||||
|
serialNumber,
|
||||||
|
certificate,
|
||||||
|
certificateChain,
|
||||||
|
privateKey
|
||||||
|
});
|
||||||
|
|
||||||
createNotification({
|
createNotification({
|
||||||
text: "Successfully created certificate",
|
text: "Successfully created certificate",
|
||||||
@@ -122,7 +138,7 @@ export const CertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
|
|||||||
if (cas?.length) {
|
if (cas?.length) {
|
||||||
setValue("caId", cas[0].id);
|
setValue("caId", cas[0].id);
|
||||||
}
|
}
|
||||||
}, [cas, setValue]);
|
}, [cas]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
@@ -130,93 +146,107 @@ export const CertificateModal = ({ popUp, handlePopUpToggle }: Props) => {
|
|||||||
onOpenChange={(isOpen) => {
|
onOpenChange={(isOpen) => {
|
||||||
handlePopUpToggle("certificate", isOpen);
|
handlePopUpToggle("certificate", isOpen);
|
||||||
reset();
|
reset();
|
||||||
|
setCertificateDetails(null);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ModalContent title={`${cert ? "View" : "Issue"} Certificate`}>
|
<ModalContent title={`${cert ? "View" : "Issue"} Certificate`}>
|
||||||
<form onSubmit={handleSubmit(onFormSubmit)}>
|
{!certificateDetails ? (
|
||||||
<Controller
|
<form onSubmit={handleSubmit(onFormSubmit)}>
|
||||||
control={control}
|
<Controller
|
||||||
name="caId"
|
control={control}
|
||||||
defaultValue=""
|
name="caId"
|
||||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
defaultValue=""
|
||||||
<FormControl
|
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||||
label="Issuing CA"
|
<FormControl
|
||||||
errorText={error?.message}
|
label="Issuing CA"
|
||||||
isError={Boolean(error)}
|
errorText={error?.message}
|
||||||
className="mt-4"
|
isError={Boolean(error)}
|
||||||
isRequired
|
className="mt-4"
|
||||||
>
|
isRequired
|
||||||
<Select
|
|
||||||
defaultValue={field.value}
|
|
||||||
{...field}
|
|
||||||
onValueChange={(e) => onChange(e)}
|
|
||||||
className="w-full"
|
|
||||||
isDisabled={Boolean(cert)}
|
|
||||||
>
|
>
|
||||||
{(cas || []).map(({ id, type, dn }) => (
|
<Select
|
||||||
<SelectItem value={id} key={`ca-${id}`}>
|
defaultValue={field.value}
|
||||||
{`${caTypeToNameMap[type]}: ${dn}`}
|
{...field}
|
||||||
</SelectItem>
|
onValueChange={(e) => onChange(e)}
|
||||||
))}
|
className="w-full"
|
||||||
</Select>
|
isDisabled={Boolean(cert)}
|
||||||
</FormControl>
|
>
|
||||||
|
{(cas || []).map(({ id, type, dn }) => (
|
||||||
|
<SelectItem value={id} key={`ca-${id}`}>
|
||||||
|
{`${caTypeToNameMap[type]}: ${dn}`}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
defaultValue=""
|
||||||
|
name="commonName"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="Common Name (CN)"
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
isRequired
|
||||||
|
>
|
||||||
|
<Input {...field} placeholder="Acme Corp" isDisabled={Boolean(cert)} />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="ttl"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl
|
||||||
|
label="TTL (seconds)"
|
||||||
|
isError={Boolean(error)}
|
||||||
|
errorText={error?.message}
|
||||||
|
>
|
||||||
|
<Input {...field} placeholder="86400" isDisabled={Boolean(cert)} />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<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>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
{!cert && (
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Button
|
||||||
|
className="mr-4"
|
||||||
|
size="sm"
|
||||||
|
type="submit"
|
||||||
|
isLoading={isSubmitting}
|
||||||
|
isDisabled={isSubmitting}
|
||||||
|
>
|
||||||
|
Create
|
||||||
|
</Button>
|
||||||
|
<Button colorSchema="secondary" variant="plain">
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</form>
|
||||||
|
) : (
|
||||||
|
<CertificateContent
|
||||||
|
serialNumber={certificateDetails.serialNumber}
|
||||||
|
certificate={certificateDetails.certificate}
|
||||||
|
certificateChain={certificateDetails.certificateChain}
|
||||||
|
privateKey={certificateDetails.privateKey}
|
||||||
/>
|
/>
|
||||||
<Controller
|
)}
|
||||||
control={control}
|
|
||||||
defaultValue=""
|
|
||||||
name="commonName"
|
|
||||||
render={({ field, fieldState: { error } }) => (
|
|
||||||
<FormControl
|
|
||||||
label="Common Name (CN)"
|
|
||||||
isError={Boolean(error)}
|
|
||||||
errorText={error?.message}
|
|
||||||
isRequired
|
|
||||||
>
|
|
||||||
<Input {...field} placeholder="Acme Corp" isDisabled={Boolean(cert)} />
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
name="ttl"
|
|
||||||
render={({ field, fieldState: { error } }) => (
|
|
||||||
<FormControl
|
|
||||||
label="TTL (seconds)"
|
|
||||||
isError={Boolean(error)}
|
|
||||||
errorText={error?.message}
|
|
||||||
>
|
|
||||||
<Input {...field} placeholder="86400" isDisabled={Boolean(cert)} />
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<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>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
{!cert && (
|
|
||||||
<div className="flex items-center">
|
|
||||||
<Button
|
|
||||||
className="mr-4"
|
|
||||||
size="sm"
|
|
||||||
type="submit"
|
|
||||||
isLoading={isSubmitting}
|
|
||||||
isDisabled={isSubmitting}
|
|
||||||
>
|
|
||||||
Create
|
|
||||||
</Button>
|
|
||||||
<Button colorSchema="secondary" variant="plain">
|
|
||||||
Cancel
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</form>
|
|
||||||
</ModalContent>
|
</ModalContent>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user