From 525568683ff18d0f391fca38522daa7bb0a75ec7 Mon Sep 17 00:00:00 2001 From: x032205 Date: Fri, 24 Oct 2025 05:42:43 -0400 Subject: [PATCH] add a toggle for resource account rotation --- .../shared/sql-account-schemas.ts | 28 ----- .../PamResourceForm/PostgresResourceForm.tsx | 4 +- .../shared/SqlRotateAccountFields.tsx | 111 +++++++++++------- 3 files changed, 70 insertions(+), 73 deletions(-) diff --git a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/shared/sql-account-schemas.ts b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/shared/sql-account-schemas.ts index 751277254..37efd67d4 100644 --- a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/shared/sql-account-schemas.ts +++ b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/shared/sql-account-schemas.ts @@ -12,31 +12,3 @@ export const BaseSqlAccountSchema = z.object({ .min(1, "Password required") .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; - }); diff --git a/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/PostgresResourceForm.tsx b/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/PostgresResourceForm.tsx index fbca7bc87..7e96fffda 100644 --- a/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/PostgresResourceForm.tsx +++ b/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/PostgresResourceForm.tsx @@ -6,7 +6,7 @@ import { z } from "zod"; import { Button, ModalClose } from "@app/components/v2"; import { PamResourceType, TPostgresResource } from "@app/hooks/api/pam"; 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 { SqlResourceFields } from "./shared/SqlResourceFields"; @@ -21,7 +21,7 @@ type Props = { const formSchema = genericResourceFieldsSchema.extend({ resourceType: z.literal(PamResourceType.Postgres), connectionDetails: BaseSqlResourceSchema, - rotationAccountCredentials: BaseSqlRotationAccountSchema.nullable().optional() + rotationAccountCredentials: BaseSqlAccountSchema.nullable().optional() }); type FormData = z.infer; diff --git a/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/shared/SqlRotateAccountFields.tsx b/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/shared/SqlRotateAccountFields.tsx index c3a98a3f6..d3f11ed60 100644 --- a/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/shared/SqlRotateAccountFields.tsx +++ b/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/shared/SqlRotateAccountFields.tsx @@ -1,40 +1,74 @@ import { useEffect, useState } from "react"; import { Controller, useFormContext, useWatch } from "react-hook-form"; -import { faTimes } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { - Accordion, - AccordionContent, - AccordionItem, - AccordionTrigger, - FormControl, - Input -} from "@app/components/v2"; +import { FormControl, Input, Switch } from "@app/components/v2"; import { UNCHANGED_PASSWORD_SENTINEL } from "@app/hooks/api/pam/constants"; export const SqlRotateAccountFields = ({ isUpdate }: { isUpdate: boolean }) => { - const { control } = useFormContext(); + const { control, setValue, getValues } = useFormContext(); const [showPassword, setShowPassword] = useState(false); 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(() => { if (password === UNCHANGED_PASSWORD_SENTINEL) { setShowPassword(false); } }, [password]); + useEffect(() => { + const isUsernamePopulated = rotationUsername && rotationUsername !== ""; + const isPasswordPopulated = + rotationPassword && + rotationPassword !== "" && + rotationPassword !== UNCHANGED_PASSWORD_SENTINEL; + + if (isUsernamePopulated || isPasswordPopulated) { + setEnabled(true); + } + }, [rotationUsername, rotationPassword]); + return ( - - - -
Rotation Account
-
- -

- Credentials of the privileged account which will be used for rotating other accounts - under this resource -

+
+ { + setEnabled(value); + if (value) { + setValue("rotationAccountCredentials.username", "", { + shouldDirty: true + }); + setValue("rotationAccountCredentials.password", "", { + shouldDirty: true + }); + } else { + setValue("rotationAccountCredentials", null, { + shouldDirty: true + }); + } + }} + isChecked={enabled} + containerClassName="flex-row-reverse w-fit" + className="ml-0" + > +

Credential Rotation

+
+ + {enabled && ( + <>
{ >
- {field.value && ( - - )}
)} @@ -83,28 +108,28 @@ export const SqlRotateAccountFields = ({ isUpdate }: { isUpdate: boolean }) => { setShowPassword(true); }} onBlur={() => { - if (isUpdate && field.value === "") { + if ( + isUpdate && + field.value === "" && + wasRotationPasswordSentinelInitially + ) { field.onChange(UNCHANGED_PASSWORD_SENTINEL); } setShowPassword(false); }} /> - {field.value && ( - - )}
)} />
-
-
-
+ +

+ Credentials of the privileged account which will be used for rotating other accounts + under this resource +

+ + )} + ); };