mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-03 21:26:04 +00:00
add a toggle for resource account rotation
This commit is contained in:
-28
@@ -12,31 +12,3 @@ export const BaseSqlAccountSchema = z.object({
|
|||||||
.min(1, "Password required")
|
.min(1, "Password required")
|
||||||
.max(256, "Password must be 256 characters or less")
|
.max(256, "Password must be 256 characters or less")
|
||||||
});
|
});
|
||||||
|
|
||||||
export const BaseSqlRotationAccountSchema = z
|
|
||||||
.object({
|
|
||||||
username: z.string().trim().max(63, "Username must be 63 characters or less"),
|
|
||||||
password: z.string().trim().max(256, "Password must be 256 characters or less")
|
|
||||||
})
|
|
||||||
.superRefine((data, ctx) => {
|
|
||||||
if (data.username && !data.password) {
|
|
||||||
ctx.addIssue({
|
|
||||||
path: ["password"],
|
|
||||||
message: "Password is required",
|
|
||||||
code: z.ZodIssueCode.custom
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (data.password && !data.username) {
|
|
||||||
ctx.addIssue({
|
|
||||||
path: ["username"],
|
|
||||||
message: "Username is required",
|
|
||||||
code: z.ZodIssueCode.custom
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.transform((val) => {
|
|
||||||
if (!val.username && !val.password) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return val;
|
|
||||||
});
|
|
||||||
|
|||||||
+2
-2
@@ -6,7 +6,7 @@ import { z } from "zod";
|
|||||||
import { Button, ModalClose } from "@app/components/v2";
|
import { Button, ModalClose } from "@app/components/v2";
|
||||||
import { PamResourceType, TPostgresResource } from "@app/hooks/api/pam";
|
import { PamResourceType, TPostgresResource } from "@app/hooks/api/pam";
|
||||||
import { UNCHANGED_PASSWORD_SENTINEL } from "@app/hooks/api/pam/constants";
|
import { UNCHANGED_PASSWORD_SENTINEL } from "@app/hooks/api/pam/constants";
|
||||||
import { BaseSqlRotationAccountSchema } from "@app/pages/pam/PamAccountsPage/components/PamAccountForm/shared/sql-account-schemas";
|
import { BaseSqlAccountSchema } from "@app/pages/pam/PamAccountsPage/components/PamAccountForm/shared/sql-account-schemas";
|
||||||
|
|
||||||
import { BaseSqlResourceSchema } from "./shared/sql-resource-schemas";
|
import { BaseSqlResourceSchema } from "./shared/sql-resource-schemas";
|
||||||
import { SqlResourceFields } from "./shared/SqlResourceFields";
|
import { SqlResourceFields } from "./shared/SqlResourceFields";
|
||||||
@@ -21,7 +21,7 @@ type Props = {
|
|||||||
const formSchema = genericResourceFieldsSchema.extend({
|
const formSchema = genericResourceFieldsSchema.extend({
|
||||||
resourceType: z.literal(PamResourceType.Postgres),
|
resourceType: z.literal(PamResourceType.Postgres),
|
||||||
connectionDetails: BaseSqlResourceSchema,
|
connectionDetails: BaseSqlResourceSchema,
|
||||||
rotationAccountCredentials: BaseSqlRotationAccountSchema.nullable().optional()
|
rotationAccountCredentials: BaseSqlAccountSchema.nullable().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
type FormData = z.infer<typeof formSchema>;
|
type FormData = z.infer<typeof formSchema>;
|
||||||
|
|||||||
+68
-43
@@ -1,40 +1,74 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Controller, useFormContext, useWatch } from "react-hook-form";
|
import { Controller, useFormContext, useWatch } from "react-hook-form";
|
||||||
import { faTimes } from "@fortawesome/free-solid-svg-icons";
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
||||||
|
|
||||||
import {
|
import { FormControl, Input, Switch } from "@app/components/v2";
|
||||||
Accordion,
|
|
||||||
AccordionContent,
|
|
||||||
AccordionItem,
|
|
||||||
AccordionTrigger,
|
|
||||||
FormControl,
|
|
||||||
Input
|
|
||||||
} from "@app/components/v2";
|
|
||||||
import { UNCHANGED_PASSWORD_SENTINEL } from "@app/hooks/api/pam/constants";
|
import { UNCHANGED_PASSWORD_SENTINEL } from "@app/hooks/api/pam/constants";
|
||||||
|
|
||||||
export const SqlRotateAccountFields = ({ isUpdate }: { isUpdate: boolean }) => {
|
export const SqlRotateAccountFields = ({ isUpdate }: { isUpdate: boolean }) => {
|
||||||
const { control } = useFormContext();
|
const { control, setValue, getValues } = useFormContext();
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
const password = useWatch({ control, name: "credentials.password" });
|
const password = useWatch({ control, name: "credentials.password" });
|
||||||
|
|
||||||
|
const rotationUsername = useWatch({ control, name: "rotationAccountCredentials.username" });
|
||||||
|
const rotationPassword = useWatch({ control, name: "rotationAccountCredentials.password" });
|
||||||
|
|
||||||
|
const [enabled, setEnabled] = useState(false);
|
||||||
|
const [wasRotationPasswordSentinelInitially, setWasRotationPasswordSentinelInitially] =
|
||||||
|
useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const initialRotationPass = getValues("rotationAccountCredentials.password");
|
||||||
|
if (initialRotationPass === UNCHANGED_PASSWORD_SENTINEL) {
|
||||||
|
setWasRotationPasswordSentinelInitially(true);
|
||||||
|
}
|
||||||
|
}, [getValues]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (password === UNCHANGED_PASSWORD_SENTINEL) {
|
if (password === UNCHANGED_PASSWORD_SENTINEL) {
|
||||||
setShowPassword(false);
|
setShowPassword(false);
|
||||||
}
|
}
|
||||||
}, [password]);
|
}, [password]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const isUsernamePopulated = rotationUsername && rotationUsername !== "";
|
||||||
|
const isPasswordPopulated =
|
||||||
|
rotationPassword &&
|
||||||
|
rotationPassword !== "" &&
|
||||||
|
rotationPassword !== UNCHANGED_PASSWORD_SENTINEL;
|
||||||
|
|
||||||
|
if (isUsernamePopulated || isPasswordPopulated) {
|
||||||
|
setEnabled(true);
|
||||||
|
}
|
||||||
|
}, [rotationUsername, rotationPassword]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Accordion type="single" collapsible className="w-full">
|
<div className="flex flex-col gap-2">
|
||||||
<AccordionItem value="advance-settings" className="data-[state=open]:border-none">
|
<Switch
|
||||||
<AccordionTrigger className="h-fit flex-none pl-1 text-sm">
|
id="account-rotation"
|
||||||
<div className="order-1 ml-3">Rotation Account</div>
|
onCheckedChange={(value) => {
|
||||||
</AccordionTrigger>
|
setEnabled(value);
|
||||||
<AccordionContent childrenClassName="px-0 py-0">
|
if (value) {
|
||||||
<p className="mb-2 text-xs">
|
setValue("rotationAccountCredentials.username", "", {
|
||||||
Credentials of the privileged account which will be used for rotating other accounts
|
shouldDirty: true
|
||||||
under this resource
|
});
|
||||||
</p>
|
setValue("rotationAccountCredentials.password", "", {
|
||||||
|
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">
|
<div className="flex gap-2">
|
||||||
<Controller
|
<Controller
|
||||||
name="rotationAccountCredentials.username"
|
name="rotationAccountCredentials.username"
|
||||||
@@ -48,15 +82,6 @@ export const SqlRotateAccountFields = ({ isUpdate }: { isUpdate: boolean }) => {
|
|||||||
>
|
>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Input {...field} autoComplete="off" />
|
<Input {...field} autoComplete="off" />
|
||||||
{field.value && (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="absolute inset-y-0 right-0 flex cursor-pointer items-center pr-3"
|
|
||||||
onClick={() => field.onChange("")}
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon icon={faTimes} className="text-gray-500" />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
@@ -83,28 +108,28 @@ export const SqlRotateAccountFields = ({ isUpdate }: { isUpdate: boolean }) => {
|
|||||||
setShowPassword(true);
|
setShowPassword(true);
|
||||||
}}
|
}}
|
||||||
onBlur={() => {
|
onBlur={() => {
|
||||||
if (isUpdate && field.value === "") {
|
if (
|
||||||
|
isUpdate &&
|
||||||
|
field.value === "" &&
|
||||||
|
wasRotationPasswordSentinelInitially
|
||||||
|
) {
|
||||||
field.onChange(UNCHANGED_PASSWORD_SENTINEL);
|
field.onChange(UNCHANGED_PASSWORD_SENTINEL);
|
||||||
}
|
}
|
||||||
setShowPassword(false);
|
setShowPassword(false);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{field.value && (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="absolute inset-y-0 right-0 flex cursor-pointer items-center pr-3"
|
|
||||||
onClick={() => field.onChange("")}
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon icon={faTimes} className="text-gray-500" />
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</AccordionContent>
|
|
||||||
</AccordionItem>
|
<p className="mb-2 text-xs text-mineshaft-400">
|
||||||
</Accordion>
|
Credentials of the privileged account which will be used for rotating other accounts
|
||||||
|
under this resource
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user