From ed21b1b7c1ca28e92ae412882b78fc4ae43db8c7 Mon Sep 17 00:00:00 2001 From: alex-slobodian Date: Mon, 3 Feb 2025 19:56:53 +0200 Subject: [PATCH 1/2] Add CSR support to certificates list --- package.json | 4 +- src/app.test.tsx | 51 ++++++++++++++++++- .../CertificateSerialNumber.test.tsx | 4 +- .../CertificateSerialNumber.tsx | 10 +++- .../CertificateTypeLabel.tsx | 51 +++++++++++++++---- src/components/date/Date.test.tsx | 4 +- src/components/date/Date.tsx | 15 +++--- src/hooks/app/useApp.test.tsx | 1 + src/hooks/app/useApp.tsx | 23 ++++++--- src/hooks/search-list/useSearchList.tsx | 7 ++- src/i18n/locales/en/main.json | 3 +- src/icons/certificate-request-30.svg | 8 +++ yarn.lock | 8 +-- 13 files changed, 148 insertions(+), 41 deletions(-) create mode 100644 src/icons/certificate-request-30.svg diff --git a/package.json b/package.json index a23ac0f2..886b183c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@peculiar/fortify-tools", "homepage": "https://tools.fortifyapp.com", - "version": "2.0.15", + "version": "2.0.16", "author": "PeculiarVentures Team", "license": "MIT", "private": true, @@ -43,7 +43,7 @@ }, "dependencies": { "@peculiar/certificates-viewer-react": "^4.3.2", - "@peculiar/fortify-client-core": "^4.1.0", + "@peculiar/fortify-client-core": "^4.1.1", "@peculiar/react-components": "^1.1.2", "@peculiar/x509": "^1.12.3", "clsx": "^2.1.1", diff --git a/src/app.test.tsx b/src/app.test.tsx index 9f0ab484..8c0e7c5b 100644 --- a/src/app.test.tsx +++ b/src/app.test.tsx @@ -49,6 +49,18 @@ describe("", () => { }, ]; + const certificateRequestsMock = [ + { + id: "1", + providerID: "provider1", + subject: { + CN: ["Certificate request test 1"], + }, + raw: new ArrayBuffer(1), + type: "request", + }, + ]; + const mockFortifyAPIInstance: Partial = { challenge: vi.fn().mockResolvedValue(""), login: vi.fn(), @@ -65,11 +77,19 @@ describe("", () => { logout: vi.fn(), }), getCertificatesByProviderId: vi.fn().mockResolvedValue(certificatesMock), + getCertificateRequestsByProviderId: vi.fn().mockResolvedValue([]), }; - it("Should render, show providers & certificates", async () => { + const mockFortifyAPIInstanceWithCertificateRequests = { + ...mockFortifyAPIInstance, + getCertificateRequestsByProviderId: vi + .fn() + .mockResolvedValue(certificateRequestsMock), + } as unknown as FortifyAPI; + + it("Should render, show providers & certificates & certificate requests", async () => { vi.mocked(FortifyAPI).mockImplementation( - () => mockFortifyAPIInstance as FortifyAPI + () => mockFortifyAPIInstanceWithCertificateRequests ); render(); @@ -79,6 +99,9 @@ describe("", () => { expect(screen.getByText(/Provider 2/)).toBeInTheDocument(); expect(screen.getByText(/Certificate test 1/)).toBeInTheDocument(); expect(screen.getByText(/Certificate test 2/)).toBeInTheDocument(); + expect( + screen.getByText(/Certificate request test 1/) + ).toBeInTheDocument(); }); }); @@ -143,6 +166,30 @@ describe("", () => { }); }); + it("Should open view certificate request details dialog", async () => { + vi.mocked(FortifyAPI).mockImplementation( + () => mockFortifyAPIInstanceWithCertificateRequests as FortifyAPI + ); + + render(); + + await waitFor(() => { + expect( + screen.getByText(/Certificate request test 1/) + ).toBeInTheDocument(); + }); + + await userEvent.click( + screen.getAllByRole("button", { name: /View details/ })[0] + ); + + await waitFor(() => { + expect( + screen.getByText(/“Certificate request test 1” details/) + ).toBeInTheDocument(); + }); + }); + it("Should open import certificate dialog", async () => { vi.mocked(FortifyAPI).mockImplementation( () => mockFortifyAPIInstance as FortifyAPI diff --git a/src/components/certificate-serial-number/CertificateSerialNumber.test.tsx b/src/components/certificate-serial-number/CertificateSerialNumber.test.tsx index 34527c61..a0ae5c9a 100644 --- a/src/components/certificate-serial-number/CertificateSerialNumber.test.tsx +++ b/src/components/certificate-serial-number/CertificateSerialNumber.test.tsx @@ -23,7 +23,7 @@ describe("", () => { }); it("Shouldn't render if no value", () => { - const { container } = render(); - expect(container.firstChild).toBeNull(); + render(); + expect(screen.getByText(/-/)).toBeInTheDocument(); }); }); diff --git a/src/components/certificate-serial-number/CertificateSerialNumber.tsx b/src/components/certificate-serial-number/CertificateSerialNumber.tsx index 599b8b80..6eb6c32e 100644 --- a/src/components/certificate-serial-number/CertificateSerialNumber.tsx +++ b/src/components/certificate-serial-number/CertificateSerialNumber.tsx @@ -15,7 +15,15 @@ export const CertificateSerialNumber: React.FunctionComponent< > = (props) => { const { className, value } = props; if (!value) { - return null; + return ( + + - + + ); } return ( diff --git a/src/components/certificate-type-label/CertificateTypeLabel.tsx b/src/components/certificate-type-label/CertificateTypeLabel.tsx index 3c906f3b..ee4aeea2 100644 --- a/src/components/certificate-type-label/CertificateTypeLabel.tsx +++ b/src/components/certificate-type-label/CertificateTypeLabel.tsx @@ -1,14 +1,18 @@ import React, { ComponentProps } from "react"; -import { ICertificate } from "@peculiar/fortify-client-core"; +import { + ICertificate, + ICertificateRequest, +} from "@peculiar/fortify-client-core"; import { useTranslation } from "react-i18next"; import clsx from "clsx"; import { Typography } from "@peculiar/react-components"; import CertificateIcon from "../../icons/certificate-30.svg?react"; import CertificateWithKeyIcon from "../../icons/certificate-with-key-30.svg?react"; +import CertificateRequestIcon from "../../icons/certificate-request-30.svg?react"; import styles from "./styles/index.module.scss"; interface CertificateTypeLabelProps { - type: ICertificate["type"]; + type: ICertificate["type"] | ICertificateRequest["type"]; withPrivatKey: boolean; className?: ComponentProps<"div">["className"]; } @@ -19,9 +23,9 @@ export const CertificateTypeLabel: React.FunctionComponent< const { type, className, withPrivatKey } = props; const { t } = useTranslation(); - return ( -
- {type === "x509" ? ( + const renderType = () => { + if (type === "x509") { + return ( <> {withPrivatKey ? : } @@ -46,11 +50,38 @@ export const CertificateTypeLabel: React.FunctionComponent< ) : undefined} - ) : ( - - {type} - - )} + ); + } + + if (type === "request") { + return ( + <> + + + + + + {t("certificates.list.cell.certificate-request")} + + + + ); + } + + return ( + + {type} + + ); + }; + + return ( +
+ {renderType()}
); }; diff --git a/src/components/date/Date.test.tsx b/src/components/date/Date.test.tsx index 877c34b0..1b7fbfd5 100644 --- a/src/components/date/Date.test.tsx +++ b/src/components/date/Date.test.tsx @@ -17,7 +17,7 @@ describe("", () => { }); it("Shouldn't render if no date", () => { - const { container } = render(); - expect(container.firstChild).toBeNull(); + render(); + expect(screen.getByText(/-/)).toBeInTheDocument(); }); }); diff --git a/src/components/date/Date.tsx b/src/components/date/Date.tsx index be402ddd..12a309ed 100644 --- a/src/components/date/Date.tsx +++ b/src/components/date/Date.tsx @@ -12,20 +12,19 @@ export const Date: React.FunctionComponent = (props) => { const { date, className } = props; const { i18n } = useTranslation(); - if (!date) { - return null; - } return ( - {date.toLocaleDateString(i18n.resolvedLanguage, { - day: "numeric", - month: "short", - year: "numeric", - })} + {date + ? date.toLocaleDateString(i18n.resolvedLanguage, { + day: "numeric", + month: "short", + year: "numeric", + }) + : "-"} ); }; diff --git a/src/hooks/app/useApp.test.tsx b/src/hooks/app/useApp.test.tsx index b465c3a0..6bcd44c8 100644 --- a/src/hooks/app/useApp.test.tsx +++ b/src/hooks/app/useApp.test.tsx @@ -47,6 +47,7 @@ describe("useApp", () => { logout: vi.fn(), }), getCertificatesByProviderId: vi.fn().mockResolvedValue(certificatesMock), + getCertificateRequestsByProviderId: vi.fn().mockResolvedValue([]), }; it("Should initialize, get providers & certificates", async () => { diff --git a/src/hooks/app/useApp.tsx b/src/hooks/app/useApp.tsx index f9337600..74cdf83e 100644 --- a/src/hooks/app/useApp.tsx +++ b/src/hooks/app/useApp.tsx @@ -3,6 +3,7 @@ import { FortifyAPI, IProviderInfo, ICertificate, + ICertificateRequest, } from "@peculiar/fortify-client-core"; import { useTranslation } from "react-i18next"; import { useToast } from "@peculiar/react-components"; @@ -21,7 +22,9 @@ export function useApp() { >(undefined); const [isCurrentProviderLogedin, setIsCurrentProviderLogedin] = React.useState(false); - const [certificates, setCertificates] = React.useState([]); + const [certificates, setCertificates] = React.useState< + (ICertificate | ICertificateRequest)[] + >([]); const [challenge, setChallenge] = React.useState(null); const [fetching, setFetching] = React.useState({ connectionDetect: "pending", @@ -184,9 +187,11 @@ export function useApp() { } try { - setCertificates( - await fortifyClient.current.getCertificatesByProviderId(id) - ); + const requests = + await fortifyClient.current.getCertificateRequestsByProviderId(id); + const certificates = + await fortifyClient.current.getCertificatesByProviderId(id); + setCertificates([...certificates, ...requests]); if (providers?.length) { setCurrentProvider(providers.find((provider) => provider.id === id)); } @@ -219,9 +224,13 @@ export function useApp() { setFetchingValue("certificates", "pending"); try { - setCertificates( - await fortifyClient.current.getCertificatesByProviderId(providerId) - ); + const requests = + await fortifyClient.current.getCertificateRequestsByProviderId( + providerId + ); + const certificates = + await fortifyClient.current.getCertificatesByProviderId(providerId); + setCertificates([...certificates, ...requests]); setFetchingValue("certificates", "resolved"); } catch (error) { setFetchingValue("certificates", "rejected"); diff --git a/src/hooks/search-list/useSearchList.tsx b/src/hooks/search-list/useSearchList.tsx index 8b00ef7f..1865e7b2 100644 --- a/src/hooks/search-list/useSearchList.tsx +++ b/src/hooks/search-list/useSearchList.tsx @@ -1,8 +1,11 @@ import { useEffect, useMemo, useState } from "react"; +import { ICertificateRequest } from "@peculiar/fortify-client-core"; import { getCertificateName } from "../../utils/certificate"; import { CertificateProps } from "../../types"; -export function useSearchList(certificates: CertificateProps[]) { +export function useSearchList( + certificates: (CertificateProps | ICertificateRequest)[] +) { const [searchedText, setSearchedText] = useState( new URLSearchParams(window.location.search).get("search") || "" ); @@ -37,7 +40,7 @@ export function useSearchList(certificates: CertificateProps[]) { () => searchedText ? certificates.filter((certificate) => - getCertificateName(certificate) + getCertificateName(certificate as CertificateProps) ?.toLocaleLowerCase() .includes(searchedText.toLocaleLowerCase()) ) diff --git a/src/i18n/locales/en/main.json b/src/i18n/locales/en/main.json index eebce44e..838c7cdb 100644 --- a/src/i18n/locales/en/main.json +++ b/src/i18n/locales/en/main.json @@ -47,7 +47,8 @@ }, "cell": { "certificate": "Certificate", - "with-privat-key": "(with private key)" + "with-privat-key": "(with private key)", + "certificate-request": "CSR" }, "action": { "view-details": "View details", diff --git a/src/icons/certificate-request-30.svg b/src/icons/certificate-request-30.svg new file mode 100644 index 00000000..d7bb01f3 --- /dev/null +++ b/src/icons/certificate-request-30.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/yarn.lock b/yarn.lock index d823a091..28e11588 100644 --- a/yarn.lock +++ b/yarn.lock @@ -963,10 +963,10 @@ resolved "https://registry.yarnpkg.com/@peculiar/color/-/color-0.1.5.tgz#ca90086f79da5a8c1c3e4e219ea627b65e4d3357" integrity sha512-nDg5qS9TU8ZTGLuWNZ1iTziAaK2hZytkjuUjIiJRfHWx65u5iyejjNeXkoe0/emAjPnX29FSI59WcwHMPZVsEw== -"@peculiar/fortify-client-core@^4.1.0": - version "4.1.0" - resolved "https://registry.yarnpkg.com/@peculiar/fortify-client-core/-/fortify-client-core-4.1.0.tgz#fa7d9d361affd48049838065ed01f27e46d746f2" - integrity sha512-a1vu08JFQC+DnV2R4mda+cpKYjTPxSwpbMW483Ay9bjdTism7KlI39V3y5FzQ7jAJpXwifIZhF1g7l55c2oSKg== +"@peculiar/fortify-client-core@^4.1.1": + version "4.1.1" + resolved "https://registry.yarnpkg.com/@peculiar/fortify-client-core/-/fortify-client-core-4.1.1.tgz#f08151245db56deb15351b5f95a75f10a3d60a30" + integrity sha512-eKaZhg+MZsxg0GW5spjHIdNtuzN9OICk7ZKXOX1uIEKVtk81mr/VVN2g8Axs/D0BYZqFc7KcJgK/m14EjU8sGQ== dependencies: "@peculiar/asn1-schema" "^2.3.13" "@peculiar/asn1-x509" "^2.3.13" From 7a598258bb4b9df1ed27256bdc69d431a17ec197 Mon Sep 17 00:00:00 2001 From: alex-slobodian Date: Mon, 3 Feb 2025 20:21:30 +0200 Subject: [PATCH 2/2] Fix certificate request copy & download --- .../certificates-list/CertificatesList.tsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/components/certificates-list/CertificatesList.tsx b/src/components/certificates-list/CertificatesList.tsx index a77a85d3..10567318 100644 --- a/src/components/certificates-list/CertificatesList.tsx +++ b/src/components/certificates-list/CertificatesList.tsx @@ -239,7 +239,11 @@ export const CertificatesList: React.FunctionComponent< title={t("certificates.list.action.copy")} value={ raw.byteLength - ? () => certificateRawToPem(raw, type) + ? () => + certificateRawToPem( + raw, + type === "x509" ? "x509" : "csr" + ) : "" } className={styles.action_icon_button} @@ -249,7 +253,11 @@ export const CertificatesList: React.FunctionComponent< title={t("certificates.list.action.download")} onClick={() => raw.byteLength && - downloadCertificate(certificateName, raw, type) + downloadCertificate( + certificateName, + raw, + type === "x509" ? "x509" : "csr" + ) } size="small" className={styles.action_icon_button}