diff --git a/.changeset/new-doors-smash.md b/.changeset/new-doors-smash.md new file mode 100644 index 0000000000000..88ddc1c9adad2 --- /dev/null +++ b/.changeset/new-doors-smash.md @@ -0,0 +1,6 @@ +--- +'@rocket.chat/i18n': minor +'@rocket.chat/meteor': minor +--- + +Prevents creation of unnamed Personal Access Tokens by requiring the form's `name` field fullfilment. diff --git a/apps/meteor/client/views/account/tokens/AccountTokensTable/AddToken.tsx b/apps/meteor/client/views/account/tokens/AccountTokensTable/AddToken.tsx index fc1d715b5249e..72ca2019b479f 100644 --- a/apps/meteor/client/views/account/tokens/AccountTokensTable/AddToken.tsx +++ b/apps/meteor/client/views/account/tokens/AccountTokensTable/AddToken.tsx @@ -1,8 +1,8 @@ import type { SelectOption } from '@rocket.chat/fuselage'; -import { Box, TextInput, Button, Margins, Select } from '@rocket.chat/fuselage'; +import { Box, TextInput, Button, Margins, Select, FieldError, FieldGroup, Field, FieldRow } from '@rocket.chat/fuselage'; import { useSetModal, useToastMessageDispatch, useUserId, useMethod } from '@rocket.chat/ui-contexts'; import DOMPurify from 'dompurify'; -import { useCallback, useMemo, useEffect } from 'react'; +import { useCallback, useId, useMemo } from 'react'; import { Controller, useForm } from 'react-hook-form'; import { useTranslation } from 'react-i18next'; @@ -27,11 +27,10 @@ const AddToken = ({ reload }: AddTokenProps) => { const initialValues = useMemo(() => ({ name: '', bypassTwoFactor: 'require' }), []); const { - register, - resetField, handleSubmit, control, - formState: { isSubmitted, submitCount }, + reset, + formState: { errors }, } = useForm({ defaultValues: initialValues }); const twoFactorAuthOptions: SelectOption[] = useMemo( @@ -47,8 +46,14 @@ const AddToken = ({ reload }: AddTokenProps) => { try { const token = await createTokenFn({ tokenName, bypassTwoFactor: bypassTwoFactor === 'bypass' }); + const handleDismissModal = () => { + setModal(null); + reload(); + reset(); + }; + setModal( - setModal(null)} onClose={() => setModal(null)}> + { dispatchToastMessage({ type: 'error', message: error }); } }, - [createTokenFn, dispatchToastMessage, setModal, t, userId], + [createTokenFn, dispatchToastMessage, reload, reset, setModal, t, userId], ); - useEffect(() => { - resetField('name'); - reload(); - }, [isSubmitted, submitCount, reload, resetField]); + const nameErrorId = useId(); return ( - - - - - + + + + } + /> + + + + + {errors?.name && ( + + {errors.name.message} + + )} + + ); }; diff --git a/apps/meteor/tests/e2e/account-profile.spec.ts b/apps/meteor/tests/e2e/account-profile.spec.ts index 9062ee07ce804..19e7366398251 100644 --- a/apps/meteor/tests/e2e/account-profile.spec.ts +++ b/apps/meteor/tests/e2e/account-profile.spec.ts @@ -110,6 +110,11 @@ test.describe.serial('settings-account-profile', () => { await poAccountProfile.btnTokenAddedOk.click(); }); + await test.step('should not allow add new personal with no name', async () => { + await poAccountProfile.btnTokensAdd.click(); + await expect(page.getByRole('alert').filter({ hasText: 'Please provide a name for your token' })).toBeVisible(); + }); + await test.step('should not allow add new personal token with same name', async () => { await poAccountProfile.inputToken.fill(token); await poAccountProfile.btnTokensAdd.click(); diff --git a/packages/i18n/src/locales/en.i18n.json b/packages/i18n/src/locales/en.i18n.json index c73e7bc322618..f46d7698c256d 100644 --- a/packages/i18n/src/locales/en.i18n.json +++ b/packages/i18n/src/locales/en.i18n.json @@ -3976,6 +3976,7 @@ "Premium_capability": "Premium capability", "Premium_omnichannel_capabilities": "Premium omnichannel capabilities", "Premium_only": "Premium only", + "Please_provide_a_name_for_your_token": "Please provide a name for your token", "Preparing_data_for_import_process": "Preparing data for import process", "Preparing_list_of_channels": "Preparing list of channels", "Preparing_list_of_messages": "Preparing list of messages",