mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-08 04:27:38 +00:00
Fix account creation, remove rotation stuff for now
This commit is contained in:
+119
@@ -0,0 +1,119 @@
|
||||
import { Controller, FormProvider, useForm, useFormContext } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import { Button, FormControl, ModalClose, TextArea } from "@app/components/v2";
|
||||
import { KubernetesAuthMethod, TKubernetesAccount } from "@app/hooks/api/pam";
|
||||
import { UNCHANGED_PASSWORD_SENTINEL } from "@app/hooks/api/pam/constants";
|
||||
|
||||
import { GenericAccountFields, genericAccountFieldsSchema } from "./GenericAccountFields";
|
||||
import { rotateAccountFieldsSchema } from "./RotateAccountFields";
|
||||
|
||||
type Props = {
|
||||
account?: TKubernetesAccount;
|
||||
onSubmit: (formData: FormData) => Promise<void>;
|
||||
};
|
||||
|
||||
const KubernetesServiceAccountTokenCredentialsSchema = z.object({
|
||||
authMethod: z.literal(KubernetesAuthMethod.ServiceAccountToken),
|
||||
serviceAccountToken: z.string().trim().min(1, "Service account token is required")
|
||||
});
|
||||
|
||||
const formSchema = genericAccountFieldsSchema.extend(rotateAccountFieldsSchema.shape).extend({
|
||||
credentials: KubernetesServiceAccountTokenCredentialsSchema
|
||||
});
|
||||
|
||||
type FormData = z.infer<typeof formSchema>;
|
||||
|
||||
const KubernetesAccountFields = ({ isUpdate }: { isUpdate: boolean }) => {
|
||||
const { control } = useFormContext<FormData>();
|
||||
|
||||
return (
|
||||
<div className="mb-4 rounded-sm border border-mineshaft-600 bg-mineshaft-700/70 p-3">
|
||||
<Controller
|
||||
name="credentials.serviceAccountToken"
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
className="mb-0"
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error?.message)}
|
||||
label="Service Account Token"
|
||||
helperText="The bearer token for the service account"
|
||||
>
|
||||
<TextArea
|
||||
{...field}
|
||||
value={field.value === UNCHANGED_PASSWORD_SENTINEL ? "" : field.value || ""}
|
||||
className="min-h-32 resize-y font-mono text-xs"
|
||||
placeholder={
|
||||
isUpdate && field.value === UNCHANGED_PASSWORD_SENTINEL
|
||||
? "Token unchanged - click to update"
|
||||
: "eyJhbGciOiJSUzI1NiIsImtpZCI6..."
|
||||
}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const KubernetesAccountForm = ({ account, onSubmit }: Props) => {
|
||||
const isUpdate = Boolean(account);
|
||||
|
||||
const form = useForm<FormData>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: account
|
||||
? {
|
||||
...account,
|
||||
credentials: {
|
||||
...account.credentials,
|
||||
serviceAccountToken: UNCHANGED_PASSWORD_SENTINEL
|
||||
}
|
||||
}
|
||||
: {
|
||||
name: "",
|
||||
description: "",
|
||||
credentials: {
|
||||
authMethod: KubernetesAuthMethod.ServiceAccountToken,
|
||||
serviceAccountToken: ""
|
||||
},
|
||||
rotationEnabled: false
|
||||
}
|
||||
});
|
||||
|
||||
const {
|
||||
handleSubmit,
|
||||
formState: { isSubmitting, isDirty }
|
||||
} = form;
|
||||
|
||||
return (
|
||||
<FormProvider {...form}>
|
||||
<form
|
||||
onSubmit={(e) => {
|
||||
handleSubmit(onSubmit)(e);
|
||||
}}
|
||||
>
|
||||
<GenericAccountFields />
|
||||
<KubernetesAccountFields isUpdate={isUpdate} />
|
||||
<div className="mt-6 flex items-center">
|
||||
<Button
|
||||
className="mr-4"
|
||||
size="sm"
|
||||
type="submit"
|
||||
colorSchema="secondary"
|
||||
isLoading={isSubmitting}
|
||||
isDisabled={isSubmitting || !isDirty}
|
||||
>
|
||||
{isUpdate ? "Update Account" : "Create Account"}
|
||||
</Button>
|
||||
<ModalClose asChild>
|
||||
<Button colorSchema="secondary" variant="plain">
|
||||
Cancel
|
||||
</Button>
|
||||
</ModalClose>
|
||||
</div>
|
||||
</form>
|
||||
</FormProvider>
|
||||
);
|
||||
};
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
import { DiscriminativePick } from "@app/types";
|
||||
|
||||
import { PamAccountHeader } from "../PamAccountHeader";
|
||||
import { KubernetesAccountForm } from "./KubernetesAccountForm";
|
||||
import { MySQLAccountForm } from "./MySQLAccountForm";
|
||||
import { PostgresAccountForm } from "./PostgresAccountForm";
|
||||
import { SshAccountForm } from "./SshAccountForm";
|
||||
@@ -70,6 +71,14 @@ const CreateForm = ({
|
||||
return (
|
||||
<SshAccountForm onSubmit={onSubmit} resourceId={resourceId} resourceType={resourceType} />
|
||||
);
|
||||
case PamResourceType.Kubernetes:
|
||||
return (
|
||||
<KubernetesAccountForm
|
||||
onSubmit={onSubmit}
|
||||
resourceId={resourceId}
|
||||
resourceType={resourceType}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
throw new Error(`Unhandled resource: ${resourceType}`);
|
||||
}
|
||||
@@ -100,6 +109,8 @@ const UpdateForm = ({ account, onComplete }: UpdateFormProps) => {
|
||||
return <MySQLAccountForm account={account as any} onSubmit={onSubmit} />;
|
||||
case PamResourceType.SSH:
|
||||
return <SshAccountForm account={account as any} onSubmit={onSubmit} />;
|
||||
case PamResourceType.Kubernetes:
|
||||
return <KubernetesAccountForm account={account as any} onSubmit={onSubmit} />;
|
||||
default:
|
||||
throw new Error(`Unhandled resource: ${account.resource.resourceType}`);
|
||||
}
|
||||
|
||||
-3
@@ -7,7 +7,6 @@ import { KubernetesAuthMethod, PamResourceType, TKubernetesResource } from "@app
|
||||
import { UNCHANGED_PASSWORD_SENTINEL } from "@app/hooks/api/pam/constants";
|
||||
|
||||
import { KubernetesResourceFields } from "./shared/KubernetesResourceFields";
|
||||
import { KubernetesRotateAccountFields } from "./shared/KubernetesRotateAccountFields";
|
||||
import { GenericResourceFields, genericResourceFieldsSchema } from "./GenericResourceFields";
|
||||
|
||||
type Props = {
|
||||
@@ -24,7 +23,6 @@ const KubernetesConnectionDetailsSchema = z.object({
|
||||
|
||||
const KubernetesServiceAccountTokenCredentialsSchema = z.object({
|
||||
authMethod: z.literal(KubernetesAuthMethod.ServiceAccountToken),
|
||||
serviceAccountName: z.string().trim().max(255),
|
||||
serviceAccountToken: z.string().trim().max(10000)
|
||||
});
|
||||
|
||||
@@ -72,7 +70,6 @@ export const KubernetesResourceForm = ({ resource, onSubmit }: Props) => {
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<GenericResourceFields />
|
||||
<KubernetesResourceFields />
|
||||
<KubernetesRotateAccountFields isUpdate={isUpdate} />
|
||||
<div className="mt-6 flex items-center">
|
||||
<Button
|
||||
className="mr-4"
|
||||
|
||||
-144
@@ -1,144 +0,0 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { Controller, useFormContext, useWatch } from "react-hook-form";
|
||||
|
||||
import { FormControl, Input, Switch } from "@app/components/v2";
|
||||
import { KubernetesAuthMethod } from "@app/hooks/api/pam";
|
||||
import { UNCHANGED_PASSWORD_SENTINEL } from "@app/hooks/api/pam/constants";
|
||||
|
||||
export const KubernetesRotateAccountFields = ({ isUpdate }: { isUpdate: boolean }) => {
|
||||
const { control, setValue, getValues } = useFormContext();
|
||||
const [showToken, setShowToken] = useState(false);
|
||||
|
||||
const rotationServiceAccountName = useWatch({
|
||||
control,
|
||||
name: "rotationAccountCredentials.serviceAccountName"
|
||||
});
|
||||
const rotationServiceAccountToken = useWatch({
|
||||
control,
|
||||
name: "rotationAccountCredentials.serviceAccountToken"
|
||||
});
|
||||
|
||||
const [enabled, setEnabled] = useState(false);
|
||||
const [wasRotationTokenSentinelInitially, setWasRotationTokenSentinelInitially] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const initialRotationToken = getValues("rotationAccountCredentials.serviceAccountToken");
|
||||
if (initialRotationToken === UNCHANGED_PASSWORD_SENTINEL) {
|
||||
setWasRotationTokenSentinelInitially(true);
|
||||
}
|
||||
}, [getValues]);
|
||||
|
||||
useEffect(() => {
|
||||
if (rotationServiceAccountToken === UNCHANGED_PASSWORD_SENTINEL) {
|
||||
setShowToken(false);
|
||||
}
|
||||
}, [rotationServiceAccountToken]);
|
||||
|
||||
useEffect(() => {
|
||||
const isServiceAccountNamePopulated =
|
||||
rotationServiceAccountName && rotationServiceAccountName !== "";
|
||||
const isTokenPopulated =
|
||||
rotationServiceAccountToken &&
|
||||
rotationServiceAccountToken !== "" &&
|
||||
rotationServiceAccountToken !== UNCHANGED_PASSWORD_SENTINEL;
|
||||
|
||||
if (isServiceAccountNamePopulated || isTokenPopulated) {
|
||||
setEnabled(true);
|
||||
}
|
||||
}, [rotationServiceAccountName, rotationServiceAccountToken]);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2">
|
||||
<Switch
|
||||
id="account-rotation"
|
||||
onCheckedChange={(value) => {
|
||||
setEnabled(value);
|
||||
if (value) {
|
||||
setValue(
|
||||
"rotationAccountCredentials.authMethod",
|
||||
KubernetesAuthMethod.ServiceAccountToken,
|
||||
{
|
||||
shouldDirty: true
|
||||
}
|
||||
);
|
||||
setValue("rotationAccountCredentials.serviceAccountName", "", {
|
||||
shouldDirty: true
|
||||
});
|
||||
setValue("rotationAccountCredentials.serviceAccountToken", "", {
|
||||
shouldDirty: true
|
||||
});
|
||||
} else {
|
||||
setValue("rotationAccountCredentials", null, {
|
||||
shouldDirty: true
|
||||
});
|
||||
}
|
||||
}}
|
||||
isChecked={enabled}
|
||||
containerClassName="flex-row-reverse w-fit"
|
||||
className="ml-0"
|
||||
>
|
||||
<p className="ml-2">Credential Rotation</p>
|
||||
</Switch>
|
||||
|
||||
{enabled && (
|
||||
<>
|
||||
<div className="flex gap-2">
|
||||
<Controller
|
||||
name="rotationAccountCredentials.serviceAccountName"
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
className="mb-0 flex-1"
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error?.message)}
|
||||
label="Service Account Name"
|
||||
>
|
||||
<div className="relative">
|
||||
<Input {...field} autoComplete="off" />
|
||||
</div>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
name="rotationAccountCredentials.serviceAccountToken"
|
||||
control={control}
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
className="mb-0 flex-1"
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error?.message)}
|
||||
label="Service Account Token"
|
||||
>
|
||||
<div className="relative">
|
||||
<Input
|
||||
{...field}
|
||||
type={showToken ? "text" : "password"}
|
||||
autoComplete="new-password"
|
||||
onFocus={() => {
|
||||
if (isUpdate && field.value === UNCHANGED_PASSWORD_SENTINEL) {
|
||||
field.onChange("");
|
||||
}
|
||||
setShowToken(true);
|
||||
}}
|
||||
onBlur={() => {
|
||||
if (isUpdate && field.value === "" && wasRotationTokenSentinelInitially) {
|
||||
field.onChange(UNCHANGED_PASSWORD_SENTINEL);
|
||||
}
|
||||
setShowToken(false);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p className="mb-2 text-xs text-mineshaft-400">
|
||||
Credentials of the privileged service account which will be used for rotating other
|
||||
accounts under this resource
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user