Merge pull request #4889 from Infisical/feature/mongodb-secret-rotation

feature(secret-rotation): add mongodb app connection and secret rotation
This commit is contained in:
Victor Hugo dos Santos
2025-12-08 18:48:43 -03:00
committed by GitHub
64 changed files with 1447 additions and 26 deletions
@@ -67,6 +67,7 @@ const Content = ({ secretRotation }: ContentProps) => {
case SecretRotation.MySqlCredentials:
case SecretRotation.MsSqlCredentials:
case SecretRotation.OracleDBCredentials:
case SecretRotation.MongoDBCredentials:
Component = (
<ViewSqlCredentialsRotationGeneratedCredentials
generatedCredentialsResponse={generatedCredentialsResponse}
@@ -1,5 +1,6 @@
import { CredentialDisplay } from "@app/components/secret-rotations-v2/ViewSecretRotationV2GeneratedCredentials/shared/CredentialDisplay";
import { ViewRotationGeneratedCredentialsDisplay } from "@app/components/secret-rotations-v2/ViewSecretRotationV2GeneratedCredentials/shared/ViewRotationGeneratedCredentialsDisplay";
import { TMongoDBCredentialsRotationGeneratedCredentialsResponse } from "@app/hooks/api/secretRotationsV2/types/mongodb-credentials-rotation";
import { TMsSqlCredentialsRotationGeneratedCredentialsResponse } from "@app/hooks/api/secretRotationsV2/types/mssql-credentials-rotation";
import { TMySqlCredentialsRotationGeneratedCredentialsResponse } from "@app/hooks/api/secretRotationsV2/types/mysql-credentials-rotation";
import { TOracleDBCredentialsRotationGeneratedCredentialsResponse } from "@app/hooks/api/secretRotationsV2/types/oracledb-credentials-rotation";
@@ -10,7 +11,8 @@ type Props = {
| TMsSqlCredentialsRotationGeneratedCredentialsResponse
| TMySqlCredentialsRotationGeneratedCredentialsResponse
| TOracleDBCredentialsRotationGeneratedCredentialsResponse
| TPostgresCredentialsRotationGeneratedCredentialsResponse;
| TPostgresCredentialsRotationGeneratedCredentialsResponse
| TMongoDBCredentialsRotationGeneratedCredentialsResponse;
};
export const ViewSqlCredentialsRotationGeneratedCredentials = ({
@@ -21,7 +21,8 @@ const COMPONENT_MAP: Record<SecretRotation, React.FC> = {
[SecretRotation.LdapPassword]: LdapPasswordRotationParametersFields,
[SecretRotation.AwsIamUserSecret]: AwsIamUserSecretRotationParametersFields,
[SecretRotation.OktaClientSecret]: OktaClientSecretRotationParametersFields,
[SecretRotation.RedisCredentials]: RedisCredentialsRotationParametersFields
[SecretRotation.RedisCredentials]: RedisCredentialsRotationParametersFields,
[SecretRotation.MongoDBCredentials]: SqlCredentialsRotationParametersFields
};
export const SecretRotationV2ParametersFields = () => {
@@ -24,7 +24,8 @@ const COMPONENT_MAP: Record<SecretRotation, React.FC> = {
[SecretRotation.LdapPassword]: LdapPasswordRotationReviewFields,
[SecretRotation.AwsIamUserSecret]: AwsIamUserSecretRotationReviewFields,
[SecretRotation.OktaClientSecret]: OktaClientSecretRotationReviewFields,
[SecretRotation.RedisCredentials]: RedisCredentialsRotationReviewFields
[SecretRotation.RedisCredentials]: RedisCredentialsRotationReviewFields,
[SecretRotation.MongoDBCredentials]: SqlCredentialsRotationReviewFields
};
export const SecretRotationV2ReviewFields = () => {
@@ -21,7 +21,8 @@ const COMPONENT_MAP: Record<SecretRotation, React.FC> = {
[SecretRotation.LdapPassword]: LdapPasswordRotationSecretsMappingFields,
[SecretRotation.AwsIamUserSecret]: AwsIamUserSecretRotationSecretsMappingFields,
[SecretRotation.OktaClientSecret]: OktaClientSecretRotationSecretsMappingFields,
[SecretRotation.RedisCredentials]: RedisCredentialsRotationSecretsMappingFields
[SecretRotation.RedisCredentials]: RedisCredentialsRotationSecretsMappingFields,
[SecretRotation.MongoDBCredentials]: SqlCredentialsRotationSecretsMappingFields
};
export const SecretRotationV2SecretsMappingFields = () => {
@@ -4,6 +4,7 @@ import { Auth0ClientSecretRotationSchema } from "@app/components/secret-rotation
import { AwsIamUserSecretRotationSchema } from "@app/components/secret-rotations-v2/forms/schemas/aws-iam-user-secret-rotation-schema";
import { AzureClientSecretRotationSchema } from "@app/components/secret-rotations-v2/forms/schemas/azure-client-secret-rotation-schema";
import { LdapPasswordRotationSchema } from "@app/components/secret-rotations-v2/forms/schemas/ldap-password-rotation-schema";
import { MongoDBCredentialsRotationSchema } from "@app/components/secret-rotations-v2/forms/schemas/mongodb-credentials-rotation-schema";
import { MsSqlCredentialsRotationSchema } from "@app/components/secret-rotations-v2/forms/schemas/mssql-credentials-rotation-schema";
import { MySqlCredentialsRotationSchema } from "@app/components/secret-rotations-v2/forms/schemas/mysql-credentials-rotation-schema";
import { PostgresCredentialsRotationSchema } from "@app/components/secret-rotations-v2/forms/schemas/postgres-credentials-rotation-schema";
@@ -27,7 +28,8 @@ export const SecretRotationV2FormSchema = (isUpdate: boolean) =>
LdapPasswordRotationSchema,
AwsIamUserSecretRotationSchema,
OktaClientSecretRotationSchema,
RedisCredentialsRotationSchema
RedisCredentialsRotationSchema,
MongoDBCredentialsRotationSchema
]),
z.object({ id: z.string().optional() })
)
@@ -0,0 +1,12 @@
import { z } from "zod";
import { BaseSecretRotationSchema } from "@app/components/secret-rotations-v2/forms/schemas/base-secret-rotation-v2-schema";
import { SqlCredentialsRotationSchema } from "@app/components/secret-rotations-v2/forms/schemas/shared";
import { SecretRotation } from "@app/hooks/api/secretRotationsV2";
export const MongoDBCredentialsRotationSchema = z
.object({
type: z.literal(SecretRotation.MongoDBCredentials)
})
.merge(SqlCredentialsRotationSchema)
.merge(BaseSecretRotationSchema);
+3
View File
@@ -31,6 +31,7 @@ import {
HCVaultConnectionMethod,
HumanitecConnectionMethod,
LdapConnectionMethod,
MongoDBConnectionMethod,
MsSqlConnectionMethod,
MySqlConnectionMethod,
OktaConnectionMethod,
@@ -129,6 +130,7 @@ export const APP_CONNECTION_MAP: Record<
[AppConnection.Northflank]: { name: "Northflank", image: "Northflank.png" },
[AppConnection.Okta]: { name: "Okta", image: "Okta.png" },
[AppConnection.Redis]: { name: "Redis", image: "Redis.png" },
[AppConnection.MongoDB]: { name: "MongoDB", image: "MongoDB.png" },
[AppConnection.LaravelForge]: {
name: "Laravel Forge",
image: "Laravel Forge.png",
@@ -181,6 +183,7 @@ export const getAppConnectionMethodDetails = (method: TAppConnection["method"])
case OracleDBConnectionMethod.UsernameAndPassword:
case AzureADCSConnectionMethod.UsernamePassword:
case RedisConnectionMethod.UsernameAndPassword:
case MongoDBConnectionMethod.UsernameAndPassword:
return { name: "Username & Password", icon: faLock };
case HCVaultConnectionMethod.AccessToken:
case TeamCityConnectionMethod.AccessToken:
+9 -2
View File
@@ -54,6 +54,11 @@ export const SECRET_ROTATION_MAP: Record<
name: "Redis Credentials",
image: "Redis.png",
size: 50
},
[SecretRotation.MongoDBCredentials]: {
name: "MongoDB Credentials",
image: "MongoDB.png",
size: 50
}
};
@@ -67,7 +72,8 @@ export const SECRET_ROTATION_CONNECTION_MAP: Record<SecretRotation, AppConnectio
[SecretRotation.LdapPassword]: AppConnection.LDAP,
[SecretRotation.AwsIamUserSecret]: AppConnection.AWS,
[SecretRotation.OktaClientSecret]: AppConnection.Okta,
[SecretRotation.RedisCredentials]: AppConnection.Redis
[SecretRotation.RedisCredentials]: AppConnection.Redis,
[SecretRotation.MongoDBCredentials]: AppConnection.MongoDB
};
// if a rotation can potentially have downtime due to rotating a single credential set this to false
@@ -81,7 +87,8 @@ export const IS_ROTATION_DUAL_CREDENTIALS: Record<SecretRotation, boolean> = {
[SecretRotation.LdapPassword]: false,
[SecretRotation.AwsIamUserSecret]: true,
[SecretRotation.OktaClientSecret]: true,
[SecretRotation.RedisCredentials]: true
[SecretRotation.RedisCredentials]: true,
[SecretRotation.MongoDBCredentials]: true
};
export const getRotateAtLocal = ({ hours, minutes }: TSecretRotationV2["rotateAtUtc"]) => {
@@ -40,6 +40,7 @@ export enum AppConnection {
Northflank = "northflank",
Okta = "okta",
Redis = "redis",
MongoDB = "mongodb",
LaravelForge = "laravel-forge",
Chef = "chef"
}
@@ -184,6 +184,10 @@ export type TRedisConnectionOption = TAppConnectionOptionBase & {
app: AppConnection.Redis;
};
export type TMongoDBConnectionOption = TAppConnectionOptionBase & {
app: AppConnection.MongoDB;
};
export type TDNSMadeEasyConnectionOption = TAppConnectionOptionBase & {
app: AppConnection.DNSMadeEasy;
};
@@ -229,6 +233,8 @@ export type TAppConnectionOption =
| TOktaConnectionOption
| TAzureAdCsConnectionOption
| TLaravelForgeConnectionOption
| TRedisConnectionOption
| TMongoDBConnectionOption
| TChefConnectionOption
| TDNSMadeEasyConnectionOption;
@@ -274,6 +280,7 @@ export type TAppConnectionOptionMap = {
[AppConnection.Okta]: TOktaConnectionOption;
[AppConnection.AzureADCS]: TAzureAdCsConnectionOption;
[AppConnection.Redis]: TRedisConnectionOption;
[AppConnection.MongoDB]: TMongoDBConnectionOption;
[AppConnection.LaravelForge]: TLaravelForgeConnectionOption;
[AppConnection.Chef]: TChefConnectionOption;
};
@@ -26,6 +26,7 @@ import { THerokuConnection } from "./heroku-connection";
import { THumanitecConnection } from "./humanitec-connection";
import { TLaravelForgeConnection } from "./laravel-forge-connection";
import { TLdapConnection } from "./ldap-connection";
import { TMongoDBConnection } from "./mongodb-connection";
import { TMsSqlConnection } from "./mssql-connection";
import { TMySqlConnection } from "./mysql-connection";
import { TNetlifyConnection } from "./netlify-connection";
@@ -69,6 +70,7 @@ export * from "./heroku-connection";
export * from "./humanitec-connection";
export * from "./laravel-forge-connection";
export * from "./ldap-connection";
export * from "./mongodb-connection";
export * from "./mssql-connection";
export * from "./mysql-connection";
export * from "./netlify-connection";
@@ -129,6 +131,7 @@ export type TAppConnection =
| TNorthflankConnection
| TOktaConnection
| TRedisConnection
| TMongoDBConnection
| TChefConnection
| TDNSMadeEasyConnection;
@@ -0,0 +1,22 @@
import { AppConnection } from "@app/hooks/api/appConnections/enums";
import { TRootAppConnection } from "@app/hooks/api/appConnections/types/root-connection";
export enum MongoDBConnectionMethod {
UsernameAndPassword = "username-and-password"
}
export type TMongoDBConnectionCredentials = {
host: string;
port: number;
username: string;
password: string;
database: string;
tlsEnabled: boolean;
tlsRejectUnauthorized: boolean;
tlsCertificate?: string;
};
export type TMongoDBConnection = TRootAppConnection & { app: AppConnection.MongoDB } & {
method: MongoDBConnectionMethod.UsernameAndPassword;
credentials: TMongoDBConnectionCredentials;
};
@@ -8,7 +8,8 @@ export enum SecretRotation {
LdapPassword = "ldap-password",
AwsIamUserSecret = "aws-iam-user-secret",
OktaClientSecret = "okta-client-secret",
RedisCredentials = "redis-credentials"
RedisCredentials = "redis-credentials",
MongoDBCredentials = "mongodb-credentials"
}
export enum SecretRotationStatus {
@@ -31,6 +31,11 @@ import { TSqlCredentialsRotationOption } from "@app/hooks/api/secretRotationsV2/
import { SecretV3RawSanitized } from "@app/hooks/api/secrets/types";
import { DiscriminativePick } from "@app/types";
import {
TMongoDBCredentialsRotation,
TMongoDBCredentialsRotationGeneratedCredentialsResponse,
TMongoDBCredentialsRotationOption
} from "./mongodb-credentials-rotation";
import {
TMySqlCredentialsRotation,
TMySqlCredentialsRotationGeneratedCredentialsResponse
@@ -61,6 +66,7 @@ export type TSecretRotationV2 = (
| TAwsIamUserSecretRotation
| TOktaClientSecretRotation
| TRedisCredentialsRotation
| TMongoDBCredentialsRotation
) & {
secrets: (SecretV3RawSanitized | null)[];
};
@@ -72,7 +78,8 @@ export type TSecretRotationV2Option =
| TLdapPasswordRotationOption
| TAwsIamUserSecretRotationOption
| TOktaClientSecretRotationOption
| TRedisCredentialsRotationOption;
| TRedisCredentialsRotationOption
| TMongoDBCredentialsRotationOption;
export type TListSecretRotationV2Options = { secretRotationOptions: TSecretRotationV2Option[] };
@@ -88,7 +95,8 @@ export type TViewSecretRotationGeneratedCredentialsResponse =
| TLdapPasswordRotationGeneratedCredentialsResponse
| TAwsIamUserSecretRotationGeneratedCredentialsResponse
| TOktaClientSecretRotationGeneratedCredentialsResponse
| TRedisCredentialsRotationGeneratedCredentialsResponse;
| TRedisCredentialsRotationGeneratedCredentialsResponse
| TMongoDBCredentialsRotationGeneratedCredentialsResponse;
export type TCreateSecretRotationV2DTO = DiscriminativePick<
TSecretRotationV2,
@@ -142,6 +150,7 @@ export type TSecretRotationOptionMap = {
[SecretRotation.AwsIamUserSecret]: TAwsIamUserSecretRotationOption;
[SecretRotation.OktaClientSecret]: TOktaClientSecretRotationOption;
[SecretRotation.RedisCredentials]: TRedisCredentialsRotationOption;
[SecretRotation.MongoDBCredentials]: TMongoDBCredentialsRotationOption;
};
export type TSecretRotationGeneratedCredentialsResponseMap = {
@@ -155,4 +164,5 @@ export type TSecretRotationGeneratedCredentialsResponseMap = {
[SecretRotation.AwsIamUserSecret]: TAwsIamUserSecretRotationGeneratedCredentialsResponse;
[SecretRotation.OktaClientSecret]: TOktaClientSecretRotationGeneratedCredentialsResponse;
[SecretRotation.RedisCredentials]: TRedisCredentialsRotationGeneratedCredentialsResponse;
[SecretRotation.MongoDBCredentials]: TMongoDBCredentialsRotationGeneratedCredentialsResponse;
};
@@ -0,0 +1,28 @@
import { AppConnection } from "@app/hooks/api/appConnections/enums";
import { SecretRotation } from "@app/hooks/api/secretRotationsV2";
import {
TSecretRotationV2Base,
TSecretRotationV2GeneratedCredentialsResponseBase,
TSqlCredentialsRotationGeneratedCredentials,
TSqlCredentialsRotationProperties
} from "@app/hooks/api/secretRotationsV2/types/shared";
export type TMongoDBCredentialsRotation = TSecretRotationV2Base & {
type: SecretRotation.MongoDBCredentials;
} & TSqlCredentialsRotationProperties;
export type TMongoDBCredentialsRotationGeneratedCredentialsResponse =
TSecretRotationV2GeneratedCredentialsResponseBase<
SecretRotation.MongoDBCredentials,
TSqlCredentialsRotationGeneratedCredentials
>;
export type TMongoDBCredentialsRotationOption = {
name: string;
type: SecretRotation.MongoDBCredentials;
connection: AppConnection.MongoDB;
template: {
createUserStatement: string;
secretsMapping: TMongoDBCredentialsRotation["secretsMapping"];
};
};
@@ -35,6 +35,7 @@ import { HerokuConnectionForm } from "./HerokuAppConnectionForm";
import { HumanitecConnectionForm } from "./HumanitecConnectionForm";
import { LaravelForgeConnectionForm } from "./LaravelForgeConnectionForm";
import { LdapConnectionForm } from "./LdapConnectionForm";
import { MongoDBConnectionForm } from "./MongoDBConnectionForm";
import { MsSqlConnectionForm } from "./MsSqlConnectionForm";
import { MySqlConnectionForm } from "./MySqlConnectionForm";
import { NetlifyConnectionForm } from "./NetlifyConnectionForm";
@@ -173,6 +174,8 @@ const CreateForm = ({ app, onComplete, projectId }: CreateFormProps) => {
return <OktaConnectionForm onSubmit={onSubmit} />;
case AppConnection.Redis:
return <RedisConnectionForm onSubmit={onSubmit} />;
case AppConnection.MongoDB:
return <MongoDBConnectionForm onSubmit={onSubmit} />;
default:
throw new Error(`Unhandled App ${app}`);
}
@@ -331,6 +334,8 @@ const UpdateForm = ({ appConnection, onComplete }: UpdateFormProps) => {
return <OktaConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
case AppConnection.Redis:
return <RedisConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
case AppConnection.MongoDB:
return <MongoDBConnectionForm onSubmit={onSubmit} appConnection={appConnection} />;
default:
throw new Error(`Unhandled App ${(appConnection as TAppConnection).app}`);
}
@@ -0,0 +1,326 @@
import { useState } from "react";
import { Controller, FormProvider, useForm } from "react-hook-form";
import { faQuestionCircle } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Tab } from "@headlessui/react";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import {
Button,
FormControl,
Input,
ModalClose,
SecretInput,
Select,
SelectItem,
Switch,
TextArea,
Tooltip
} from "@app/components/v2";
import { APP_CONNECTION_MAP, getAppConnectionMethodDetails } from "@app/helpers/appConnections";
import { MongoDBConnectionMethod, TMongoDBConnection } from "@app/hooks/api/appConnections";
import { AppConnection } from "@app/hooks/api/appConnections/enums";
import {
genericAppConnectionFieldsSchema,
GenericAppConnectionsFields
} from "./GenericAppConnectionFields";
type Props = {
appConnection?: TMongoDBConnection;
onSubmit: (formData: FormData) => Promise<void>;
};
const rootSchema = genericAppConnectionFieldsSchema.extend({
app: z.literal(AppConnection.MongoDB)
});
const formSchema = z.discriminatedUnion("method", [
rootSchema.extend({
method: z.literal(MongoDBConnectionMethod.UsernameAndPassword),
credentials: z.object({
host: z.string().trim().min(1, "Host required"),
port: z.coerce.number().default(27017),
username: z.string().trim().min(1, "Username required"),
password: z.string().trim().min(1, "Password required"),
database: z.string().trim().min(1, "Database required"),
tlsEnabled: z.boolean().default(false),
tlsRejectUnauthorized: z.boolean().default(true),
tlsCertificate: z
.string()
.trim()
.transform((value) => value || undefined)
.optional()
})
})
]);
type FormData = z.infer<typeof formSchema>;
export const MongoDBConnectionForm = ({ appConnection, onSubmit }: Props) => {
const isUpdate = Boolean(appConnection);
const [selectedTabIndex, setSelectedTabIndex] = useState(0);
const form = useForm<FormData>({
resolver: zodResolver(formSchema),
defaultValues: appConnection ?? {
app: AppConnection.MongoDB,
method: MongoDBConnectionMethod.UsernameAndPassword,
credentials: {
host: "",
port: 27017,
username: "",
password: "",
database: "",
tlsEnabled: false,
tlsRejectUnauthorized: true,
tlsCertificate: undefined
}
}
});
const {
handleSubmit,
watch,
control,
formState: { isSubmitting, isDirty }
} = form;
const tlsEnabled = watch("credentials.tlsEnabled");
return (
<FormProvider {...form}>
<form onSubmit={handleSubmit(onSubmit)}>
{!isUpdate && <GenericAppConnectionsFields />}
<Controller
name="method"
control={control}
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
tooltipText={`The method you would like to use to connect with ${
APP_CONNECTION_MAP[AppConnection.MongoDB].name
}. This field cannot be changed after creation.`}
errorText={error?.message}
isError={Boolean(error?.message)}
label="Method"
>
<Select
isDisabled={isUpdate}
value={value}
onValueChange={(val) => onChange(val)}
className="w-full border border-mineshaft-500"
position="popper"
dropdownContainerClassName="max-w-none"
>
{Object.values(MongoDBConnectionMethod).map((method) => {
return (
<SelectItem value={method} key={method}>
{getAppConnectionMethodDetails(method).name}{" "}
</SelectItem>
);
})}
</Select>
</FormControl>
)}
/>
<Tab.Group selectedIndex={selectedTabIndex} onChange={setSelectedTabIndex}>
<Tab.List className="-pb-1 mb-6 w-full border-b-2 border-mineshaft-600">
<Tab
className={({ selected }) =>
`-mb-[0.14rem] px-4 py-2 text-sm font-medium whitespace-nowrap outline-hidden disabled:opacity-60 ${
selected
? "border-b-2 border-mineshaft-300 text-mineshaft-200"
: "text-bunker-300"
}`
}
>
Configuration
</Tab>
<Tab
className={({ selected }) =>
`-mb-[0.14rem] px-4 py-2 text-sm font-medium whitespace-nowrap outline-hidden disabled:opacity-60 ${
selected
? "border-b-2 border-mineshaft-300 text-mineshaft-200"
: "text-bunker-300"
}`
}
>
TLS ({tlsEnabled ? "Enabled" : "Disabled"})
</Tab>
</Tab.List>
<Tab.Panels className="mb-4 rounded-sm border border-mineshaft-600 bg-mineshaft-700/70 p-3 pb-0">
<Tab.Panel>
<div className="mt-[0.675rem] flex items-start gap-2">
<Controller
name="credentials.host"
control={control}
render={({ field, fieldState: { error } }) => (
<FormControl
className="flex-1"
errorText={error?.message}
isError={Boolean(error?.message)}
label="Host"
>
<Input {...field} />
</FormControl>
)}
/>
<Controller
name="credentials.database"
control={control}
render={({ field, fieldState: { error } }) => (
<FormControl
className="flex-1"
errorText={error?.message}
isError={Boolean(error?.message)}
label="Database"
>
<Input {...field} />
</FormControl>
)}
/>
<Controller
name="credentials.port"
control={control}
render={({ field, fieldState: { error } }) => (
<FormControl
className="w-28"
errorText={error?.message}
isError={Boolean(error?.message)}
label="Port"
>
<Input type="number" {...field} />
</FormControl>
)}
/>
</div>
<div className="mb-[0.675rem] flex items-start gap-2">
<Controller
name="credentials.username"
control={control}
render={({ field, fieldState: { error } }) => (
<FormControl
errorText={error?.message}
isError={Boolean(error?.message)}
label="Username"
className="flex-1"
>
<Input {...field} />
</FormControl>
)}
/>
<Controller
name="credentials.password"
control={control}
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
errorText={error?.message}
isError={Boolean(error?.message)}
label="Password"
className="flex-1"
>
<SecretInput
containerClassName="text-gray-400 w-full group-focus-within:border-primary-400/50! border border-mineshaft-500 bg-mineshaft-900 px-2.5 py-1.5"
value={value}
onChange={(e) => onChange(e.target.value)}
/>
</FormControl>
)}
/>
</div>
</Tab.Panel>
<Tab.Panel>
<Controller
name="credentials.tlsEnabled"
control={control}
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl isError={Boolean(error?.message)} errorText={error?.message}>
<Switch
className="bg-mineshaft-400/50 shadow-inner data-[state=checked]:bg-green/80"
id="tls-enabled"
thumbClassName="bg-mineshaft-800"
isChecked={value}
onCheckedChange={onChange}
>
Enable TLS
</Switch>
</FormControl>
)}
/>
<Controller
name="credentials.tlsCertificate"
control={control}
render={({ field, fieldState: { error } }) => (
<FormControl
errorText={error?.message}
isError={Boolean(error?.message)}
className={tlsEnabled ? "" : "opacity-50"}
label="TLS Certificate"
isOptional
>
<TextArea className="h-14 resize-none!" {...field} isDisabled={!tlsEnabled} />
</FormControl>
)}
/>
<Controller
name="credentials.tlsRejectUnauthorized"
control={control}
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
className={tlsEnabled ? "" : "opacity-50"}
isError={Boolean(error?.message)}
errorText={error?.message}
>
<Switch
className="bg-mineshaft-400/50 shadow-inner data-[state=checked]:bg-green/80"
id="tls-reject-unauthorized"
thumbClassName="bg-mineshaft-800"
isChecked={tlsEnabled ? value : false}
onCheckedChange={onChange}
isDisabled={!tlsEnabled}
>
<p className="w-38">
Reject Unauthorized
<Tooltip
className="max-w-md"
content={
<p>
If enabled, Infisical will only connect to the server if it has a
valid, trusted TLS certificate.
</p>
}
>
<FontAwesomeIcon icon={faQuestionCircle} size="sm" className="ml-1" />
</Tooltip>
</p>
</Switch>
</FormControl>
)}
/>
</Tab.Panel>
</Tab.Panels>
</Tab.Group>
<div className="mt-6 flex items-center">
<Button
className="mr-4"
size="sm"
type="submit"
colorSchema="secondary"
isLoading={isSubmitting}
isDisabled={isSubmitting || !isDirty}
>
{isUpdate ? "Update Credentials" : "Connect to Database"}
</Button>
<ModalClose asChild>
<Button colorSchema="secondary" variant="plain">
Cancel
</Button>
</ModalClose>
</div>
</form>
</FormProvider>
);
};