mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-21 19:28:16 +00:00
feat: POC for ACME done
This commit is contained in:
+350
@@ -0,0 +1,350 @@
|
||||
import { useEffect } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import {
|
||||
Button,
|
||||
FilterableSelect,
|
||||
FormControl,
|
||||
Input,
|
||||
Modal,
|
||||
ModalContent,
|
||||
Select,
|
||||
SelectItem,
|
||||
Switch
|
||||
} from "@app/components/v2";
|
||||
import { useWorkspace } from "@app/context";
|
||||
import { useListAvailableAppConnections } from "@app/hooks/api/appConnections";
|
||||
import { AppConnection } from "@app/hooks/api/appConnections/enums";
|
||||
import {
|
||||
AcmeDnsProvider,
|
||||
CaStatus,
|
||||
CaType,
|
||||
useCreateUnifiedCa,
|
||||
useGetCaById,
|
||||
useUpdateCa
|
||||
} from "@app/hooks/api/ca";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
const schema = z
|
||||
.object({
|
||||
type: z.enum([CaType.ACME]),
|
||||
name: z.string(),
|
||||
disableDirectIssuance: z.boolean(),
|
||||
status: z.enum([CaStatus.ACTIVE, CaStatus.DISABLED]),
|
||||
configuration: z.object({
|
||||
dnsAppConnection: z.object({
|
||||
id: z.string(),
|
||||
name: z.string()
|
||||
}),
|
||||
dnsProvider: z.nativeEnum(AcmeDnsProvider),
|
||||
directoryUrl: z.string(),
|
||||
accountEmail: z.string()
|
||||
})
|
||||
})
|
||||
.required();
|
||||
|
||||
export type FormData = z.infer<typeof schema>;
|
||||
|
||||
type Props = {
|
||||
popUp: UsePopUpState<["ca"]>;
|
||||
handlePopUpToggle: (popUpName: keyof UsePopUpState<["ca"]>, state?: boolean) => void;
|
||||
};
|
||||
|
||||
const caTypes = [{ label: "ACME", value: CaType.ACME }];
|
||||
|
||||
export const ExternalCaModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
|
||||
const { data: ca } = useGetCaById((popUp?.ca?.data as { caId: string })?.caId || "");
|
||||
|
||||
// SHEEN TODO: finish up CA management
|
||||
const { mutateAsync: createMutateAsync } = useCreateUnifiedCa();
|
||||
const { mutateAsync: updateMutateAsync } = useUpdateCa();
|
||||
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
reset,
|
||||
formState: { isSubmitting },
|
||||
watch
|
||||
} = useForm<FormData>({
|
||||
resolver: zodResolver(schema),
|
||||
defaultValues: {
|
||||
type: CaType.ACME,
|
||||
name: "",
|
||||
status: CaStatus.ACTIVE,
|
||||
disableDirectIssuance: false,
|
||||
configuration: {
|
||||
dnsAppConnection: {
|
||||
id: "",
|
||||
name: ""
|
||||
},
|
||||
dnsProvider: AcmeDnsProvider.ROUTE53,
|
||||
directoryUrl: "",
|
||||
accountEmail: ""
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const caType = watch("type");
|
||||
const dnsProvider = watch("configuration.dnsProvider");
|
||||
|
||||
const { data: availableConnections, isPending } = useListAvailableAppConnections(
|
||||
AppConnection.AWS,
|
||||
{
|
||||
enabled: dnsProvider === AcmeDnsProvider.ROUTE53
|
||||
}
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (ca) {
|
||||
// reset({
|
||||
// type: ca.type,
|
||||
// name: ca.name,
|
||||
// disableDirectIssuance: ca.disableDirectIssuance
|
||||
// });
|
||||
} else {
|
||||
reset({
|
||||
type: CaType.ACME,
|
||||
name: "",
|
||||
status: CaStatus.ACTIVE,
|
||||
disableDirectIssuance: false,
|
||||
configuration: {
|
||||
dnsAppConnection: {
|
||||
id: "",
|
||||
name: ""
|
||||
},
|
||||
dnsProvider: AcmeDnsProvider.ROUTE53,
|
||||
directoryUrl: "",
|
||||
accountEmail: ""
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [ca]);
|
||||
|
||||
const onFormSubmit = async ({
|
||||
type,
|
||||
name,
|
||||
disableDirectIssuance,
|
||||
status,
|
||||
configuration
|
||||
}: FormData) => {
|
||||
try {
|
||||
if (!currentWorkspace?.slug) return;
|
||||
|
||||
if (ca) {
|
||||
// update
|
||||
// await updateMutateAsync({
|
||||
// projectSlug: currentWorkspace.slug,
|
||||
// caId: ca.id,
|
||||
// name,
|
||||
// disableDirectIssuance,
|
||||
// status
|
||||
// });
|
||||
} else {
|
||||
// create
|
||||
await createMutateAsync({
|
||||
projectId: currentWorkspace.id,
|
||||
name,
|
||||
type,
|
||||
status,
|
||||
disableDirectIssuance,
|
||||
configuration: {
|
||||
...configuration,
|
||||
dnsAppConnectionId: configuration.dnsAppConnection.id
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
reset();
|
||||
handlePopUpToggle("ca", false);
|
||||
|
||||
createNotification({
|
||||
text: `Successfully ${ca ? "updated" : "created"} CA`,
|
||||
type: "success"
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
createNotification({
|
||||
text: "Failed to create CA",
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={popUp?.ca?.isOpen}
|
||||
onOpenChange={(isOpen) => {
|
||||
reset();
|
||||
handlePopUpToggle("ca", isOpen);
|
||||
}}
|
||||
>
|
||||
<ModalContent title={`${ca ? "View" : "Create"} External CA`}>
|
||||
<form onSubmit={handleSubmit(onFormSubmit)}>
|
||||
{ca && (
|
||||
<FormControl label="CA ID">
|
||||
<Input value={ca.id} isDisabled className="bg-white/[0.07]" />
|
||||
</FormControl>
|
||||
)}
|
||||
<Controller
|
||||
control={control}
|
||||
name="type"
|
||||
defaultValue={CaType.ACME}
|
||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||
<FormControl label="Type" errorText={error?.message} isError={Boolean(error)}>
|
||||
<Select
|
||||
defaultValue={field.value}
|
||||
{...field}
|
||||
onValueChange={(e) => onChange(e)}
|
||||
className="w-full"
|
||||
isDisabled={Boolean(ca)}
|
||||
>
|
||||
{caTypes.map(({ label, value }) => (
|
||||
<SelectItem value={String(value || "")} key={label}>
|
||||
{label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
defaultValue=""
|
||||
name="name"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl label="Name" isError={Boolean(error)} errorText={error?.message}>
|
||||
<Input {...field} placeholder="my-external-ca" isDisabled={Boolean(ca)} />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
{caType === CaType.ACME && (
|
||||
<>
|
||||
<Controller
|
||||
control={control}
|
||||
name="configuration.dnsProvider"
|
||||
defaultValue={AcmeDnsProvider.ROUTE53}
|
||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="DNS Provider"
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
>
|
||||
<Select
|
||||
defaultValue={field.value}
|
||||
{...field}
|
||||
onValueChange={(e) => onChange(e)}
|
||||
className="w-full"
|
||||
isDisabled={Boolean(ca)}
|
||||
>
|
||||
<SelectItem
|
||||
value={String(AcmeDnsProvider.ROUTE53)}
|
||||
key={AcmeDnsProvider.ROUTE53}
|
||||
>
|
||||
Route53
|
||||
</SelectItem>
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
render={({ field: { value, onChange }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
tooltipText={`${dnsProvider === AcmeDnsProvider.ROUTE53 ? "Route53" : ""} requires an AWS App Connection. This can be created from the Organization Settings page.`}
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
label="DNS App Connection"
|
||||
>
|
||||
<FilterableSelect
|
||||
value={value}
|
||||
onChange={(newValue) => {
|
||||
onChange(newValue);
|
||||
}}
|
||||
isLoading={isPending}
|
||||
options={availableConnections}
|
||||
placeholder="Select connection..."
|
||||
getOptionLabel={(option) => option.name}
|
||||
getOptionValue={(option) => option.id}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
control={control}
|
||||
name="configuration.dnsAppConnection"
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
defaultValue=""
|
||||
name="configuration.directoryUrl"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Directory URL"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Input
|
||||
{...field}
|
||||
placeholder="https://acme-v02.api.letsencrypt.org/directory"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
defaultValue=""
|
||||
name="configuration.accountEmail"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Account Email"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Input {...field} placeholder="[email protected]" />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<Controller
|
||||
control={control}
|
||||
name="disableDirectIssuance"
|
||||
render={({ field, fieldState: { error } }) => {
|
||||
return (
|
||||
<FormControl isError={Boolean(error)} errorText={error?.message} className="my-8">
|
||||
<Switch
|
||||
id="is-active"
|
||||
onCheckedChange={(value) => field.onChange(value)}
|
||||
isChecked={field.value}
|
||||
>
|
||||
<p className="w-full">Disable Direct Issuance</p>
|
||||
</Switch>
|
||||
</FormControl>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<div className="flex items-center">
|
||||
<Button
|
||||
className="mr-4"
|
||||
size="sm"
|
||||
type="submit"
|
||||
isLoading={isSubmitting}
|
||||
isDisabled={isSubmitting}
|
||||
>
|
||||
{popUp?.ca?.data ? "Update" : "Create"}
|
||||
</Button>
|
||||
<Button
|
||||
colorSchema="secondary"
|
||||
variant="plain"
|
||||
onClick={() => handlePopUpToggle("ca", false)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user