Merge pull request #2711 from Infisical/misc/moved-aws-sm-integration-to-react-hook-form

misc: moved aws secret manager integration to react hook form
This commit is contained in:
Maidul Islam
2024-11-09 08:59:38 -05:00
committed by GitHub
@@ -1,4 +1,5 @@
import { useEffect, useState } from "react"; import { useEffect } from "react";
import { Controller, useForm } from "react-hook-form";
import Head from "next/head"; import Head from "next/head";
import Image from "next/image"; import Image from "next/image";
import Link from "next/link"; import Link from "next/link";
@@ -10,9 +11,12 @@ import {
faCircleInfo faCircleInfo
} from "@fortawesome/free-solid-svg-icons"; } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod";
import { motion } from "framer-motion"; import { motion } from "framer-motion";
import queryString from "query-string"; import queryString from "query-string";
import z from "zod";
import { SecretPathInput } from "@app/components/v2/SecretPathInput";
import { useCreateIntegration } from "@app/hooks/api"; import { useCreateIntegration } from "@app/hooks/api";
import { useGetIntegrationAuthAwsKmsKeys } from "@app/hooks/api/integrationAuth/queries"; import { useGetIntegrationAuthAwsKmsKeys } from "@app/hooks/api/integrationAuth/queries";
import { IntegrationMappingBehavior } from "@app/hooks/api/integrations/types"; import { IntegrationMappingBehavior } from "@app/hooks/api/integrations/types";
@@ -83,10 +87,61 @@ const mappingBehaviors = [
} }
]; ];
const schema = z
.object({
awsRegion: z.string().trim().min(1, { message: "AWS region is required" }),
secretPath: z.string().trim().min(1, { message: "Secret path is required" }),
sourceEnvironment: z.string().trim().min(1, { message: "Source environment is required" }),
secretPrefix: z.string().default(""),
secretName: z.string().trim().min(1).optional(),
mappingBehavior: z.nativeEnum(IntegrationMappingBehavior),
kmsKeyId: z.string().optional(),
shouldTag: z.boolean().optional(),
tags: z
.object({
key: z.string(),
value: z.string()
})
.array()
})
.refine(
(val) =>
val.mappingBehavior === IntegrationMappingBehavior.ONE_TO_ONE ||
(val.mappingBehavior === IntegrationMappingBehavior.MANY_TO_ONE &&
val.secretName &&
val.secretName !== ""),
{
message: "Secret name must be defined for many-to-one integrations",
path: ["secretName"]
}
);
type TFormSchema = z.infer<typeof schema>;
export default function AWSSecretManagerCreateIntegrationPage() { export default function AWSSecretManagerCreateIntegrationPage() {
const router = useRouter(); const router = useRouter();
const { mutateAsync } = useCreateIntegration(); const { mutateAsync } = useCreateIntegration();
const {
control,
setValue,
handleSubmit,
watch,
formState: { isSubmitting }
} = useForm<TFormSchema>({
resolver: zodResolver(schema),
defaultValues: {
shouldTag: false,
secretPath: "/",
secretPrefix: "",
mappingBehavior: IntegrationMappingBehavior.MANY_TO_ONE,
tags: []
}
});
const shouldTagState = watch("shouldTag");
const selectedSourceEnvironment = watch("sourceEnvironment");
const selectedAWSRegion = watch("awsRegion");
const selectedMappingBehavior = watch("mappingBehavior");
const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]); const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]);
const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? ""); const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? "");
@@ -94,25 +149,6 @@ export default function AWSSecretManagerCreateIntegrationPage() {
(integrationAuthId as string) ?? "" (integrationAuthId as string) ?? ""
); );
const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState("");
const [secretPath, setSecretPath] = useState("/");
const [selectedAWSRegion, setSelectedAWSRegion] = useState("");
const [selectedMappingBehavior, setSelectedMappingBehavior] = useState(
IntegrationMappingBehavior.MANY_TO_ONE
);
const [targetSecretName, setTargetSecretName] = useState("");
const [targetSecretNameErrorText, setTargetSecretNameErrorText] = useState("");
const [tagKey, setTagKey] = useState("");
const [tagValue, setTagValue] = useState("");
const [kmsKeyId, setKmsKeyId] = useState("");
const [secretPrefix, setSecretPrefix] = useState("");
// const [path, setPath] = useState('');
// const [pathErrorText, setPathErrorText] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [shouldTag, setShouldTag] = useState(false);
const { data: integrationAuthAwsKmsKeys, isLoading: isIntegrationAuthAwsKmsKeysLoading } = const { data: integrationAuthAwsKmsKeys, isLoading: isIntegrationAuthAwsKmsKeysLoading } =
useGetIntegrationAuthAwsKmsKeys({ useGetIntegrationAuthAwsKmsKeys({
integrationAuthId: String(integrationAuthId), integrationAuthId: String(integrationAuthId),
@@ -121,63 +157,46 @@ export default function AWSSecretManagerCreateIntegrationPage() {
useEffect(() => { useEffect(() => {
if (workspace) { if (workspace) {
setSelectedSourceEnvironment(workspace.environments[0].slug); setValue("sourceEnvironment", workspace.environments[0].slug);
setSelectedAWSRegion(awsRegions[0].slug); setValue("awsRegion", awsRegions[0].slug);
} }
}, [workspace]); }, [workspace]);
// const isValidAWSPath = (path: string) => { const handleButtonClick = async ({
// const pattern = /^\/[\w./]+\/$/; secretName,
// return pattern.test(path) && path.length <= 2048; sourceEnvironment,
// } awsRegion,
secretPath,
const handleButtonClick = async () => { shouldTag,
tags,
secretPrefix,
kmsKeyId,
mappingBehavior
}: TFormSchema) => {
try { try {
if (!selectedMappingBehavior) {
return;
}
if (
selectedMappingBehavior === IntegrationMappingBehavior.MANY_TO_ONE &&
targetSecretName.trim() === ""
) {
setTargetSecretName("Secret name cannot be blank");
return;
}
if (!integrationAuth?.id) return; if (!integrationAuth?.id) return;
setIsLoading(true);
await mutateAsync({ await mutateAsync({
integrationAuthId: integrationAuth?.id, integrationAuthId: integrationAuth?.id,
isActive: true, isActive: true,
app: targetSecretName.trim(), app: secretName,
sourceEnvironment: selectedSourceEnvironment, sourceEnvironment,
region: selectedAWSRegion, region: awsRegion,
secretPath, secretPath,
metadata: { metadata: {
...(shouldTag ...(shouldTag
? { ? {
secretAWSTag: [ secretAWSTag: tags
{
key: tagKey,
value: tagValue
}
]
} }
: {}), : {}),
...(secretPrefix && { secretPrefix }), ...(secretPrefix && { secretPrefix }),
...(kmsKeyId && { kmsKeyId }), ...(kmsKeyId && { kmsKeyId }),
mappingBehavior: selectedMappingBehavior mappingBehavior
} }
}); });
setIsLoading(false);
setTargetSecretNameErrorText("");
router.push(`/integrations/${localStorage.getItem("projectData.id")}`); router.push(`/integrations/${localStorage.getItem("projectData.id")}`);
} catch (err) { } catch (err) {
setIsLoading(false);
console.error(err); console.error(err);
} }
}; };
@@ -191,13 +210,14 @@ export default function AWSSecretManagerCreateIntegrationPage() {
<title>Set Up AWS Secrets Manager Integration</title> <title>Set Up AWS Secrets Manager Integration</title>
<link rel="icon" href="/infisical.ico" /> <link rel="icon" href="/infisical.ico" />
</Head> </Head>
<form onSubmit={handleSubmit(handleButtonClick)}>
<Card className="max-w-lg rounded-md border border-mineshaft-600"> <Card className="max-w-lg rounded-md border border-mineshaft-600">
<CardTitle <CardTitle
className="px-6 text-left text-xl" className="px-6 text-left text-xl"
subTitle="Choose which environment in Infisical you want to sync to secerts in AWS Secrets Manager." subTitle="Choose which environment in Infisical you want to sync to secerts in AWS Secrets Manager."
> >
<div className="flex flex-row items-center"> <div className="flex flex-row items-center">
<div className="inline flex items-center"> <div className="flex items-center">
<Image <Image
src="/images/integrations/Amazon Web Services.png" src="/images/integrations/Amazon Web Services.png"
height={35} height={35}
@@ -206,7 +226,10 @@ export default function AWSSecretManagerCreateIntegrationPage() {
/> />
</div> </div>
<span className="ml-1.5">AWS Secrets Manager Integration </span> <span className="ml-1.5">AWS Secrets Manager Integration </span>
<Link href="https://infisical.com/docs/integrations/cloud/aws-secret-manager" passHref> <Link
href="https://infisical.com/docs/integrations/cloud/aws-secret-manager"
passHref
>
<a target="_blank" rel="noopener noreferrer"> <a target="_blank" rel="noopener noreferrer">
<div className="ml-2 mb-1 inline-block cursor-default rounded-md bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] text-sm text-yellow opacity-80 hover:opacity-100"> <div className="ml-2 mb-1 inline-block cursor-default rounded-md bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] text-sm text-yellow opacity-80 hover:opacity-100">
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5" /> <FontAwesomeIcon icon={faBookOpen} className="mr-1.5" />
@@ -235,37 +258,62 @@ export default function AWSSecretManagerCreateIntegrationPage() {
animate={{ opacity: 1, translateX: 0 }} animate={{ opacity: 1, translateX: 0 }}
exit={{ opacity: 0, translateX: 30 }} exit={{ opacity: 0, translateX: 30 }}
> >
<FormControl label="Project Environment"> <Controller
control={control}
name="sourceEnvironment"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Project Environment"
errorText={error?.message}
isError={Boolean(error)}
>
<Select <Select
value={selectedSourceEnvironment}
onValueChange={(val) => setSelectedSourceEnvironment(val)}
className="w-full border border-mineshaft-500" className="w-full border border-mineshaft-500"
dropdownContainerClassName="max-w-full"
value={field.value}
onValueChange={(val) => {
field.onChange(val);
}}
> >
{workspace?.environments.map((sourceEnvironment) => ( {workspace?.environments.map((sourceEnvironment) => (
<SelectItem <SelectItem
value={sourceEnvironment.slug} value={sourceEnvironment.slug}
key={`flyio-environment-${sourceEnvironment.slug}`} key={`source-environment-${sourceEnvironment.slug}`}
> >
{sourceEnvironment.name} {sourceEnvironment.name}
</SelectItem> </SelectItem>
))} ))}
</Select> </Select>
</FormControl> </FormControl>
<FormControl label="Secrets Path"> )}
<Input
value={secretPath}
onChange={(evt) => setSecretPath(evt.target.value)}
placeholder="Provide a path, default is /"
/> />
<Controller
control={control}
name="secretPath"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Secrets Path"
errorText={error?.message}
isError={Boolean(error)}
>
<SecretPathInput {...field} />
</FormControl> </FormControl>
<FormControl label="AWS Region"> )}
/>
<Controller
control={control}
name="awsRegion"
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
<FormControl
label="AWS region"
errorText={error?.message}
isError={Boolean(error)}
>
<Select <Select
value={selectedAWSRegion} defaultValue={field.value}
onValueChange={(val) => { onValueChange={(e) => onChange(e)}
setSelectedAWSRegion(val);
setKmsKeyId("");
}}
className="w-full border border-mineshaft-500" className="w-full border border-mineshaft-500"
dropdownContainerClassName="max-w-full"
> >
{awsRegions.map((awsRegion) => ( {awsRegions.map((awsRegion) => (
<SelectItem <SelectItem
@@ -278,40 +326,56 @@ export default function AWSSecretManagerCreateIntegrationPage() {
))} ))}
</Select> </Select>
</FormControl> </FormControl>
<FormControl label="Mapping Behavior"> )}
/>
<Controller
control={control}
name="mappingBehavior"
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
<FormControl
label="Mapping Behavior"
errorText={error?.message}
isError={Boolean(error)}
>
<Select <Select
value={selectedMappingBehavior} defaultValue={field.value}
onValueChange={(val) => { onValueChange={(e) => onChange(e)}
setSelectedMappingBehavior(val as IntegrationMappingBehavior); className="w-full border border-mineshaft-500"
}} dropdownContainerClassName="max-w-full"
className="w-full border border-mineshaft-500 text-left"
> >
{mappingBehaviors.map((option) => ( {mappingBehaviors.map((option) => (
<SelectItem <SelectItem
value={option.value} value={option.value}
className="text-left" className="text-left"
key={`aws-environment-${option.value}`} key={`mapping-behavior-${option.value}`}
> >
{option.label} {option.label}
</SelectItem> </SelectItem>
))} ))}
</Select> </Select>
</FormControl> </FormControl>
)}
/>
{selectedMappingBehavior === IntegrationMappingBehavior.MANY_TO_ONE && ( {selectedMappingBehavior === IntegrationMappingBehavior.MANY_TO_ONE && (
<Controller
control={control}
name="secretName"
render={({ field, fieldState: { error } }) => (
<FormControl <FormControl
label="AWS SM Secret Name" label="AWS SM Secret Name"
errorText={targetSecretNameErrorText} errorText={error?.message}
isError={targetSecretNameErrorText !== "" ?? false} isError={Boolean(error)}
> >
<Input <Input
placeholder={`${workspace.name placeholder={`${workspace.name
.toLowerCase() .toLowerCase()
.replace(/ /g, "-")}/${selectedSourceEnvironment}`} .replace(/ /g, "-")}/${selectedSourceEnvironment}`}
value={targetSecretName} {...field}
onChange={(e) => setTargetSecretName(e.target.value)}
/> />
</FormControl> </FormControl>
)} )}
/>
)}
</motion.div> </motion.div>
</TabPanel> </TabPanel>
<TabPanel value={TabSections.Options}> <TabPanel value={TabSections.Options}>
@@ -323,49 +387,80 @@ export default function AWSSecretManagerCreateIntegrationPage() {
exit={{ opacity: 0, translateX: 30 }} exit={{ opacity: 0, translateX: 30 }}
> >
<div className="mt-2 ml-1"> <div className="mt-2 ml-1">
<Controller
control={control}
name="shouldTag"
render={({ field: { onChange, value } }) => (
<Switch <Switch
id="tag-aws" id="tag-aws"
onCheckedChange={() => setShouldTag(!shouldTag)} onCheckedChange={(isChecked) => onChange(isChecked)}
isChecked={shouldTag} isChecked={value}
> >
Tag in AWS Secrets Manager Tag in AWS Secrets Manager
</Switch> </Switch>
)}
/>
</div> </div>
{shouldTag && ( {shouldTagState && (
<div className="mt-4 flex justify-between"> <div className="mt-4 flex justify-between">
<FormControl label="Tag Key"> <Controller
<Input control={control}
placeholder="managed-by" name="tags.0.key"
value={tagKey} render={({ field, fieldState: { error } }) => (
onChange={(e) => setTagKey(e.target.value)} <FormControl
/> label="Tag Key"
errorText={error?.message}
isError={Boolean(error)}
>
<Input placeholder="managed-by" {...field} />
</FormControl> </FormControl>
<FormControl label="Tag Value"> )}
<Input
placeholder="infisical"
value={tagValue}
onChange={(e) => setTagValue(e.target.value)}
/> />
<Controller
control={control}
name="tags.0.value"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Tag Value"
errorText={error?.message}
isError={Boolean(error)}
>
<Input placeholder="infisical" {...field} />
</FormControl> </FormControl>
)}
/>
</div> </div>
)} )}
<FormControl label="Secret Prefix" className="mt-4"> <Controller
<Input control={control}
value={secretPrefix} name="secretPrefix"
onChange={(e) => setSecretPrefix(e.target.value)} render={({ field, fieldState: { error } }) => (
placeholder="INFISICAL_" <FormControl
/> label="Secret Prefix"
errorText={error?.message}
isError={Boolean(error)}
className="mt-4"
>
<Input placeholder="INFISICAL_" {...field} />
</FormControl> </FormControl>
)}
/>
<FormControl label="Encryption Key" className="mt-4"> <Controller
control={control}
name="kmsKeyId"
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
<FormControl
label="Encryption Key"
errorText={error?.message}
isError={Boolean(error)}
>
<Select <Select
value={kmsKeyId} defaultValue={field.value}
onValueChange={(e) => { onValueChange={(e) => onChange(e)}
if (e === "no-keys") return;
setKmsKeyId(e);
}}
className="w-full border border-mineshaft-500" className="w-full border border-mineshaft-500"
dropdownContainerClassName="max-w-full"
> >
{integrationAuthAwsKmsKeys?.length ? ( {integrationAuthAwsKmsKeys?.length ? (
integrationAuthAwsKmsKeys.map((key) => { integrationAuthAwsKmsKeys.map((key) => {
@@ -386,15 +481,17 @@ export default function AWSSecretManagerCreateIntegrationPage() {
)} )}
</Select> </Select>
</FormControl> </FormControl>
)}
/>
</motion.div> </motion.div>
</TabPanel> </TabPanel>
</Tabs> </Tabs>
<Button <Button
onClick={handleButtonClick}
color="mineshaft" color="mineshaft"
variant="outline_bg" variant="outline_bg"
type="submit"
className="mb-6 mt-2 ml-auto mr-6" className="mb-6 mt-2 ml-auto mr-6"
isLoading={isLoading} isLoading={isSubmitting}
> >
Create Integration Create Integration
</Button> </Button>
@@ -411,6 +508,7 @@ export default function AWSSecretManagerCreateIntegrationPage() {
Infisical. Infisical.
</span> </span>
</div> </div>
</form>
</div> </div>
) : ( ) : (
<div className="flex h-full w-full items-center justify-center"> <div className="flex h-full w-full items-center justify-center">