From 549c41e53d0314dbc7a5a7d1d4c6e89ee17959d8 Mon Sep 17 00:00:00 2001 From: Gabriel Hernandez Date: Thu, 20 Mar 2025 19:10:48 +0000 Subject: [PATCH] fixes for various UI issues with cert authority feature (#27341) For #26606 various fixes to the UI for the cert authority feature - [x] Manual QA for all new/changed functionality --- .../AddCertAuthorityModal/_styles.scss | 1 - .../AddCertAuthorityModal/helpers.tsx | 70 ++++++- .../CustomSCEPForm/CustomSCEPForm.tsx | 26 ++- .../components/CustomSCEPForm/helpers.ts | 155 ++++++++-------- .../components/DigicertForm/DigicertForm.tsx | 24 ++- .../components/DigicertForm/helpers.ts | 174 ++++++++++-------- .../EditCertAuthorityModal.tsx | 5 +- .../EditCertAuthorityModal/_styles.scss | 3 - .../EditCertAuthorityModal/helpers.tsx | 15 +- .../components/NDESForm/NDESForm.tsx | 4 + .../components/NDESForm/helpers.ts | 32 +++- 11 files changed, 322 insertions(+), 187 deletions(-) delete mode 100644 frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/EditCertAuthorityModal/_styles.scss diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/AddCertAuthorityModal/_styles.scss b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/AddCertAuthorityModal/_styles.scss index 8457d0880e..f126bf1c2c 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/AddCertAuthorityModal/_styles.scss +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/AddCertAuthorityModal/_styles.scss @@ -1,5 +1,4 @@ .add-cert-authority-modal { - &__cert-authority-dropdown { margin-bottom: $pad-large; } diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/AddCertAuthorityModal/helpers.tsx b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/AddCertAuthorityModal/helpers.tsx index e241dee672..988c0c884e 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/AddCertAuthorityModal/helpers.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/AddCertAuthorityModal/helpers.tsx @@ -1,4 +1,8 @@ +import React from "react"; + +import CustomLink from "components/CustomLink"; import { IDropdownOption } from "interfaces/dropdownOption"; +import { getErrorReason } from "interfaces/errors"; const DEFAULT_CERT_AUTHORITY_OPTIONS: IDropdownOption[] = [ { label: "Digicert", value: "digicert" }, @@ -24,10 +28,66 @@ export const generateDropdownOptions = (hasNDESCert: boolean) => { return DEFAULT_CERT_AUTHORITY_OPTIONS; }; -const DEFAULT_ERROR_MESSAGE = - "Couldn't add certificate authority. Please try again."; +/** + * errors used in the add certificate authority flow + */ +const DEFAULT_ERROR = "Please try again."; +const INVALID_API_TOKEN_ERROR = + "Invalid API token. Please correct and try again."; +const INVALID_PROFILE_GUID_ERROR = + "Invalid profile GUID. Please correct and try again."; +const INVALID_URL_ERROR = "Invalid URL. Please correct and try again."; +const PRIVATE_KEY_NOT_CONFIGURED_ERROR = ( + <> + Private key must be configured.{" "} + + +); +const INVALID_SCEP_URL_ERROR = + "Invalid SCEP URL. Please correct and try again."; +const INVALID_ADMIN_URL_OR_CREDENTIALS_ERROR = + "Invalid admin URL or credentials. Please correct and try again."; +const NDES_PASSWORD_CACHE_FULL_ERROR = + "The NDES password cache is full. Please increase the number of cached passwords in NDES and try again."; +const INVALID_CHALLENGE_ERROR = + "Invalid challenge. Please correct and try again."; -// eslint-disable-next-line import/prefer-default-export -export const getErrorMessage = (e: unknown) => { - return DEFAULT_ERROR_MESSAGE; +/** + * Gets the error message we want to display from the api error message. + * This is used in both add and edit certificate authority flows. + */ +export const getDisplayErrMessage = (err: unknown) => { + let message: string | JSX.Element = DEFAULT_ERROR; + const reason = getErrorReason(err); + + if (reason.includes("invalid API token")) { + message = INVALID_API_TOKEN_ERROR; + } else if (reason.includes("invalid profile GUID")) { + message = INVALID_PROFILE_GUID_ERROR; + } else if (reason.includes("invalid URL")) { + message = INVALID_URL_ERROR; + } else if (reason.includes("private key")) { + message = PRIVATE_KEY_NOT_CONFIGURED_ERROR; + } else if (reason.includes("invalid SCEP URL")) { + message = INVALID_SCEP_URL_ERROR; + } else if (reason.includes("invalid admin URL or credentials")) { + message = INVALID_ADMIN_URL_OR_CREDENTIALS_ERROR; + } else if (reason.includes("password cache is full")) { + message = NDES_PASSWORD_CACHE_FULL_ERROR; + } else if (reason.includes("invalid challenge")) { + message = INVALID_CHALLENGE_ERROR; + } else { + message = DEFAULT_ERROR; + } + + return message; +}; + +export const getErrorMessage = (err: unknown) => { + return `Couldn't add certificate authority. ${getDisplayErrMessage(err)}`; }; diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/CustomSCEPForm/CustomSCEPForm.tsx b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/CustomSCEPForm/CustomSCEPForm.tsx index 9be5a4d90f..4fb92db9ff 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/CustomSCEPForm/CustomSCEPForm.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/CustomSCEPForm/CustomSCEPForm.tsx @@ -1,11 +1,17 @@ -import React, { useState } from "react"; +import React, { useContext, useMemo, useState } from "react"; + +import { AppContext } from "context/app"; // @ts-ignore import InputField from "components/forms/fields/InputField"; import Button from "components/buttons/Button"; import TooltipWrapper from "components/TooltipWrapper"; -import { ICustomSCEPFormValidation, validateFormData } from "./helpers"; +import { + generateFormValidations, + ICustomSCEPFormValidation, + validateFormData, +} from "./helpers"; const baseClass = "ndes-form"; @@ -19,6 +25,7 @@ interface ICustomSCEPFormProps { formData: ICustomSCEPFormData; submitBtnText: string; isSubmitting: boolean; + isEditing?: boolean; onChange: (update: { name: string; value: string }) => void; onSubmit: () => void; onCancel: () => void; @@ -28,10 +35,20 @@ const CustomSCEPForm = ({ formData, submitBtnText, isSubmitting, + isEditing = false, onChange, onSubmit, onCancel, }: ICustomSCEPFormProps) => { + const { config } = useContext(AppContext); + const validations = useMemo( + () => + generateFormValidations( + config?.integrations.custom_scep_proxy ?? [], + isEditing + ), + [config?.integrations.custom_scep_proxy] + ); const [ formValidation, setFormValidation, @@ -48,7 +65,10 @@ const CustomSCEPForm = ({ const onInputChange = (update: { name: string; value: string }) => { setFormValidation( - validateFormData({ ...formData, [update.name]: update.value }) + validateFormData( + { ...formData, [update.name]: update.value }, + validations + ) ); onChange(update); }; diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/CustomSCEPForm/helpers.ts b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/CustomSCEPForm/helpers.ts index 705ee3a192..8d86246c72 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/CustomSCEPForm/helpers.ts +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/CustomSCEPForm/helpers.ts @@ -1,3 +1,5 @@ +import { ICertificatesIntegrationCustomSCEP } from "interfaces/integration"; + import valid_url from "components/forms/validators/valid_url"; import { ICustomSCEPFormData } from "./CustomSCEPForm"; @@ -21,56 +23,76 @@ interface IValidation { message?: IValidationMessage; } -const FORM_VALIDATIONS: Record< +type IFormValidations = Record< IFormValidationKey, { validations: IValidation[] } -> = { - name: { - validations: [ - { - name: "required", - isValid: (formData: ICustomSCEPFormData) => { - return formData.name.length > 0; +>; + +export const generateFormValidations = ( + customSCEPIntegrations: ICertificatesIntegrationCustomSCEP[], + isEditing: boolean +) => { + const FORM_VALIDATIONS: IFormValidations = { + name: { + validations: [ + { + name: "required", + isValid: (formData: ICustomSCEPFormData) => { + return formData.name.length > 0; + }, }, - }, - { - name: "invalidCharacters", - isValid: (formData: ICustomSCEPFormData) => { - return /^[a-zA-Z0-9_]+$/.test(formData.name); + { + name: "invalidCharacters", + isValid: (formData: ICustomSCEPFormData) => { + return /^[a-zA-Z0-9_]+$/.test(formData.name); + }, + message: + "Invalid characters. Only letters, numbers and underscores allowed.", }, - message: - "Inalid characters. Only letters, numbers and underscores allowed.", - }, - ], - }, - scepURL: { - validations: [ - { - name: "required", - isValid: (formData: ICustomSCEPFormData) => { - return formData.scepURL.length > 0; + { + name: "unique", + isValid: (formData: ICustomSCEPFormData) => { + return ( + isEditing || + customSCEPIntegrations.find( + (cert) => cert.name === formData.name + ) === undefined + ); + }, + message: "Name is already used by another custom SCEP CA.", }, - }, - { - name: "validUrl", - isValid: (formData: ICustomSCEPFormData) => { - return valid_url({ url: formData.scepURL }); + ], + }, + scepURL: { + validations: [ + { + name: "required", + isValid: (formData: ICustomSCEPFormData) => { + return formData.scepURL.length > 0; + }, }, - message: (formData: ICustomSCEPFormData) => - `${formData.scepURL} is not a valid URL`, - }, - ], - }, - challenge: { - validations: [ - { - name: "required", - isValid: (formData: ICustomSCEPFormData) => { - return formData.challenge.length > 0; + { + name: "validUrl", + isValid: (formData: ICustomSCEPFormData) => { + return valid_url({ url: formData.scepURL }); + }, + message: "Must be a valid URL.", }, - }, - ], - }, + ], + }, + challenge: { + validations: [ + { + name: "required", + isValid: (formData: ICustomSCEPFormData) => { + return formData.challenge.length > 0; + }, + }, + ], + }, + }; + + return FORM_VALIDATIONS; }; const getErrorMessage = ( @@ -84,14 +106,17 @@ const getErrorMessage = ( }; // eslint-disable-next-line import/prefer-default-export -export const validateFormData = (formData: ICustomSCEPFormData) => { +export const validateFormData = ( + formData: ICustomSCEPFormData, + validationConfig: IFormValidations +) => { const formValidation: ICustomSCEPFormValidation = { isValid: true, }; - Object.keys(FORM_VALIDATIONS).forEach((key) => { - const objKey = key as keyof typeof FORM_VALIDATIONS; - const failedValidation = FORM_VALIDATIONS[objKey].validations.find( + Object.keys(validationConfig).forEach((key) => { + const objKey = key as keyof typeof validationConfig; + const failedValidation = validationConfig[objKey].validations.find( (validation) => !validation.isValid(formData) ); @@ -110,39 +135,3 @@ export const validateFormData = (formData: ICustomSCEPFormData) => { return formValidation; }; - -const BAD_SCEP_URL_ERROR = "Invalid SCEP URL. Please correct and try again."; -const BAD_CREDENTIALS_ERROR = - "Couldn't add. Admin URL or credentials are invalid."; -const CACHE_ERROR = - "The NDES password cache is full. Please increase the number of cached passwords in NDES and try again. By default, NDES caches 5 passwords and they expire 60 minutes after they are created."; -const INSUFFICIENT_PERMISSIONS_ERROR = - "Couldn't add. This account doesn't have sufficient permissions. Please use the account with enroll permission."; -const SCEP_URL_TIMEOUT_ERROR = - "Couldn't add. Request to NDES (SCEP URL) timed out. Please try again."; -const DEFAULT_ERROR = - "Something went wrong updating your SCEP server. Please try again."; - -// export const getErrorMessage = ( -// err: unknown, -// formData: ICustomSCEPFormData -// ) => { -// const reason = getErrorReason(err); - -// if (reason.includes("invalid admin URL or credentials")) { -// return BAD_CREDENTIALS_ERROR; -// } else if (reason.includes("the password cache is full")) { -// return CACHE_ERROR; -// } else if (reason.includes("does not have sufficient permissions")) { -// INSUFFICIENT_PERMISSIONS_ERROR; -// } else if ( -// reason.includes(formData.scepURL) && -// reason.includes("context deadline exceeded") -// ) { -// return SCEP_URL_TIMEOUT_ERROR; -// } else if (reason.includes("invalid SCEP URL")) { -// return BAD_SCEP_URL_ERROR; -// } - -// return DEFAULT_ERROR; -// }; diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DigicertForm/DigicertForm.tsx b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DigicertForm/DigicertForm.tsx index 9f30595060..59855b02c9 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DigicertForm/DigicertForm.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DigicertForm/DigicertForm.tsx @@ -1,11 +1,17 @@ -import React, { useState } from "react"; +import React, { useContext, useMemo, useState } from "react"; + +import { AppContext } from "context/app"; // @ts-ignore import InputField from "components/forms/fields/InputField"; import Button from "components/buttons/Button"; import CustomLink from "components/CustomLink"; import TooltipWrapper from "components/TooltipWrapper"; -import { validateFormData, IDigicertFormValidation } from "./helpers"; +import { + validateFormData, + IDigicertFormValidation, + generateFormValidations, +} from "./helpers"; const baseClass = "digicert-form"; @@ -23,6 +29,7 @@ interface IDigicertFormProps { formData: IDigicertFormData; submitBtnText: string; isSubmitting: boolean; + isEditing?: boolean; onChange: (update: { name: string; value: string }) => void; onSubmit: () => void; onCancel: () => void; @@ -32,10 +39,18 @@ const DigicertForm = ({ formData, submitBtnText, isSubmitting, + isEditing = false, onChange, onSubmit, onCancel, }: IDigicertFormProps) => { + const { config } = useContext(AppContext); + const validations = useMemo( + () => + generateFormValidations(config?.integrations.digicert ?? [], isEditing), + [config?.integrations.digicert, isEditing] + ); + const [formValidation, setFormValidation] = useState( { isValid: false, @@ -59,7 +74,10 @@ const DigicertForm = ({ const onInputChange = (update: { name: string; value: string }) => { setFormValidation( - validateFormData({ ...formData, [update.name]: update.value }) + validateFormData( + { ...formData, [update.name]: update.value }, + validations + ) ); onChange(update); }; diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DigicertForm/helpers.ts b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DigicertForm/helpers.ts index ddc946f18b..b1e6beae07 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DigicertForm/helpers.ts +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/DigicertForm/helpers.ts @@ -1,4 +1,7 @@ +import { ICertificatesIntegrationDigicert } from "interfaces/integration"; + import valid_url from "components/forms/validators/valid_url"; + import { IDigicertFormData } from "./DigicertForm"; // TODO: create a validator abstraction for this and the other form validation files @@ -23,86 +26,105 @@ interface IValidation { message?: IValidationMessage; } -const FORM_VALIDATIONS: Record< +type IFormValidations = Record< IFormValidationKey, { validations: IValidation[] } -> = { - name: { - validations: [ - { - name: "required", - isValid: (formData: IDigicertFormData) => { - return formData.name.length > 0; +>; + +export const generateFormValidations = ( + digicertIntegrations: ICertificatesIntegrationDigicert[], + isEditing: boolean +) => { + const FORM_VALIDATIONS: IFormValidations = { + name: { + validations: [ + { + name: "required", + isValid: (formData: IDigicertFormData) => { + return formData.name.length > 0; + }, }, - }, - { - name: "invalidCharacters", - isValid: (formData: IDigicertFormData) => { - return /^[a-zA-Z0-9_]+$/.test(formData.name); + { + name: "invalidCharacters", + isValid: (formData: IDigicertFormData) => { + return /^[a-zA-Z0-9_]+$/.test(formData.name); + }, + message: + "Invalid characters. Only letters, numbers and underscores allowed.", }, - message: - "Inalid characters. Only letters, numbers and underscores allowed.", - }, - ], - }, - url: { - validations: [ - { - name: "required", - isValid: (formData: IDigicertFormData) => { - return formData.url.length > 0; + { + name: "unique", + isValid: (formData: IDigicertFormData) => { + return ( + isEditing || + digicertIntegrations.find( + (cert) => cert.name === formData.name + ) === undefined + ); + }, + message: "Name is already used by another DigiCert CA.", }, - }, - { - name: "validUrl", - isValid: (formData: IDigicertFormData) => { - return valid_url({ url: formData.url }); + ], + }, + url: { + validations: [ + { + name: "required", + isValid: (formData: IDigicertFormData) => { + return formData.url.length > 0; + }, }, - message: (formData: IDigicertFormData) => - `${formData.url} is not a valid URL`, - }, - ], - }, - apiToken: { - validations: [ - { - name: "required", - isValid: (formData: IDigicertFormData) => { - return formData.apiToken.length > 0; + { + name: "validUrl", + isValid: (formData: IDigicertFormData) => { + return valid_url({ url: formData.url }); + }, + message: "Must be a valid URL.", }, - }, - ], - }, - profileId: { - validations: [ - { - name: "required", - isValid: (formData: IDigicertFormData) => { - return formData.profileId.length > 0; + ], + }, + apiToken: { + validations: [ + { + name: "required", + isValid: (formData: IDigicertFormData) => { + return formData.apiToken.length > 0; + }, }, - }, - ], - }, - commonName: { - validations: [ - { - name: "required", - isValid: (formData: IDigicertFormData) => { - return formData.commonName.length > 0; + ], + }, + profileId: { + validations: [ + { + name: "required", + isValid: (formData: IDigicertFormData) => { + return formData.profileId.length > 0; + }, }, - }, - ], - }, - certificateSeatId: { - validations: [ - { - name: "required", - isValid: (formData: IDigicertFormData) => { - return formData.certificateSeatId.length > 0; + ], + }, + commonName: { + validations: [ + { + name: "required", + isValid: (formData: IDigicertFormData) => { + return formData.commonName.length > 0; + }, }, - }, - ], - }, + ], + }, + certificateSeatId: { + validations: [ + { + name: "required", + isValid: (formData: IDigicertFormData) => { + return formData.certificateSeatId.length > 0; + }, + }, + ], + }, + }; + return FORM_VALIDATIONS; }; const getErrorMessage = ( @@ -115,15 +137,17 @@ const getErrorMessage = ( return message(formData); }; -// eslint-disable-next-line import/prefer-default-export -export const validateFormData = (formData: IDigicertFormData) => { +export const validateFormData = ( + formData: IDigicertFormData, + validationConfig: IFormValidations +) => { const formValidation: IDigicertFormValidation = { isValid: true, }; - Object.keys(FORM_VALIDATIONS).forEach((key) => { - const objKey = key as keyof typeof FORM_VALIDATIONS; - const failedValidation = FORM_VALIDATIONS[objKey].validations.find( + Object.keys(validationConfig).forEach((key) => { + const objKey = key as keyof typeof validationConfig; + const failedValidation = validationConfig[objKey].validations.find( (validation) => !validation.isValid(formData) ); diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/EditCertAuthorityModal/EditCertAuthorityModal.tsx b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/EditCertAuthorityModal/EditCertAuthorityModal.tsx index 1ac6919d36..0bfb60d79b 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/EditCertAuthorityModal/EditCertAuthorityModal.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/EditCertAuthorityModal/EditCertAuthorityModal.tsx @@ -13,7 +13,7 @@ import Modal from "components/Modal"; import { generateDefaultFormData, - generateErrorMessage, + getErrorMessage, getCertificateAuthorityType, } from "./helpers"; @@ -68,7 +68,7 @@ const EditCertAuthorityModal = ({ onExit(); setConfig(newConfig); } catch (e) { - renderFlash("error", generateErrorMessage(e)); + renderFlash("error", getErrorMessage(e)); } setIsUpdating(false); }; @@ -93,6 +93,7 @@ const EditCertAuthorityModal = ({ formData={formData} submitBtnText="Save" isSubmitting={isUpdating} + isEditing onChange={onChangeForm} onSubmit={onEditCertAuthority} onCancel={onExit} diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/EditCertAuthorityModal/_styles.scss b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/EditCertAuthorityModal/_styles.scss deleted file mode 100644 index ff608def43..0000000000 --- a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/EditCertAuthorityModal/_styles.scss +++ /dev/null @@ -1,3 +0,0 @@ -.edit-cert-authority-modal { - -} diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/EditCertAuthorityModal/helpers.tsx b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/EditCertAuthorityModal/helpers.tsx index 8d52de3276..44336fb228 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/EditCertAuthorityModal/helpers.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/EditCertAuthorityModal/helpers.tsx @@ -2,22 +2,13 @@ import { ICertificateAuthorityType, ICertificateIntegration, ICertificatesIntegrationCustomSCEP, - ICertificatesIntegrationDigicert, - ICertificatesIntegrationNDES, isCustomSCEPCertIntegration, isDigicertCertIntegration, isNDESCertIntegration, } from "interfaces/integration"; import { ICertFormData } from "../AddCertAuthorityModal/AddCertAuthorityModal"; - -const DEFAULT_ERROR_MESSAGE = - "Couldn't edit certificate authority. Please try again."; - -// eslint-disable-next-line import/prefer-default-export -export const generateErrorMessage = (e: unknown) => { - return DEFAULT_ERROR_MESSAGE; -}; +import { getDisplayErrMessage } from "../AddCertAuthorityModal/helpers"; export const getCertificateAuthorityType = ( certAuthority: ICertificateIntegration @@ -56,3 +47,7 @@ export const generateDefaultFormData = ( challenge: customSCEPcert.challenge, }; }; + +export const getErrorMessage = (err: unknown) => { + return `Couldn't edit certificate authority. ${getDisplayErrMessage(err)}`; +}; diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/NDESForm/NDESForm.tsx b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/NDESForm/NDESForm.tsx index 892dcf3b42..311d3daf6a 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/NDESForm/NDESForm.tsx +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/NDESForm/NDESForm.tsx @@ -20,6 +20,7 @@ interface INDESFormProps { formData: INDESFormData; submitBtnText: string; isSubmitting: boolean; + isEditing?: boolean; onChange: (update: { name: string; value: string }) => void; onSubmit: () => void; onCancel: () => void; @@ -29,6 +30,7 @@ const NDESForm = ({ formData, submitBtnText, isSubmitting, + isEditing = false, onChange, onSubmit, onCancel, @@ -58,6 +60,7 @@ const NDESForm = ({ label="SCEP URL" name="scepURL" value={scepURL} + error={formValidation.scepURL?.message} onChange={onInputChange} parseTarget placeholder="https://example.com/certsrv/mscep/mscep.dll" @@ -67,6 +70,7 @@ const NDESForm = ({ label="Admin URL" name="adminURL" value={adminURL} + error={formValidation.adminURL?.message} onChange={onInputChange} parseTarget placeholder="https://example.com/certsrv/mscep_admin/" diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/NDESForm/helpers.ts b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/NDESForm/helpers.ts index 3af2e45906..b3f3c61052 100644 --- a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/NDESForm/helpers.ts +++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/NDESForm/helpers.ts @@ -1,12 +1,15 @@ import { getErrorReason } from "interfaces/errors"; + +import valid_url from "components/forms/validators/valid_url"; + import { INDESFormData } from "./NDESForm"; // TODO: create a validator abstraction for this and the other form validation files export interface INDESFormValidation { isValid: boolean; - scepURL?: { isValid: boolean }; - adminURL?: { isValid: boolean }; + scepURL?: { isValid: boolean; message?: string }; + adminURL?: { isValid: boolean; message?: string }; username?: { isValid: boolean }; password?: { isValid: boolean }; } @@ -33,6 +36,13 @@ const FORM_VALIDATIONS: Record< return formData.scepURL.length > 0; }, }, + { + name: "validUrl", + isValid: (formData: INDESFormData) => { + return valid_url({ url: formData.scepURL }); + }, + message: "Must be a valid URL.", + }, ], }, adminURL: { @@ -43,6 +53,13 @@ const FORM_VALIDATIONS: Record< return formData.adminURL.length > 0; }, }, + { + name: "validUrl", + isValid: (formData: INDESFormData) => { + return valid_url({ url: formData.adminURL }); + }, + message: "Must be a valid URL", + }, ], }, username: { @@ -67,6 +84,16 @@ const FORM_VALIDATIONS: Record< }, }; +const getValifationErrorMessage = ( + formData: INDESFormData, + message?: IValidationMessage +) => { + if (message === undefined || typeof message === "string") { + return message; + } + return message(formData); +}; + // eslint-disable-next-line import/prefer-default-export export const validateFormData = (formData: INDESFormData) => { const formValidation: INDESFormValidation = { @@ -87,6 +114,7 @@ export const validateFormData = (formData: INDESFormData) => { formValidation.isValid = false; formValidation[objKey] = { isValid: false, + message: getValifationErrorMessage(formData, failedValidation.message), }; } });