From d8b7929eb9b50dbf6a16b15f2445c9cd9384ae76 Mon Sep 17 00:00:00 2001 From: Sergey Kintsel Date: Wed, 9 Oct 2024 08:12:17 +0100 Subject: [PATCH] Improve name validation --- .../settings/network/UpsertNetworkModal.tsx | 4 +++- .../Menu/NetworkMenu/EditNetworkMenu.tsx | 9 ++++++++- packages/state/src/hooks/labels.test.ts | 18 +++++++++++++++++- packages/state/src/hooks/labels.ts | 8 ++++++++ 4 files changed, 36 insertions(+), 3 deletions(-) diff --git a/apps/desktop/src/views/settings/network/UpsertNetworkModal.tsx b/apps/desktop/src/views/settings/network/UpsertNetworkModal.tsx index 419df9af0f..6b05889424 100644 --- a/apps/desktop/src/views/settings/network/UpsertNetworkModal.tsx +++ b/apps/desktop/src/views/settings/network/UpsertNetworkModal.tsx @@ -10,7 +10,7 @@ import { ModalHeader, } from "@chakra-ui/react"; import { useDynamicModalContext } from "@umami/components"; -import { networksActions, useAvailableNetworks } from "@umami/state"; +import { networksActions, useAvailableNetworks, useValidateName } from "@umami/state"; import { type Network } from "@umami/tezos"; import { useForm } from "react-hook-form"; import { useDispatch } from "react-redux"; @@ -25,6 +25,7 @@ export const UpsertNetworkModal = ({ network }: { network?: Network }) => { const { onClose } = useDynamicModalContext(); const dispatch = useDispatch(); const availableNetworks = useAvailableNetworks(); + const validateName = useValidateName(); const { formState: { errors, isValid }, @@ -56,6 +57,7 @@ export const UpsertNetworkModal = ({ network }: { network?: Network }) => { if (availableNetworks.find(n => n.name === name)) { return "Network with this name already exists"; } + return validateName(name); }, })} /> diff --git a/apps/web/src/components/Menu/NetworkMenu/EditNetworkMenu.tsx b/apps/web/src/components/Menu/NetworkMenu/EditNetworkMenu.tsx index 35d4453fba..7cccc5630a 100644 --- a/apps/web/src/components/Menu/NetworkMenu/EditNetworkMenu.tsx +++ b/apps/web/src/components/Menu/NetworkMenu/EditNetworkMenu.tsx @@ -1,6 +1,11 @@ import { Button, FormControl, FormErrorMessage, FormLabel, Input, VStack } from "@chakra-ui/react"; import { useDynamicDrawerContext } from "@umami/components"; -import { networksActions, useAppDispatch, useAvailableNetworks } from "@umami/state"; +import { + networksActions, + useAppDispatch, + useAvailableNetworks, + useValidateName, +} from "@umami/state"; import { type Network } from "@umami/tezos"; import { useForm } from "react-hook-form"; @@ -16,6 +21,7 @@ export const EditNetworkMenu = ({ network }: EditNetworkMenuProps) => { const { goBack } = useDynamicDrawerContext(); const dispatch = useAppDispatch(); const availableNetworks = useAvailableNetworks(); + const validateName = useValidateName(); const { formState: { errors, isValid }, @@ -43,6 +49,7 @@ export const EditNetworkMenu = ({ network }: EditNetworkMenuProps) => { if (availableNetworks.find(n => n.name === name)) { return "Network with this name already exists"; } + return validateName(name); }, })} /> diff --git a/packages/state/src/hooks/labels.test.ts b/packages/state/src/hooks/labels.test.ts index 2ddc953f10..00d8ed6969 100644 --- a/packages/state/src/hooks/labels.test.ts +++ b/packages/state/src/hooks/labels.test.ts @@ -9,7 +9,7 @@ import { } from "@umami/core"; import { MAINNET } from "@umami/tezos"; -import { useGetNextAvailableAccountLabels, useValidateName } from "./labels"; +import { PROHIBITED_CHARACTERS, useGetNextAvailableAccountLabels, useValidateName } from "./labels"; import { contactsActions, multisigsActions, networksActions } from "../slices"; import { type UmamiStore, makeStore } from "../store"; import { addTestAccounts, renderHook } from "../testUtils"; @@ -22,6 +22,22 @@ beforeEach(() => { describe("labelsHooks", () => { describe("useValidateName", () => { + it.each(PROHIBITED_CHARACTERS)("fails if name contains `%s` special character", char => { + const { + result: { current: validateName }, + } = renderHook(() => useValidateName(), { store }); + + expect(validateName(`Some ${char} name`)).toEqual("Name contains special character(s)"); + }); + + it("fails if name exceeds 256 characters", () => { + const { + result: { current: validateName }, + } = renderHook(() => useValidateName(), { store }); + + expect(validateName("a".repeat(257))).toEqual("Name should not exceed 256 characters"); + }); + describe.each([ { testCase: "with trailing whitespaces", withWhitespaces: true }, { testCase: "without trailing whitespaces", withWhitespaces: false }, diff --git a/packages/state/src/hooks/labels.ts b/packages/state/src/hooks/labels.ts index 7cd545cefc..7bb25cb469 100644 --- a/packages/state/src/hooks/labels.ts +++ b/packages/state/src/hooks/labels.ts @@ -1,12 +1,20 @@ import { useAllContacts } from "./contacts"; import { useAllAccounts } from "./getAccountData"; +export const PROHIBITED_CHARACTERS = `/\\"'<>:{}$#|\`\t\r\n`.split(""); + /** Hook for validating name for account or contact. */ export const useValidateName = (oldName?: string | undefined) => { const isUniqueLabel = useIsUniqueLabel(); return (name: string) => { const trimmedName = name.trim(); + if (trimmedName.length > 256) { + return "Name should not exceed 256 characters"; + } + if (PROHIBITED_CHARACTERS.some(char => trimmedName.includes(char))) { + return "Name contains special character(s)"; + } if (trimmedName.length === 0) { return "Name should not be empty"; }