Add certificate model for http and dns

change is_ecc to boolean, its still stored as int in sqlite
This commit is contained in:
Jamie Curnow
2023-02-28 20:55:40 +10:00
parent 7455accf58
commit fc2df47753
22 changed files with 599 additions and 71 deletions

View File

@@ -1,9 +1,5 @@
import {
Button,
FormControl,
FormErrorMessage,
FormLabel,
Input,
Modal,
ModalOverlay,
ModalContent,
@@ -16,14 +12,14 @@ import {
} from "@chakra-ui/react";
import { Certificate } from "api/npm";
import { PrettyButton } from "components";
import { Formik, Form, Field } from "formik";
import { Formik, Form } from "formik";
import { useSetCertificate } from "hooks";
import { intl } from "locale";
import { validateString } from "modules/Validations";
import CustomForm from "./CustomForm";
import DNSForm from "./DNSForm";
import HTTPForm from "./HTTPForm";
import MKCertForm from "./MKCertForm";
interface CertificateCreateModalProps {
isOpen: boolean;
@@ -44,8 +40,9 @@ function CertificateCreateModal({
const onSubmit = async (
payload: Certificate,
{ setErrors, setSubmitting }: any,
{ /*setErrors,*/ setSubmitting }: any,
) => {
payload.type = certType;
const showErr = (msg: string) => {
toast({
description: intl.formatMessage({
@@ -60,6 +57,8 @@ function CertificateCreateModal({
setCertificate(payload, {
onError: (err: any) => {
showErr(err.message);
/*
if (err.message === "ca-bundle-does-not-exist") {
setErrors({
caBundle: intl.formatMessage({
@@ -68,28 +67,52 @@ function CertificateCreateModal({
});
} else {
showErr(err.message);
}
}*/
},
onSuccess: () => onModalClose(),
onSettled: () => setSubmitting(false),
});
};
const getInitialValues = (type: string): any => {
switch (type) {
case "http":
return {
certificateAuthorityId: 0,
name: "",
domainNames: [],
isEcc: false,
} as any;
case "dns":
return {
certificateAuthorityId: 0,
dnsProviderId: 0,
name: "",
domainNames: [],
isEcc: false,
} as any;
case "custom":
return {
name: "",
domainNames: [],
// isEcc: false, // todo, required?
// todo: add meta?
} as any;
case "mkcert":
return {
name: "",
domainNames: [],
// isEcc: false, // todo, supported?
// todo: add meta?
} as any;
}
};
return (
<Modal isOpen={isOpen} onClose={onModalClose} closeOnOverlayClick={false}>
<ModalOverlay />
<ModalContent>
<Formik
initialValues={
{
name: "",
acmeshServer: "",
caBundle: "",
maxDomains: 5,
isWildcardSupported: false,
} as Certificate
}
onSubmit={onSubmit}>
<Formik initialValues={getInitialValues(certType)} onSubmit={onSubmit}>
{({ isSubmitting }) => (
<Form>
<ModalHeader>
@@ -98,39 +121,16 @@ function CertificateCreateModal({
<ModalCloseButton />
<ModalBody>
<Stack spacing={4}>
<Field name="name" validate={validateString(1, 100)}>
{({ field, form }: any) => (
<FormControl
isRequired
isInvalid={form.errors.name && form.touched.name}>
<FormLabel htmlFor="name">
{intl.formatMessage({
id: "name",
})}
</FormLabel>
<Input
{...field}
id="name"
placeholder={intl.formatMessage({
id: "name",
})}
/>
<FormErrorMessage>{form.errors.name}</FormErrorMessage>
</FormControl>
)}
</Field>
{certType === "http" ? <HTTPForm /> : null}
{certType === "dns" ? <DNSForm /> : null}
{certType === "custom" ? <CustomForm /> : null}
{certType === "mkcert" ? <MKCertForm /> : null}
</Stack>
{certType === "http" ? <HTTPForm /> : null}
{certType === "dns" ? <DNSForm /> : null}
{certType === "custom" ? <CustomForm /> : null}
</ModalBody>
<ModalFooter>
{certType !== "" ? (
<PrettyButton mr={3} isLoading={isSubmitting}>
{intl.formatMessage({ id: "form.save" })}
</PrettyButton>
) : null}
<PrettyButton mr={3} isLoading={isSubmitting}>
{intl.formatMessage({ id: "form.save" })}
</PrettyButton>
<Button onClick={onModalClose} isLoading={isSubmitting}>
{intl.formatMessage({ id: "form.cancel" })}
</Button>

View File

@@ -0,0 +1,77 @@
import {
FormControl,
FormErrorMessage,
FormLabel,
Select,
} from "@chakra-ui/react";
import { CertificateAuthority } from "api/npm";
import { Field, useFormikContext } from "formik";
import { useCertificateAuthorities } from "hooks";
import { intl } from "locale";
const fieldName = "certificateAuthorityId";
interface CertificateAuthorityFieldProps {
onChange?: (maxDomains: number, isWildcardSupported: boolean) => any;
}
function CertificateAuthorityField({
onChange,
}: CertificateAuthorityFieldProps) {
const { setFieldValue } = useFormikContext();
const { data, isLoading } = useCertificateAuthorities(0, 999, [{ id: "id" }]);
const handleOnChange = (e: any) => {
if (e.currentTarget.value) {
const id = parseInt(e.currentTarget.value, 10);
// This step enforces that the formik payload has a
// string number instead of a string as the value
// for this field
setFieldValue(fieldName, id);
if (onChange) {
// find items in list of data
const ca = data?.items.find((item) => item.id === id);
if (ca) {
onChange(ca.maxDomains, ca.isWildcardSupported);
} else {
onChange(0, false);
}
}
}
};
return (
<Field name={fieldName}>
{({ field, form }: any) => (
<FormControl
isRequired
isInvalid={form.errors[fieldName] && form.touched[fieldName]}>
<FormLabel htmlFor={fieldName}>
{intl.formatMessage({
id: "certificate-authority",
})}
</FormLabel>
<Select
{...field}
id={fieldName}
disabled={isLoading}
onChange={(e: any) => {
field.onChange(e);
handleOnChange(e);
}}>
<option value="" />
{data?.items?.map((item: CertificateAuthority) => {
return (
<option key={item.id} value={item.id}>
{item.name}
</option>
);
})}
</Select>
<FormErrorMessage>{form.errors[fieldName]}</FormErrorMessage>
</FormControl>
)}
</Field>
);
}
export { CertificateAuthorityField };

View File

@@ -0,0 +1,73 @@
import {
FormControl,
FormErrorMessage,
FormLabel,
Select,
} from "@chakra-ui/react";
import { DNSProvider } from "api/npm";
import { Field, useFormikContext } from "formik";
import { useDNSProviders } from "hooks";
import { intl } from "locale";
const fieldName = "dnsProviderId";
function DNSProviderField() {
const { setFieldValue } = useFormikContext();
const { data, isLoading } = useDNSProviders(0, 999);
const handleOnChange = (e: any) => {
if (e.currentTarget.value) {
const id = parseInt(e.currentTarget.value, 10);
// This step enforces that the formik payload has a
// string number instead of a string as the value
// for this field
setFieldValue(fieldName, id);
}
};
return (
<Field name={fieldName}>
{({ field, form }: any) => (
<FormControl
isRequired
isInvalid={
!isLoading &&
(!data?.total ||
(form.errors[fieldName] && form.touched[fieldName]))
}>
<FormLabel htmlFor={fieldName}>
{intl.formatMessage({
id: "dns-provider",
})}
</FormLabel>
<Select
{...field}
id={fieldName}
disabled={isLoading}
onChange={(e: any) => {
field.onChange(e);
handleOnChange(e);
}}>
<option value="" />
{data?.items?.map((item: DNSProvider) => {
return (
<option key={item.id} value={item.id}>
{item.name}
</option>
);
})}
</Select>
<FormErrorMessage>
{!isLoading && !data?.total
? intl.formatMessage({
id: "dns-providers-empty",
})
: form.errors[fieldName]}
</FormErrorMessage>
</FormControl>
)}
</Field>
);
}
export { DNSProviderField };

View File

@@ -0,0 +1,109 @@
import {
FormControl,
FormErrorMessage,
FormLabel,
FormHelperText,
} from "@chakra-ui/react";
import { CreatableSelect, OptionBase } from "chakra-react-select";
import { Field, useFormikContext } from "formik";
import { intl } from "locale";
interface SelectOption extends OptionBase {
label: string;
value: string;
}
interface DomainNamesFieldProps {
maxDomains?: number;
isWildcardSupported?: boolean;
onChange?: (i: string[]) => any;
}
function DomainNamesField({
maxDomains,
isWildcardSupported,
onChange,
}: DomainNamesFieldProps) {
const { values, setFieldValue } = useFormikContext();
const getDomainCount = (v: string[] | undefined) => {
if (typeof v !== "undefined" && v?.length) {
return v.length;
}
return 0;
};
const isDomainValid = (d: string): boolean => {
const dom = d.trim().toLowerCase();
const v: any = values;
// Deny if the list of domains is hit
if (maxDomains && getDomainCount(v?.domainNames) >= maxDomains) {
return false;
}
if (dom.length < 3) {
return false;
}
// Prevent wildcards
if (!isWildcardSupported && dom.indexOf("*") !== -1) {
return false;
}
// Prevent duplicate * in domain
if ((dom.match(/\*/g) || []).length > 1) {
return false;
}
// Prevent some invalid characters
// @ ,
if ((dom.match(/(@|,)/g) || []).length > 0) {
return false;
}
// This will match *.com type domains,
return dom.match(/\*\.[^.]+$/m) === null;
};
const handleChange = (values: any) => {
const doms = values?.map((i: SelectOption) => {
return i.value;
});
setFieldValue("domainNames", doms);
};
return (
<Field name="domainNames">
{({ field, form }: any) => (
<FormControl
isInvalid={form.errors.domainNames && form.touched.domainNames}>
<FormLabel htmlFor="domainNames">
{intl.formatMessage({
id: "domain_names",
})}
</FormLabel>
<CreatableSelect
name={field.domainNames}
id="domainNames"
closeMenuOnSelect={true}
isClearable={false}
onChange={handleChange}
isValidNewOption={isDomainValid}
isMulti
placeholder="example.com"
/>
{maxDomains ? (
<FormHelperText>
{intl.formatMessage(
{ id: "domain_names.max" },
{ count: maxDomains },
)}
</FormHelperText>
) : null}
<FormErrorMessage>{form.errors.domainNames}</FormErrorMessage>
</FormControl>
)}
</Field>
);
}
export { DomainNamesField };

View File

@@ -0,0 +1,31 @@
import {
FormControl,
FormErrorMessage,
FormLabel,
Switch,
} from "@chakra-ui/react";
import { Field } from "formik";
import { intl } from "locale";
const fieldName = "isEcc";
function EccField() {
return (
<Field name={fieldName}>
{({ field, form }: any) => (
<FormControl
isInvalid={form.errors[fieldName] && form.touched[fieldName]}>
<FormLabel htmlFor={fieldName}>
{intl.formatMessage({
id: "is-ecc",
})}
</FormLabel>
<Switch {...field} id={fieldName} />
<FormErrorMessage>{form.errors[fieldName]}</FormErrorMessage>
</FormControl>
)}
</Field>
);
}
export { EccField };

View File

@@ -0,0 +1,37 @@
import {
FormControl,
FormErrorMessage,
FormLabel,
Input,
} from "@chakra-ui/react";
import { Field } from "formik";
import { intl } from "locale";
import { validateString } from "modules/Validations";
function NameField() {
return (
<Field name="name" validate={validateString(1, 100)}>
{({ field, form }: any) => (
<FormControl
isRequired
isInvalid={form.errors.name && form.touched.name}>
<FormLabel htmlFor="name">
{intl.formatMessage({
id: "name",
})}
</FormLabel>
<Input
{...field}
id="name"
placeholder={intl.formatMessage({
id: "name",
})}
/>
<FormErrorMessage>{form.errors.name}</FormErrorMessage>
</FormControl>
)}
</Field>
);
}
export { NameField };

View File

@@ -0,0 +1,5 @@
export * from "./CertificateAuthorityField";
export * from "./DNSProviderField";
export * from "./DomainNamesField";
export * from "./EccField";
export * from "./NameField";

View File

@@ -1,5 +1,12 @@
import { DomainNamesField, NameField } from "./Common";
function CustomForm() {
return <p>Custom form</p>;
return (
<>
<NameField />
<DomainNamesField />
</>
);
}
export default CustomForm;

View File

@@ -1,5 +1,34 @@
import { useState } from "react";
import {
CertificateAuthorityField,
DNSProviderField,
DomainNamesField,
EccField,
NameField,
} from "./Common";
function DNSForm() {
return <p>DNS form</p>;
const [maxDomains, setMaxDomains] = useState(0);
const [isWildcardSupported, setIsWildcardSupported] = useState(false);
const handleCAChange = (maxD: number, wildcards: boolean) => {
setMaxDomains(maxD);
setIsWildcardSupported(wildcards);
};
return (
<>
<NameField />
<CertificateAuthorityField onChange={handleCAChange} />
<DomainNamesField
maxDomains={maxDomains}
isWildcardSupported={isWildcardSupported}
/>
<DNSProviderField />
<EccField />
</>
);
}
export default DNSForm;

View File

@@ -1,5 +1,32 @@
import { useState } from "react";
import {
CertificateAuthorityField,
DomainNamesField,
EccField,
NameField,
} from "./Common";
function HTTPForm() {
return <p>Http form</p>;
const [maxDomains, setMaxDomains] = useState(0);
const [isWildcardSupported, setIsWildcardSupported] = useState(false);
const handleCAChange = (maxD: number, wildcards: boolean) => {
setMaxDomains(maxD);
setIsWildcardSupported(wildcards);
};
return (
<>
<NameField />
<CertificateAuthorityField onChange={handleCAChange} />
<DomainNamesField
maxDomains={maxDomains}
isWildcardSupported={isWildcardSupported}
/>
<EccField />
</>
);
}
export default HTTPForm;

View File

@@ -0,0 +1,12 @@
import { DomainNamesField, NameField } from "./Common";
function MKCertForm() {
return (
<>
<NameField />
<DomainNamesField />
</>
);
}
export default MKCertForm;