From 34577fe2558266d966aadaf628760138e6648f62 Mon Sep 17 00:00:00 2001 From: Kuba <78603704+jakub-tldr@users.noreply.github.com> Date: Thu, 12 Mar 2026 13:09:38 +0100 Subject: [PATCH 1/5] validate if key/title already exists --- web/messages/en/form.json | 1 + .../AddAuthKeyModal/AddAuthKeyModal.tsx | 26 ++++++++++++++++--- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/web/messages/en/form.json b/web/messages/en/form.json index 49f16fa47a..0775653714 100644 --- a/web/messages/en/form.json +++ b/web/messages/en/form.json @@ -23,6 +23,7 @@ "form_error_code": "Incorrect code. Please check and try again.", "form_error_current_password": "Current password is incorrect.", "form_error_invalid_key": "Invalid key format", + "form_error_key_exists": "Key already exists", "form_error_ip_invalid": "IP is invalid", "form_error_ip_reserved": "IP is not available", "password_form_check_title": "Your password must include:", diff --git a/web/src/shared/components/modals/AddAuthKeyModal/AddAuthKeyModal.tsx b/web/src/shared/components/modals/AddAuthKeyModal/AddAuthKeyModal.tsx index 5995c0e485..99e748d8fd 100644 --- a/web/src/shared/components/modals/AddAuthKeyModal/AddAuthKeyModal.tsx +++ b/web/src/shared/components/modals/AddAuthKeyModal/AddAuthKeyModal.tsx @@ -12,11 +12,12 @@ import { import { ModalName } from '../../../hooks/modalControls/modalTypes'; import './style.scss'; import { useStore } from '@tanstack/react-form'; -import { useMutation } from '@tanstack/react-query'; +import { useMutation, useQuery } from '@tanstack/react-query'; import type { AxiosError } from 'axios'; import { useEffect, useMemo, useState } from 'react'; import api from '../../../api/api'; import { type ApiError, AuthKeyType, type AuthKeyTypeValue } from '../../../api/types'; +import { getUserAuthKeysQueryOptions } from '../../../query'; import { Select } from '../../../defguard-ui/components/Select/Select'; import type { SelectOption } from '../../../defguard-ui/components/Select/types'; import { isPresent } from '../../../defguard-ui/utils/isPresent'; @@ -67,9 +68,13 @@ const selectOptions: SelectOption[] = [ }, ] as const; -const getFormSchema = () => +const getFormSchema = (existingNames: string[]) => z.object({ - name: z.string().trim().min(1, m.form_error_required()), + name: z + .string() + .trim() + .min(1, m.form_error_required()) + .refine((val) => !existingNames.includes(val.trim()), m.form_error_name_reserved()), key: z.string().trim().min(1, m.form_error_required()), }); @@ -82,7 +87,12 @@ const defaultValues: FormFields = { const ModalContent = ({ username }: { username: string }) => { const [selected, setSelected] = useState(selectOptions[0]); - const formSchema = useMemo(() => getFormSchema(), []); + const { data: existingNames = [] } = useQuery({ + ...getUserAuthKeysQueryOptions(username), + select: (response) => + response.data.map((k) => k.name).filter((name): name is string => !!name), + }); + const formSchema = useMemo(() => getFormSchema(existingNames), [existingNames]); const { mutateAsync: addKey } = useMutation({ mutationFn: api.user.addAuthKey, @@ -116,6 +126,14 @@ const ModalContent = ({ username }: { username: string }) => { }, }, }); + } else if (msg?.includes('already exists')) { + formApi.setErrorMap({ + onSubmit: { + fields: { + key: m.form_error_key_exists(), + }, + }, + }); } }); }, From 1cb98688529dda1032e629e5ae625e77a5840261 Mon Sep 17 00:00:00 2001 From: Kuba <78603704+jakub-tldr@users.noreply.github.com> Date: Thu, 12 Mar 2026 13:09:49 +0100 Subject: [PATCH 2/5] lint --- .../components/modals/AddAuthKeyModal/AddAuthKeyModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/shared/components/modals/AddAuthKeyModal/AddAuthKeyModal.tsx b/web/src/shared/components/modals/AddAuthKeyModal/AddAuthKeyModal.tsx index 99e748d8fd..d31abaa9ac 100644 --- a/web/src/shared/components/modals/AddAuthKeyModal/AddAuthKeyModal.tsx +++ b/web/src/shared/components/modals/AddAuthKeyModal/AddAuthKeyModal.tsx @@ -17,10 +17,10 @@ import type { AxiosError } from 'axios'; import { useEffect, useMemo, useState } from 'react'; import api from '../../../api/api'; import { type ApiError, AuthKeyType, type AuthKeyTypeValue } from '../../../api/types'; -import { getUserAuthKeysQueryOptions } from '../../../query'; import { Select } from '../../../defguard-ui/components/Select/Select'; import type { SelectOption } from '../../../defguard-ui/components/Select/types'; import { isPresent } from '../../../defguard-ui/utils/isPresent'; +import { getUserAuthKeysQueryOptions } from '../../../query'; const modalNameKey = ModalName.AddAuthKey; From 1e89dcfb078bdff77fe483437fe04ec3f9202f34 Mon Sep 17 00:00:00 2001 From: Kuba <78603704+jakub-tldr@users.noreply.github.com> Date: Thu, 12 Mar 2026 13:15:09 +0100 Subject: [PATCH 3/5] case sensitive --- .../components/modals/AddAuthKeyModal/AddAuthKeyModal.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/src/shared/components/modals/AddAuthKeyModal/AddAuthKeyModal.tsx b/web/src/shared/components/modals/AddAuthKeyModal/AddAuthKeyModal.tsx index d31abaa9ac..5473932141 100644 --- a/web/src/shared/components/modals/AddAuthKeyModal/AddAuthKeyModal.tsx +++ b/web/src/shared/components/modals/AddAuthKeyModal/AddAuthKeyModal.tsx @@ -74,7 +74,7 @@ const getFormSchema = (existingNames: string[]) => .string() .trim() .min(1, m.form_error_required()) - .refine((val) => !existingNames.includes(val.trim()), m.form_error_name_reserved()), + .refine((val) => !existingNames.includes(val.trim().toLowerCase()), m.form_error_name_reserved()), key: z.string().trim().min(1, m.form_error_required()), }); @@ -90,7 +90,7 @@ const ModalContent = ({ username }: { username: string }) => { const { data: existingNames = [] } = useQuery({ ...getUserAuthKeysQueryOptions(username), select: (response) => - response.data.map((k) => k.name).filter((name): name is string => !!name), + response.data.map((k) => k.name?.toLowerCase()).filter((name): name is string => !!name), }); const formSchema = useMemo(() => getFormSchema(existingNames), [existingNames]); From 3a3c81872b28b3bc70be88e6b990a45b83f80ccf Mon Sep 17 00:00:00 2001 From: Kuba <78603704+jakub-tldr@users.noreply.github.com> Date: Thu, 12 Mar 2026 13:16:51 +0100 Subject: [PATCH 4/5] simplify --- .../components/modals/AddAuthKeyModal/AddAuthKeyModal.tsx | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/web/src/shared/components/modals/AddAuthKeyModal/AddAuthKeyModal.tsx b/web/src/shared/components/modals/AddAuthKeyModal/AddAuthKeyModal.tsx index 5473932141..88569a26f5 100644 --- a/web/src/shared/components/modals/AddAuthKeyModal/AddAuthKeyModal.tsx +++ b/web/src/shared/components/modals/AddAuthKeyModal/AddAuthKeyModal.tsx @@ -87,11 +87,8 @@ const defaultValues: FormFields = { const ModalContent = ({ username }: { username: string }) => { const [selected, setSelected] = useState(selectOptions[0]); - const { data: existingNames = [] } = useQuery({ - ...getUserAuthKeysQueryOptions(username), - select: (response) => - response.data.map((k) => k.name?.toLowerCase()).filter((name): name is string => !!name), - }); + const { data: authKeys = [] } = useQuery(getUserAuthKeysQueryOptions(username)); + const existingNames = authKeys.map((k) => k.name?.toLowerCase()).filter(Boolean) as string[]; const formSchema = useMemo(() => getFormSchema(existingNames), [existingNames]); const { mutateAsync: addKey } = useMutation({ From 4c7936b5035c68b98e5adfa6d6f2e1adf7f69e6d Mon Sep 17 00:00:00 2001 From: Kuba <78603704+jakub-tldr@users.noreply.github.com> Date: Thu, 12 Mar 2026 13:17:08 +0100 Subject: [PATCH 5/5] lint --- .../modals/AddAuthKeyModal/AddAuthKeyModal.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/web/src/shared/components/modals/AddAuthKeyModal/AddAuthKeyModal.tsx b/web/src/shared/components/modals/AddAuthKeyModal/AddAuthKeyModal.tsx index 88569a26f5..c2dc30e937 100644 --- a/web/src/shared/components/modals/AddAuthKeyModal/AddAuthKeyModal.tsx +++ b/web/src/shared/components/modals/AddAuthKeyModal/AddAuthKeyModal.tsx @@ -74,7 +74,10 @@ const getFormSchema = (existingNames: string[]) => .string() .trim() .min(1, m.form_error_required()) - .refine((val) => !existingNames.includes(val.trim().toLowerCase()), m.form_error_name_reserved()), + .refine( + (val) => !existingNames.includes(val.trim().toLowerCase()), + m.form_error_name_reserved(), + ), key: z.string().trim().min(1, m.form_error_required()), }); @@ -88,7 +91,9 @@ const defaultValues: FormFields = { const ModalContent = ({ username }: { username: string }) => { const [selected, setSelected] = useState(selectOptions[0]); const { data: authKeys = [] } = useQuery(getUserAuthKeysQueryOptions(username)); - const existingNames = authKeys.map((k) => k.name?.toLowerCase()).filter(Boolean) as string[]; + const existingNames = authKeys + .map((k) => k.name?.toLowerCase()) + .filter(Boolean) as string[]; const formSchema = useMemo(() => getFormSchema(existingNames), [existingNames]); const { mutateAsync: addKey } = useMutation({