) => {
+ evt.preventDefault();
+ onSubmit();
+ };
+
+ return (
+
+ );
+};
+
+export default CustomESTForm;
diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/CustomESTForm/helpers.ts b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/CustomESTForm/helpers.ts
new file mode 100644
index 0000000000..05e328bab7
--- /dev/null
+++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/CustomESTForm/helpers.ts
@@ -0,0 +1,148 @@
+import { ICertificateAuthorityPartial } from "interfaces/certificates";
+
+import valid_url from "components/forms/validators/valid_url";
+
+import { ICustomESTFormData } from "./CustomESTForm";
+
+export interface ICustomESTFormValidation {
+ isValid: boolean;
+ name?: { isValid: boolean; message?: string };
+ url?: { isValid: boolean; message?: string };
+ username?: { isValid: boolean; message?: string };
+ password?: { isValid: boolean; message?: string };
+}
+
+type IMessageFunc = (formData: ICustomESTFormData) => string;
+type IValidationMessage = string | IMessageFunc;
+type IFormValidationKey = keyof Omit;
+
+interface IValidation {
+ name: string;
+ isValid: (formData: ICustomESTFormData) => boolean;
+ message?: IValidationMessage;
+}
+
+type IFormValidations = Record<
+ IFormValidationKey,
+ { validations: IValidation[] }
+>;
+
+export const generateFormValidations = (
+ customESTIntegrations: ICertificateAuthorityPartial[],
+ isEditing: boolean
+) => {
+ const FORM_VALIDATIONS: IFormValidations = {
+ name: {
+ validations: [
+ {
+ name: "required",
+ isValid: (formData: ICustomESTFormData) => {
+ return formData.name.length > 0;
+ },
+ },
+ {
+ name: "invalidCharacters",
+ isValid: (formData: ICustomESTFormData) => {
+ return /^[a-zA-Z0-9_]+$/.test(formData.name);
+ },
+ message:
+ "Invalid characters. Only letters, numbers and underscores allowed.",
+ },
+ {
+ name: "unique",
+ isValid: (formData: ICustomESTFormData) => {
+ return (
+ isEditing ||
+ customESTIntegrations.find(
+ (cert) =>
+ cert.name === formData.name &&
+ cert.type === "custom_est_proxy"
+ ) === undefined
+ );
+ },
+ message: "Name is already used by another custom EST CA.",
+ },
+ ],
+ },
+ url: {
+ validations: [
+ {
+ name: "required",
+ isValid: (formData: ICustomESTFormData) => {
+ return formData.url.length > 0;
+ },
+ },
+ {
+ name: "validUrl",
+ isValid: (formData: ICustomESTFormData) => {
+ return valid_url({ url: formData.url });
+ },
+ message: "Must be a valid URL.",
+ },
+ ],
+ },
+ username: {
+ validations: [
+ {
+ name: "required",
+ isValid: (formData: ICustomESTFormData) => {
+ return formData.username.length > 0;
+ },
+ },
+ ],
+ },
+ password: {
+ validations: [
+ {
+ name: "required",
+ isValid: (formData: ICustomESTFormData) => {
+ return formData.password.length > 0;
+ },
+ },
+ ],
+ },
+ };
+
+ return FORM_VALIDATIONS;
+};
+
+const getErrorMessage = (
+ formData: ICustomESTFormData,
+ 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: ICustomESTFormData,
+ validationConfig: IFormValidations
+) => {
+ const formValidation: ICustomESTFormValidation = {
+ isValid: true,
+ };
+
+ Object.keys(validationConfig).forEach((key) => {
+ const objKey = key as keyof typeof validationConfig;
+ const failedValidation = validationConfig[objKey].validations.find(
+ (validation) => !validation.isValid(formData)
+ );
+
+ if (!failedValidation) {
+ formValidation[objKey] = {
+ isValid: true,
+ };
+ } else {
+ formValidation.isValid = false;
+ formValidation[objKey] = {
+ isValid: false,
+ message: getErrorMessage(formData, failedValidation.message),
+ };
+ }
+ });
+
+ return formValidation;
+};
diff --git a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/CustomESTForm/index.ts b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/CustomESTForm/index.ts
new file mode 100644
index 0000000000..a0d2892d7c
--- /dev/null
+++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/CustomESTForm/index.ts
@@ -0,0 +1 @@
+export { default } from "./CustomESTForm";
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 f5a112619c..4e5ee3ebf8 100644
--- a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/EditCertAuthorityModal/EditCertAuthorityModal.tsx
+++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/EditCertAuthorityModal/EditCertAuthorityModal.tsx
@@ -23,6 +23,7 @@ import NDESForm from "../NDESForm";
import CustomSCEPForm from "../CustomSCEPForm";
import HydrantForm from "../HydrantForm";
import SmallstepForm from "../SmallstepForm";
+import CustomESTForm from "../CustomESTForm";
const baseClass = "edit-cert-authority-modal";
@@ -75,7 +76,7 @@ const EditCertAuthorityModal = ({
certAuthority.id,
editPatchData
);
- renderFlash("success", "Successfully edited your certificate authority.");
+ renderFlash("success", "Successfully edited certificate authority.");
onExit();
} catch (e) {
renderFlash("error", getErrorMessage(e));
@@ -84,26 +85,24 @@ const EditCertAuthorityModal = ({
};
const getFormComponent = () => {
- if (certAuthority.type === "ndes_scep_proxy") {
- return NDESForm;
+ switch (certAuthority.type) {
+ case "ndes_scep_proxy":
+ return NDESForm;
+ case "digicert":
+ return DigicertForm;
+ case "hydrant":
+ return HydrantForm;
+ case "smallstep":
+ return SmallstepForm;
+ case "custom_scep_proxy":
+ return CustomSCEPForm;
+ case "custom_est_proxy":
+ return CustomESTForm;
+ default:
+ throw new Error(
+ `Unknown certificate authority type: ${certAuthority.type}`
+ );
}
- if (certAuthority.type === "digicert") {
- return DigicertForm;
- }
- if (certAuthority.type === "hydrant") {
- return HydrantForm;
- }
- if (certAuthority.type === "smallstep") {
- return SmallstepForm;
- }
-
- // FIXME: seems like we have some competing patterns in here where we sometimes do switch
- // statements with a default and sometimes do if or if/else if with a final default return. We
- // should probably standardize on one or the other. Also, do we really want this to be the
- // default? Why not have an explicit check for custom_scep_proxy and have the final
- // else throw an error?
-
- return CustomSCEPForm;
};
const renderForm = () => {
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 b76497eccc..fabf974e91 100644
--- a/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/EditCertAuthorityModal/helpers.tsx
+++ b/frontend/pages/admin/IntegrationsPage/cards/CertificateAuthorities/components/EditCertAuthorityModal/helpers.tsx
@@ -14,57 +14,67 @@ import { INDESFormData } from "../NDESForm/NDESForm";
import { ICustomSCEPFormData } from "../CustomSCEPForm/CustomSCEPForm";
import { IHydrantFormData } from "../HydrantForm/HydrantForm";
import { ISmallstepFormData } from "../SmallstepForm/SmallstepForm";
+import { ICustomESTFormData } from "../CustomESTForm/CustomESTForm";
+
+const UNCHANGED_PASSWORD_API_RESPONSE = "********";
export const generateDefaultFormData = (
certAuthority: ICertificateAuthority
): ICertFormData => {
- if (certAuthority.type === "ndes_scep_proxy") {
- return {
- scepURL: certAuthority.url,
- adminURL: certAuthority.admin_url,
- username: certAuthority.username,
- password: certAuthority.password,
- };
- } else if (certAuthority.type === "digicert") {
- return {
- name: certAuthority.name,
- url: certAuthority.url,
- apiToken: certAuthority.api_token,
- profileId: certAuthority.profile_id,
- commonName: certAuthority.certificate_common_name,
- userPrincipalName:
- certAuthority.certificate_user_principal_names?.[0] ?? "",
- certificateSeatId: certAuthority.certificate_seat_id,
- };
- } else if (certAuthority.type === "hydrant") {
- return {
- name: certAuthority.name,
- url: certAuthority.url,
- clientId: certAuthority.client_id,
- clientSecret: certAuthority.client_secret,
- };
- } else if (certAuthority.type === "smallstep") {
- return {
- name: certAuthority.name,
- scepURL: certAuthority.url,
- challengeURL: certAuthority.challenge_url,
- username: certAuthority.username,
- password: certAuthority.password,
- };
+ switch (certAuthority.type) {
+ case "ndes_scep_proxy":
+ return {
+ scepURL: certAuthority.url,
+ adminURL: certAuthority.admin_url,
+ username: certAuthority.username,
+ password: certAuthority.password,
+ };
+ case "digicert":
+ return {
+ name: certAuthority.name,
+ url: certAuthority.url,
+ apiToken: certAuthority.api_token,
+ profileId: certAuthority.profile_id,
+ commonName: certAuthority.certificate_common_name,
+ userPrincipalName:
+ certAuthority.certificate_user_principal_names?.[0] ?? "",
+ certificateSeatId: certAuthority.certificate_seat_id,
+ };
+ case "hydrant":
+ return {
+ name: certAuthority.name,
+ url: certAuthority.url,
+ clientId: certAuthority.client_id,
+ clientSecret: certAuthority.client_secret,
+ };
+ case "smallstep":
+ return {
+ name: certAuthority.name,
+ scepURL: certAuthority.url,
+ challengeURL: certAuthority.challenge_url,
+ username: certAuthority.username,
+ password: certAuthority.password,
+ };
+ case "custom_scep_proxy": {
+ const customSCEPcert = certAuthority as ICertificatesCustomSCEP;
+ return {
+ name: customSCEPcert.name,
+ scepURL: customSCEPcert.url,
+ challenge: customSCEPcert.challenge,
+ };
+ }
+ case "custom_est_proxy":
+ return {
+ name: certAuthority.name,
+ url: certAuthority.url,
+ username: certAuthority.username,
+ password: certAuthority.password,
+ };
+ default:
+ throw new Error(
+ `Unknown certificate authority type: ${certAuthority.type}`
+ );
}
-
- // FIXME: seems like we have some competing patterns in here where we sometimes do switch
- // statements with a default and sometimes do if or if/else if with a final default return. We
- // should probably standardize on one or the other. Also, do we really want this to be the
- // default? Why not have an explicit check for custom_scep_proxy and have the final
- // else throw an error?
-
- const customSCEPcert = certAuthority as ICertificatesCustomSCEP;
- return {
- name: customSCEPcert.name,
- scepURL: customSCEPcert.url,
- challenge: customSCEPcert.challenge,
- };
};
export const generateEditCertAuthorityData = (
@@ -76,8 +86,7 @@ export const generateEditCertAuthorityData = (
delete certAuthWithoutType.id;
switch (certAuthority.type) {
- case "ndes_scep_proxy":
- // eslint-disable-next-line no-case-declarations
+ case "ndes_scep_proxy": {
const {
scepURL,
adminURL,
@@ -95,8 +104,8 @@ export const generateEditCertAuthorityData = (
certAuthWithoutType
),
};
- case "digicert":
- // eslint-disable-next-line no-case-declarations
+ }
+ case "digicert": {
const {
name,
url: digicertUrl,
@@ -120,8 +129,8 @@ export const generateEditCertAuthorityData = (
certAuthWithoutType
),
};
- case "hydrant":
- // eslint-disable-next-line no-case-declarations
+ }
+ case "hydrant": {
const {
name: hydrantName,
url: hydrantUrl,
@@ -139,8 +148,8 @@ export const generateEditCertAuthorityData = (
certAuthWithoutType
),
};
- case "smallstep":
- // eslint-disable-next-line no-case-declarations
+ }
+ case "smallstep": {
const {
name: smallstepName,
scepURL: smallstepURL,
@@ -160,12 +169,8 @@ export const generateEditCertAuthorityData = (
certAuthWithoutType
),
};
-
- // FIXME: do we really want this to be the default? why not have an explicit case for
- // custom_scep_proxy and have the default throw an error?
- default:
- // custom_scep_proxy
- // eslint-disable-next-line no-case-declarations
+ }
+ case "custom_scep_proxy": {
const {
name: customSCEPName,
scepURL: customSCEPUrl,
@@ -181,6 +186,30 @@ export const generateEditCertAuthorityData = (
certAuthWithoutType
),
};
+ }
+ case "custom_est_proxy": {
+ const {
+ name: customESTName,
+ scepURL: customESTUrl,
+ username: customESTUsername,
+ password: customESTPassword,
+ } = formData as ISmallstepFormData;
+ return {
+ custom_est_proxy: deepDifference(
+ {
+ name: customESTName,
+ url: customESTUrl,
+ username: customESTUsername,
+ password: customESTPassword,
+ },
+ certAuthWithoutType
+ ),
+ };
+ }
+ default:
+ throw new Error(
+ `Unknown certificate authority type: ${certAuthority.type}`
+ );
}
};
@@ -191,69 +220,114 @@ export const updateFormData = (
) => {
const newData = { ...prevFormData, [update.name]: update.value };
- // for some inputs that change we want to reset one of the other inputs
- // and force users to re-enter it. we only want to clear these values if it
+ // for some inputs that change we want to reset one or more of the other inputs
+ // and force users to re-enter them. we only want to clear these values if it
// has not been updated. The characters "********" is the value the API sends
- // back so we check for that value to determine if its been changed or not.
- if (certAuthority.type === "digicert") {
- const formData = prevFormData as IDigicertFormData;
- if (
- update.name === "name" ||
- update.name === "url" ||
- update.name === "profileId"
- ) {
- return {
- ...newData,
- apiToken: formData.apiToken === "********" ? "" : formData.apiToken,
- };
+ // back so we check for that value to determine if it's been changed or not.
+ switch (certAuthority.type) {
+ case "digicert": {
+ const formData = prevFormData as IDigicertFormData;
+ if (
+ update.name === "name" ||
+ update.name === "url" ||
+ update.name === "profileId"
+ ) {
+ return {
+ ...newData,
+ apiToken:
+ formData.apiToken === UNCHANGED_PASSWORD_API_RESPONSE
+ ? ""
+ : formData.apiToken,
+ };
+ }
+ break;
}
- } else if (certAuthority.type === "ndes_scep_proxy") {
- const formData = prevFormData as INDESFormData;
- if (update.name === "adminURL" || update.name === "username") {
- return {
- ...newData,
- password: formData.password === "********" ? "" : formData.password,
- };
+ case "ndes_scep_proxy": {
+ const formData = prevFormData as INDESFormData;
+ if (update.name === "adminURL" || update.name === "username") {
+ return {
+ ...newData,
+ password:
+ formData.password === UNCHANGED_PASSWORD_API_RESPONSE
+ ? ""
+ : formData.password,
+ };
+ }
+ break;
}
- } else if (certAuthority.type === "custom_scep_proxy") {
- const formData = prevFormData as ICustomSCEPFormData;
- if (update.name === "name" || update.name === "scepURL") {
- return {
- ...newData,
- challenge: formData.challenge === "********" ? "" : formData.challenge,
- };
+ case "custom_scep_proxy": {
+ const formData = prevFormData as ICustomSCEPFormData;
+ if (update.name === "name" || update.name === "scepURL") {
+ return {
+ ...newData,
+ challenge:
+ formData.challenge === UNCHANGED_PASSWORD_API_RESPONSE
+ ? ""
+ : formData.challenge,
+ };
+ }
+ break;
}
- } else if (certAuthority.type === "hydrant") {
- // for Hydrant, we reset clientId and clientSecret if name or url changes
- // and the fields have not been updated. We do this to force users to send
- // the correct clientId and clientSecret for the new name or url.
- const formData = prevFormData as IHydrantFormData;
- if (update.name === "name" || update.name === "url") {
- return {
- ...newData,
- clientId:
- formData.clientId === certAuthority.client_id
- ? ""
- : formData.clientId,
- clientSecret:
- formData.clientSecret === "********" ? "" : formData.clientSecret,
- };
+ case "hydrant": {
+ // for Hydrant, we reset clientId and clientSecret if name or url changes
+ // and the fields have not been updated. We do this to force users to send
+ // the correct clientId and clientSecret for the new name or url.
+ const formData = prevFormData as IHydrantFormData;
+ if (update.name === "name" || update.name === "url") {
+ return {
+ ...newData,
+ clientId:
+ formData.clientId === certAuthority.client_id
+ ? ""
+ : formData.clientId,
+ clientSecret:
+ formData.clientSecret === UNCHANGED_PASSWORD_API_RESPONSE
+ ? ""
+ : formData.clientSecret,
+ };
+ }
+ break;
}
- } else if (certAuthority.type === "smallstep") {
- const formData = prevFormData as ISmallstepFormData;
- if (
- update.name === "name" ||
- update.name === "scepURL" ||
- update.name === "challengeURL" ||
- update.name === "username"
- ) {
- return {
- ...newData,
- password: formData.password === "********" ? "" : formData.password,
- };
+ case "smallstep": {
+ const formData = prevFormData as ISmallstepFormData;
+ if (
+ update.name === "name" ||
+ update.name === "scepURL" ||
+ update.name === "challengeURL" ||
+ update.name === "username"
+ ) {
+ return {
+ ...newData,
+ password:
+ formData.password === UNCHANGED_PASSWORD_API_RESPONSE
+ ? ""
+ : formData.password,
+ };
+ }
+ break;
}
+ case "custom_est_proxy": {
+ const formData = prevFormData as ICustomESTFormData;
+ if (update.name === "url") {
+ return {
+ ...newData,
+ username:
+ formData.username === certAuthority.username
+ ? ""
+ : formData.username,
+ password:
+ formData.password === UNCHANGED_PASSWORD_API_RESPONSE
+ ? ""
+ : formData.password,
+ };
+ }
+ break;
+ }
+ default:
+ throw new Error(
+ `Unknown certificate authority type: ${certAuthority.type}`
+ );
}
-
return newData;
};
diff --git a/frontend/services/entities/certificates.ts b/frontend/services/entities/certificates.ts
index bdfcaced27..6ea5db064d 100644
--- a/frontend/services/entities/certificates.ts
+++ b/frontend/services/entities/certificates.ts
@@ -8,6 +8,7 @@ import {
ICertificatesHydrant,
ICertificatesNDES,
ICertificatesSmallstep,
+ ICertificatesCustomEST,
} from "interfaces/certificates";
type IGetCertAuthoritiesListResponse = {
@@ -27,14 +28,16 @@ export type IAddCertAuthorityBody =
| { ndes_scep_proxy: ICertificatesNDES }
| { custom_scep_proxy: ICertificatesCustomSCEP }
| { hydrant: ICertificatesHydrant }
- | { smallstep: ICertificatesSmallstep };
+ | { smallstep: ICertificatesSmallstep }
+ | { custom_est_proxy: ICertificatesCustomEST };
export type IEditCertAuthorityBody =
| { digicert: Partial }
| { ndes_scep_proxy: Partial }
| { custom_scep_proxy: Partial }
| { hydrant: Partial }
- | { smallstep: Partial };
+ | { smallstep: Partial }
+ | { custom_est_proxy: Partial };
export default {
getCertificateAuthoritiesList: (): Promise => {