mirror of
https://github.com/fleetdm/fleet
synced 2026-05-23 08:58:41 +00:00
Update UI for Smallstep CA feature (#33448)
This commit is contained in:
parent
3a3a0ca480
commit
f2eb991644
15 changed files with 595 additions and 8 deletions
1
changes/32722-ui-smallstep
Normal file
1
changes/32722-ui-smallstep
Normal file
|
|
@ -0,0 +1 @@
|
|||
- Updated UI to add Smallstep certificate authority integration.
|
||||
|
|
@ -60,6 +60,9 @@ export enum ActivityType {
|
|||
AddedHydrant = "added_hydrant",
|
||||
DeletedHydrant = "deleted_hydrant",
|
||||
EditedHydrant = "edited_hydrant",
|
||||
AddedSmallstep = "added_smallstep",
|
||||
DeletedSmallstep = "deleted_smallstep",
|
||||
EditedSmallstep = "edited_smallstep",
|
||||
CreatedWindowsProfile = "created_windows_profile",
|
||||
DeletedWindowsProfile = "deleted_windows_profile",
|
||||
EditedWindowsProfile = "edited_windows_profile",
|
||||
|
|
|
|||
|
|
@ -78,18 +78,30 @@ export interface ICertificatesCustomSCEP {
|
|||
challenge: string;
|
||||
}
|
||||
|
||||
export interface ICertificatesSmallstep {
|
||||
id?: number;
|
||||
type?: "smallstep";
|
||||
name: string;
|
||||
url: string;
|
||||
challenge_url: string;
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export type ICertificateAuthorityType =
|
||||
| "ndes_scep_proxy"
|
||||
| "digicert"
|
||||
| "custom_scep_proxy"
|
||||
| "hydrant";
|
||||
| "hydrant"
|
||||
| "smallstep";
|
||||
|
||||
/** all the types of certificates */
|
||||
export type ICertificateAuthority =
|
||||
| ICertificatesNDES
|
||||
| ICertificatesDigicert
|
||||
| ICertificatesHydrant
|
||||
| ICertificatesCustomSCEP;
|
||||
| ICertificatesCustomSCEP
|
||||
| ICertificatesSmallstep;
|
||||
|
||||
export const isNDESCertAuthority = (
|
||||
integration: ICertificateAuthority
|
||||
|
|
@ -130,3 +142,15 @@ export const isCustomSCEPCertAuthority = (
|
|||
"name" in integration && "url" in integration && "challenge" in integration
|
||||
);
|
||||
};
|
||||
|
||||
export const isSmallstepCertAuthority = (
|
||||
integration: ICertificateAuthority
|
||||
): integration is ICertificatesSmallstep => {
|
||||
return (
|
||||
"name" in integration &&
|
||||
"url" in integration &&
|
||||
"challenge_url" in integration &&
|
||||
"username" in integration &&
|
||||
"password" in integration
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1656,19 +1656,22 @@ const getDetail = (activity: IActivity, isPremiumTier: boolean) => {
|
|||
}
|
||||
case ActivityType.AddedCustomScepProxy:
|
||||
case ActivityType.AddedDigicert:
|
||||
case ActivityType.AddedHydrant: {
|
||||
case ActivityType.AddedHydrant:
|
||||
case ActivityType.AddedSmallstep: {
|
||||
return TAGGED_TEMPLATES.addedCertificateAuthority(activity.details?.name);
|
||||
}
|
||||
case ActivityType.DeletedCustomScepProxy:
|
||||
case ActivityType.DeletedDigicert:
|
||||
case ActivityType.DeletedHydrant: {
|
||||
case ActivityType.DeletedHydrant:
|
||||
case ActivityType.DeletedSmallstep: {
|
||||
return TAGGED_TEMPLATES.deletedCertificateAuthority(
|
||||
activity.details?.name
|
||||
);
|
||||
}
|
||||
case ActivityType.EditedCustomScepProxy:
|
||||
case ActivityType.EditedDigicert:
|
||||
case ActivityType.EditedHydrant: {
|
||||
case ActivityType.EditedHydrant:
|
||||
case ActivityType.EditedSmallstep: {
|
||||
return TAGGED_TEMPLATES.editedCertificateAuthority(
|
||||
activity.details?.name
|
||||
);
|
||||
|
|
|
|||
|
|
@ -25,12 +25,14 @@ import CustomSCEPForm from "../CustomSCEPForm";
|
|||
import { ICustomSCEPFormData } from "../CustomSCEPForm/CustomSCEPForm";
|
||||
import HydrantForm from "../HydrantForm";
|
||||
import { IHydrantFormData } from "../HydrantForm/HydrantForm";
|
||||
import { ISmallstepFormData } from "../SmallstepForm/SmallstepForm";
|
||||
|
||||
export type ICertFormData =
|
||||
| IDigicertFormData
|
||||
| IHydrantFormData
|
||||
| INDESFormData
|
||||
| ICustomSCEPFormData;
|
||||
| ICustomSCEPFormData
|
||||
| ISmallstepFormData;
|
||||
|
||||
const baseClass = "add-cert-authority-modal";
|
||||
|
||||
|
|
@ -79,6 +81,16 @@ const AddCertAuthorityModal = ({
|
|||
scepURL: "",
|
||||
challenge: "",
|
||||
});
|
||||
const [
|
||||
smallstepFormData,
|
||||
setSmallstepFormData,
|
||||
] = useState<ISmallstepFormData>({
|
||||
name: "",
|
||||
scepURL: "",
|
||||
challengeURL: "",
|
||||
username: "",
|
||||
password: "",
|
||||
});
|
||||
|
||||
const onChangeDropdown = (value: ICertificateAuthorityType) => {
|
||||
setCertAuthorityType(value);
|
||||
|
|
@ -104,6 +116,10 @@ const AddCertAuthorityModal = ({
|
|||
setFormData = setCustomSCEPFormData;
|
||||
formData = customSCEPFormData;
|
||||
break;
|
||||
case "smallstep":
|
||||
setFormData = setSmallstepFormData;
|
||||
formData = smallstepFormData;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
|
@ -129,6 +145,9 @@ const AddCertAuthorityModal = ({
|
|||
case "custom_scep_proxy":
|
||||
formData = customSCEPFormData;
|
||||
break;
|
||||
case "smallstep":
|
||||
formData = smallstepFormData;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,9 @@ import { ICertFormData } from "../AddCertAuthorityModal/AddCertAuthorityModal";
|
|||
import { INDESFormData } from "../NDESForm/NDESForm";
|
||||
import { ICustomSCEPFormData } from "../CustomSCEPForm/CustomSCEPForm";
|
||||
import { IHydrantFormData } from "../HydrantForm/HydrantForm";
|
||||
import { ISmallstepFormData } from "../SmallstepForm/SmallstepForm";
|
||||
|
||||
// FIXME: do we care about the order of these? Should we alphabetize them or something?
|
||||
const DEFAULT_CERT_AUTHORITY_OPTIONS: IDropdownOption[] = [
|
||||
{ label: "DigiCert", value: "digicert" },
|
||||
{
|
||||
|
|
@ -28,6 +30,7 @@ const DEFAULT_CERT_AUTHORITY_OPTIONS: IDropdownOption[] = [
|
|||
label: "Custom SCEP (Simple Certificate Enrollment Protocol)",
|
||||
value: "custom_scep_proxy",
|
||||
},
|
||||
{ label: "Smallstep", value: "smallstep" },
|
||||
];
|
||||
|
||||
/**
|
||||
|
|
@ -131,6 +134,24 @@ export const generateAddCertAuthorityData = (
|
|||
client_secret: clientSecret,
|
||||
},
|
||||
};
|
||||
case "smallstep":
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
const {
|
||||
name: smallstepName,
|
||||
scepURL: smallstepScepURL,
|
||||
challengeURL,
|
||||
username: smallstepUsername,
|
||||
password: smallstepPassword,
|
||||
} = formData as ISmallstepFormData;
|
||||
return {
|
||||
smallstep: {
|
||||
name: smallstepName,
|
||||
url: smallstepScepURL,
|
||||
challenge_url: challengeURL,
|
||||
username: smallstepUsername,
|
||||
password: smallstepPassword,
|
||||
},
|
||||
};
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,9 @@ export const generateListData = (
|
|||
case "hydrant":
|
||||
description = "Hydrant (EST - Enrollment Over Secure Transport) ";
|
||||
break;
|
||||
case "smallstep":
|
||||
description = "Smallstep";
|
||||
break;
|
||||
default:
|
||||
description = "Unknown Certificate Authority Type";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import { ICertFormData } from "../AddCertAuthorityModal/AddCertAuthorityModal";
|
|||
import NDESForm from "../NDESForm";
|
||||
import CustomSCEPForm from "../CustomSCEPForm";
|
||||
import HydrantForm from "../HydrantForm";
|
||||
import SmallstepForm from "../SmallstepForm";
|
||||
|
||||
const baseClass = "edit-cert-authority-modal";
|
||||
|
||||
|
|
@ -92,6 +93,16 @@ const EditCertAuthorityModal = ({
|
|||
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;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { IDigicertFormData } from "../DigicertForm/DigicertForm";
|
|||
import { INDESFormData } from "../NDESForm/NDESForm";
|
||||
import { ICustomSCEPFormData } from "../CustomSCEPForm/CustomSCEPForm";
|
||||
import { IHydrantFormData } from "../HydrantForm/HydrantForm";
|
||||
import { ISmallstepFormData } from "../SmallstepForm/SmallstepForm";
|
||||
|
||||
export const generateDefaultFormData = (
|
||||
certAuthority: ICertificateAuthority
|
||||
|
|
@ -40,8 +41,22 @@ export const generateDefaultFormData = (
|
|||
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,
|
||||
};
|
||||
}
|
||||
|
||||
// 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,
|
||||
|
|
@ -122,6 +137,30 @@ export const generateEditCertAuthorityData = (
|
|||
certAuthWithoutType
|
||||
),
|
||||
};
|
||||
case "smallstep":
|
||||
// eslint-disable-next-line no-case-declarations
|
||||
const {
|
||||
name: smallstepName,
|
||||
scepURL: smallstepURL,
|
||||
challengeURL: smallstepChallengeURL,
|
||||
username: smallstepUsername,
|
||||
password: smallstepPassword,
|
||||
} = formData as ISmallstepFormData;
|
||||
return {
|
||||
smallstep: deepDifference(
|
||||
{
|
||||
name: smallstepName,
|
||||
scep_url: smallstepURL,
|
||||
challenge_url: smallstepChallengeURL,
|
||||
username: smallstepUsername,
|
||||
password: smallstepPassword,
|
||||
},
|
||||
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
|
||||
|
|
@ -198,6 +237,19 @@ export const updateFormData = (
|
|||
formData.clientSecret === "********" ? "" : formData.clientSecret,
|
||||
};
|
||||
}
|
||||
} 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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return newData;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,113 @@
|
|||
import React from "react";
|
||||
import { noop } from "lodash";
|
||||
import { render, screen } from "@testing-library/react";
|
||||
|
||||
import SmallstepForm, { ISmallstepFormData } from "./SmallstepForm";
|
||||
|
||||
const createTestFormData = (overrides?: Partial<ISmallstepFormData>) => ({
|
||||
name: "TEST_NAME",
|
||||
scepURL: "https://test.com",
|
||||
challengeURL: "https://test.com/challenge",
|
||||
username: "testuser",
|
||||
password: "testpassword",
|
||||
...overrides,
|
||||
});
|
||||
|
||||
describe("SmallstepForm", () => {
|
||||
it("render the custom button text", () => {
|
||||
render(
|
||||
<SmallstepForm
|
||||
formData={createTestFormData()}
|
||||
isSubmitting={false}
|
||||
submitBtnText="Submit"
|
||||
onChange={noop}
|
||||
onSubmit={noop}
|
||||
onCancel={noop}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByRole("button", { name: "Submit" })).toBeVisible();
|
||||
});
|
||||
|
||||
it("enables submission depending on the form validation", async () => {
|
||||
const testData = createTestFormData();
|
||||
render(
|
||||
<SmallstepForm
|
||||
formData={testData}
|
||||
isSubmitting={false}
|
||||
submitBtnText="Submit"
|
||||
onChange={noop}
|
||||
onSubmit={noop}
|
||||
onCancel={noop}
|
||||
/>
|
||||
);
|
||||
|
||||
// data is valid, so submit should be enabled
|
||||
expect(screen.getByRole("button", { name: "Submit" })).toBeEnabled();
|
||||
});
|
||||
|
||||
it("disables submission when form is invalid", async () => {
|
||||
const testData = createTestFormData();
|
||||
// make name invalid by setting it to an empty string
|
||||
testData.name = "";
|
||||
render(
|
||||
<SmallstepForm
|
||||
formData={testData}
|
||||
isSubmitting={false}
|
||||
submitBtnText="Submit"
|
||||
onChange={noop}
|
||||
onSubmit={noop}
|
||||
onCancel={noop}
|
||||
/>
|
||||
);
|
||||
// name is required, so submit should be disabled
|
||||
expect(screen.getByRole("button", { name: "Submit" })).toBeDisabled();
|
||||
});
|
||||
|
||||
it("disables submit when isSubmitting is set to true", () => {
|
||||
render(
|
||||
<SmallstepForm
|
||||
formData={createTestFormData()}
|
||||
isSubmitting
|
||||
submitBtnText="Submit"
|
||||
onChange={noop}
|
||||
onSubmit={noop}
|
||||
onCancel={noop}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByRole("button", { name: "Submit" })).toBeDisabled();
|
||||
});
|
||||
|
||||
it("submit button is disabled if isDirty is false", () => {
|
||||
render(
|
||||
<SmallstepForm
|
||||
formData={createTestFormData()}
|
||||
isSubmitting={false}
|
||||
submitBtnText="Submit"
|
||||
isDirty={false}
|
||||
onChange={noop}
|
||||
onSubmit={noop}
|
||||
onCancel={noop}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByRole("button", { name: "Submit" })).toBeDisabled();
|
||||
});
|
||||
|
||||
it("submit button is enabled if isDirty", () => {
|
||||
render(
|
||||
<SmallstepForm
|
||||
formData={createTestFormData()}
|
||||
isSubmitting={false}
|
||||
submitBtnText="Submit"
|
||||
isDirty
|
||||
onChange={noop}
|
||||
onSubmit={noop}
|
||||
onCancel={noop}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByRole("button", { name: "Submit" })).toBeEnabled();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,151 @@
|
|||
import React, { useMemo } from "react";
|
||||
|
||||
import { ICertificateAuthorityPartial } from "interfaces/certificates";
|
||||
|
||||
// @ts-ignore
|
||||
import InputField from "components/forms/fields/InputField";
|
||||
import Button from "components/buttons/Button";
|
||||
import TooltipWrapper from "components/TooltipWrapper";
|
||||
|
||||
import { generateFormValidations, validateFormData } from "./helpers";
|
||||
|
||||
const baseClass = "smallstep-form";
|
||||
|
||||
export interface ISmallstepFormData {
|
||||
name: string;
|
||||
scepURL: string;
|
||||
challengeURL: string;
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
interface ISmallstepFormProps {
|
||||
certAuthorities?: ICertificateAuthorityPartial[];
|
||||
formData: ISmallstepFormData;
|
||||
submitBtnText: string;
|
||||
isSubmitting: boolean;
|
||||
isEditing?: boolean;
|
||||
isDirty?: boolean;
|
||||
onChange: (update: { name: string; value: string }) => void;
|
||||
onSubmit: () => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
const SmallstepForm = ({
|
||||
certAuthorities,
|
||||
formData,
|
||||
submitBtnText,
|
||||
isSubmitting,
|
||||
isEditing = false,
|
||||
isDirty = true,
|
||||
onChange,
|
||||
onSubmit,
|
||||
onCancel,
|
||||
}: ISmallstepFormProps) => {
|
||||
const validationsConfig = useMemo(() => {
|
||||
return generateFormValidations(certAuthorities ?? [], isEditing);
|
||||
}, [certAuthorities, isEditing]);
|
||||
|
||||
const validations = useMemo(() => {
|
||||
return validateFormData(formData, validationsConfig);
|
||||
}, [formData, validationsConfig]);
|
||||
|
||||
const { name, scepURL, challengeURL, username, password } = formData;
|
||||
|
||||
const onSubmitForm = (evt: React.FormEvent<HTMLFormElement>) => {
|
||||
evt.preventDefault();
|
||||
onSubmit();
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={onSubmitForm}>
|
||||
<div className={`${baseClass}__fields`}>
|
||||
<InputField
|
||||
label="Name"
|
||||
name="name"
|
||||
value={name}
|
||||
error={validations.name?.message}
|
||||
onChange={onChange}
|
||||
parseTarget
|
||||
placeholder="WIFI_CERTIFICATE"
|
||||
helpText="Letters, numbers, and underscores only. Fleet will create configuration profile variables with the name as suffix (e.g. $FLEET_VAR_SMALLSTEP_DATA_WIFI_CERTIFICATE)."
|
||||
/>
|
||||
<InputField
|
||||
label="SCEP URL"
|
||||
name="scepURL"
|
||||
value={scepURL}
|
||||
error={validations.scepURL?.message}
|
||||
onChange={onChange}
|
||||
parseTarget
|
||||
placeholder="https://example.scep.smallstep.com/p/agents/integration-fleet-xr9f4db7"
|
||||
/>
|
||||
<InputField
|
||||
label="Challenge URL"
|
||||
name="challengeURL"
|
||||
value={challengeURL}
|
||||
error={validations.challengeURL?.message}
|
||||
onChange={onChange}
|
||||
parseTarget
|
||||
placeholder="https://example.scep.smallstep.com/fleet/xr9f4db7-83f1-48ab-8982-8b6870d4fl85/challenge"
|
||||
helpText={
|
||||
<>
|
||||
Smallstep calls this the <b>SCEP Challenge URL</b>.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<InputField
|
||||
label="Username"
|
||||
name="username"
|
||||
value={username}
|
||||
error={validations.username?.message}
|
||||
onChange={onChange}
|
||||
parseTarget
|
||||
placeholder={"r9c5faea-af93-4679-922c-5548c6254438"}
|
||||
helpText={
|
||||
<>
|
||||
Smallstep calls this the{" "}
|
||||
<b>Challenge Basic Authentication Username</b>.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<InputField
|
||||
type="password"
|
||||
label="Password"
|
||||
name="password"
|
||||
value={password}
|
||||
error={validations.password?.message}
|
||||
onChange={onChange}
|
||||
parseTarget
|
||||
helpText={
|
||||
<>
|
||||
Smallstep calls this the{" "}
|
||||
<b>Challenge Basic Authentication Password</b>.
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className={`${baseClass}__cta`}>
|
||||
<TooltipWrapper
|
||||
tipContent="Complete all required fields to save."
|
||||
underline={false}
|
||||
position="top"
|
||||
disableTooltip={validations.isValid}
|
||||
showArrow
|
||||
>
|
||||
<Button
|
||||
type="submit"
|
||||
isLoading={isSubmitting}
|
||||
disabled={!validations.isValid || isSubmitting || !isDirty}
|
||||
>
|
||||
{submitBtnText}
|
||||
</Button>
|
||||
</TooltipWrapper>
|
||||
<Button variant="inverse" onClick={onCancel}>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export default SmallstepForm;
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
.smallstep-form {
|
||||
&__fields {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $pad-large;
|
||||
}
|
||||
|
||||
&__cta {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
gap: $pad-medium;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,169 @@
|
|||
import { ICertificateAuthorityPartial } from "interfaces/certificates";
|
||||
|
||||
import valid_url from "components/forms/validators/valid_url";
|
||||
|
||||
import { ISmallstepFormData } from "./SmallstepForm";
|
||||
|
||||
// TODO: create a validator abstraction for this and the other form validation files
|
||||
|
||||
export interface ISmallstepFormValidation {
|
||||
isValid: boolean;
|
||||
name?: { isValid: boolean; message?: string };
|
||||
scepURL?: { isValid: boolean; message?: string };
|
||||
challengeURL?: { isValid: boolean; message?: string };
|
||||
username?: { isValid: boolean; message?: string };
|
||||
password?: { isValid: boolean; message?: string };
|
||||
}
|
||||
|
||||
type IMessageFunc = (formData: ISmallstepFormData) => string;
|
||||
type IValidationMessage = string | IMessageFunc;
|
||||
type IFormValidationKey = keyof Omit<ISmallstepFormValidation, "isValid">;
|
||||
|
||||
interface IValidation {
|
||||
name: string;
|
||||
isValid: (formData: ISmallstepFormData) => boolean;
|
||||
message?: IValidationMessage;
|
||||
}
|
||||
|
||||
type IFormValidations = Record<
|
||||
IFormValidationKey,
|
||||
{ validations: IValidation[] }
|
||||
>;
|
||||
|
||||
export const generateFormValidations = (
|
||||
smallstepIntegrations: ICertificateAuthorityPartial[],
|
||||
isEditing: boolean
|
||||
) => {
|
||||
const FORM_VALIDATIONS: IFormValidations = {
|
||||
name: {
|
||||
validations: [
|
||||
{
|
||||
name: "required",
|
||||
isValid: (formData: ISmallstepFormData) => {
|
||||
return formData.name.length > 0;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalidCharacters",
|
||||
isValid: (formData: ISmallstepFormData) => {
|
||||
return /^[a-zA-Z0-9_]+$/.test(formData.name);
|
||||
},
|
||||
message:
|
||||
"Invalid characters. Only letters, numbers and underscores allowed.",
|
||||
},
|
||||
{
|
||||
name: "unique",
|
||||
isValid: (formData: ISmallstepFormData) => {
|
||||
return (
|
||||
isEditing ||
|
||||
smallstepIntegrations.find(
|
||||
(cert) => cert.name === formData.name
|
||||
) === undefined
|
||||
);
|
||||
},
|
||||
message: "Name is already used by another custom SCEP CA.",
|
||||
},
|
||||
],
|
||||
},
|
||||
scepURL: {
|
||||
validations: [
|
||||
{
|
||||
name: "required",
|
||||
isValid: (formData: ISmallstepFormData) => {
|
||||
return formData.scepURL.length > 0;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "validUrl",
|
||||
isValid: (formData: ISmallstepFormData) => {
|
||||
return valid_url({ url: formData.scepURL });
|
||||
},
|
||||
message: "Must be a valid URL.",
|
||||
},
|
||||
],
|
||||
},
|
||||
challengeURL: {
|
||||
validations: [
|
||||
{
|
||||
name: "required",
|
||||
isValid: (formData: ISmallstepFormData) => {
|
||||
return formData.challengeURL.length > 0;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "validUrl",
|
||||
isValid: (formData: ISmallstepFormData) => {
|
||||
return valid_url({ url: formData.challengeURL });
|
||||
},
|
||||
message: "Must be a valid URL.",
|
||||
},
|
||||
],
|
||||
},
|
||||
username: {
|
||||
validations: [
|
||||
{
|
||||
name: "required",
|
||||
isValid: (formData: ISmallstepFormData) => {
|
||||
return formData.username.length > 0;
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
password: {
|
||||
validations: [
|
||||
{
|
||||
name: "required",
|
||||
isValid: (formData: ISmallstepFormData) => {
|
||||
return formData.password.length > 0;
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
return FORM_VALIDATIONS;
|
||||
};
|
||||
|
||||
const getErrorMessage = (
|
||||
formData: ISmallstepFormData,
|
||||
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: ISmallstepFormData,
|
||||
validationConfig: IFormValidations
|
||||
) => {
|
||||
const formValidation: ISmallstepFormValidation = {
|
||||
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),
|
||||
};
|
||||
}
|
||||
|
||||
console.log("objKey", objKey);
|
||||
console.log("formValidation", formValidation);
|
||||
});
|
||||
|
||||
return formValidation;
|
||||
};
|
||||
|
|
@ -0,0 +1 @@
|
|||
export { default } from "./SmallstepForm";
|
||||
|
|
@ -7,6 +7,7 @@ import {
|
|||
ICertificatesDigicert,
|
||||
ICertificatesHydrant,
|
||||
ICertificatesNDES,
|
||||
ICertificatesSmallstep,
|
||||
} from "interfaces/certificates";
|
||||
|
||||
type IGetCertAuthoritiesListResponse = {
|
||||
|
|
@ -25,13 +26,15 @@ export type IAddCertAuthorityBody =
|
|||
| { digicert: ICertificatesDigicert }
|
||||
| { ndes_scep_proxy: ICertificatesNDES }
|
||||
| { custom_scep_proxy: ICertificatesCustomSCEP }
|
||||
| { hydrant: ICertificatesHydrant };
|
||||
| { hydrant: ICertificatesHydrant }
|
||||
| { smallstep: ICertificatesSmallstep };
|
||||
|
||||
export type IEditCertAuthorityBody =
|
||||
| { digicert: Partial<ICertificatesDigicert> }
|
||||
| { ndes_scep_proxy: Partial<ICertificatesNDES> }
|
||||
| { custom_scep_proxy: Partial<ICertificatesCustomSCEP> }
|
||||
| { hydrant: Partial<ICertificatesHydrant> };
|
||||
| { hydrant: Partial<ICertificatesHydrant> }
|
||||
| { smallstep: Partial<ICertificatesSmallstep> };
|
||||
|
||||
export default {
|
||||
getCertificateAuthoritiesList: (): Promise<IGetCertAuthoritiesListResponse> => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue