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..c2dc30e937 100644 --- a/web/src/shared/components/modals/AddAuthKeyModal/AddAuthKeyModal.tsx +++ b/web/src/shared/components/modals/AddAuthKeyModal/AddAuthKeyModal.tsx @@ -12,7 +12,7 @@ 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'; @@ -20,6 +20,7 @@ import { type ApiError, AuthKeyType, type AuthKeyTypeValue } from '../../../api/ 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; @@ -67,9 +68,16 @@ 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().toLowerCase()), + m.form_error_name_reserved(), + ), key: z.string().trim().min(1, m.form_error_required()), }); @@ -82,7 +90,11 @@ const defaultValues: FormFields = { const ModalContent = ({ username }: { username: string }) => { const [selected, setSelected] = useState(selectOptions[0]); - const formSchema = useMemo(() => getFormSchema(), []); + 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({ mutationFn: api.user.addAuthKey, @@ -116,6 +128,14 @@ const ModalContent = ({ username }: { username: string }) => { }, }, }); + } else if (msg?.includes('already exists')) { + formApi.setErrorMap({ + onSubmit: { + fields: { + key: m.form_error_key_exists(), + }, + }, + }); } }); },