diff --git a/e2e/tests/auth.spec.ts b/e2e/tests/auth.spec.ts index 80c9e7e684..ef911cbba0 100644 --- a/e2e/tests/auth.spec.ts +++ b/e2e/tests/auth.spec.ts @@ -24,7 +24,7 @@ test.use({ storageState: { cookies: [], origins: [] } }); test('can auth with admin key', { tag: '@auth' }, async ({ page }) => { const settingsModal = page.getByRole('dialog', { name: 'Settings' }); - const adminKeyInput = page.getByRole('textbox', { name: 'Admin Key' }); + const adminKeyInput = page.getByLabel('Admin Key'); const failedMsg = page.getByText('failed to check token'); const checkSettingsModal = async () => { @@ -64,3 +64,40 @@ test('can auth with admin key', { tag: '@auth' }, async ({ page }) => { await expect(failedMsg).toBeHidden(); }); }); + +test('password input can toggle visibility', { tag: '@auth' }, async ({ page }) => { + const settingsModal = page.getByRole('dialog', { name: 'Settings' }); + const adminKeyInput = page.getByLabel('Admin Key'); + const testPassword = 'test-admin-key-12345'; + + await expect(settingsModal).toBeVisible(); + + await test.step('verify password input is initially masked', async () => { + await adminKeyInput.fill(testPassword); + + await expect(adminKeyInput).toHaveAttribute('type', 'password'); + }); + + await test.step('reveal password by clicking visibility toggle', async () => { + // Mantine PasswordInput has a button with class mantine-PasswordInput-visibilityToggle + const toggleButton = settingsModal.locator( + '.mantine-PasswordInput-visibilityToggle' + ); + + await toggleButton.click(); + + await expect(adminKeyInput).toHaveAttribute('type', 'text'); + await expect(adminKeyInput).toHaveValue(testPassword); + }); + + await test.step('hide password by clicking visibility toggle again', async () => { + const toggleButton = settingsModal.locator( + '.mantine-PasswordInput-visibilityToggle' + ); + + await toggleButton.click(); + + await expect(adminKeyInput).toHaveAttribute('type', 'password'); + await expect(adminKeyInput).toHaveValue(testPassword); + }); +}); diff --git a/e2e/utils/test.ts b/e2e/utils/test.ts index 3e2a2d4c88..690d78de58 100644 --- a/e2e/utils/test.ts +++ b/e2e/utils/test.ts @@ -51,7 +51,8 @@ export const test = baseTest.extend({ // we need to authenticate const settingsModal = page.getByRole('dialog', { name: 'Settings' }); await expect(settingsModal).toBeVisible(); - const adminKeyInput = page.getByRole('textbox', { name: 'Admin Key' }); + // PasswordInput renders with a label, use getByLabel instead + const adminKeyInput = page.getByLabel('Admin Key'); await adminKeyInput.clear(); await adminKeyInput.fill(adminKey); await page diff --git a/src/components/form-slice/FormPartSecret.tsx b/src/components/form-slice/FormPartSecret.tsx index 33aca159f3..7b19aa0249 100644 --- a/src/components/form-slice/FormPartSecret.tsx +++ b/src/components/form-slice/FormPartSecret.tsx @@ -18,6 +18,7 @@ import { Divider, InputWrapper } from '@mantine/core'; import { useFormContext } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; +import { FormItemPasswordInput } from '@/components/form/PasswordInput'; import { FormItemSelect } from '@/components/form/Select'; import { FormItemSwitch } from '@/components/form/Switch'; import { FormItemTextInput } from '@/components/form/TextInput'; @@ -42,7 +43,7 @@ const VaultSecretForm = () => { name="prefix" label={t('form.secrets.vault.prefix')} /> - { return ( <> - - - { name="auth_config.client_email" label={t('form.secrets.gcp.client_email')} /> - = + UseControllerProps & PasswordInputProps; + +/** + * Form field component for sensitive data (passwords, tokens, keys). + * Renders input with masked characters by default with an option to reveal. + */ +export const FormItemPasswordInput = ( + props: FormItemPasswordInputProps +) => { + const { controllerProps, restProps } = genControllerProps(props, ''); + const { + field: { value, onChange: fOnChange, ...restField }, + fieldState, + } = useController(controllerProps); + return ( + { + fOnChange(e); + restProps.onChange?.(e); + }} + {...restField} + {...restProps} + /> + ); +}; diff --git a/src/components/page/SettingsModal.tsx b/src/components/page/SettingsModal.tsx index a9e582ee22..10cde2ce5f 100644 --- a/src/components/page/SettingsModal.tsx +++ b/src/components/page/SettingsModal.tsx @@ -14,7 +14,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Divider, InputWrapper, Modal, Text, TextInput } from '@mantine/core'; +import { + Divider, + InputWrapper, + Modal, + PasswordInput, + Text, +} from '@mantine/core'; import { useAtom } from 'jotai'; import { useTranslation } from 'react-i18next'; @@ -27,7 +33,7 @@ const AdminKey = () => { const [adminKey, setAdminKey] = useAtom(adminKeyAtom); return ( - {