From 1e2b31b040d74c449fdf679a0ba3f8ec00e2b5ee Mon Sep 17 00:00:00 2001 From: BugIsGod <87571967+bugisthegod@users.noreply.github.com> Date: Thu, 26 Mar 2026 07:25:57 +0000 Subject: [PATCH] fix: prevent saving API key with empty name (#18970) Fixes: #18959 Generally, users need to type name before they create the api key. https://github.com/user-attachments/assets/bb3ec0ec-d05f-48a8-b762-a35288a9e111 image --- .../SettingsApiKeysFieldItemTableRow.tsx | 8 ++++++-- .../SettingsDevelopersApiKeyDetail.tsx | 20 ++++++++++++------- .../api-keys/SettingsDevelopersApiKeysNew.tsx | 12 ++++++----- 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/packages/twenty-front/src/modules/settings/developers/components/SettingsApiKeysFieldItemTableRow.tsx b/packages/twenty-front/src/modules/settings/developers/components/SettingsApiKeysFieldItemTableRow.tsx index 3db8d2adfe2..211f715be38 100644 --- a/packages/twenty-front/src/modules/settings/developers/components/SettingsApiKeysFieldItemTableRow.tsx +++ b/packages/twenty-front/src/modules/settings/developers/components/SettingsApiKeysFieldItemTableRow.tsx @@ -4,9 +4,10 @@ import { } from '@/settings/developers/utils/formatExpiration'; import { TableCell } from '@/ui/layout/table/components/TableCell'; import { TableRow } from '@/ui/layout/table/components/TableRow'; -import { IconChevronRight } from 'twenty-ui/display'; import { styled } from '@linaria/react'; +import { useLingui } from '@lingui/react/macro'; import { useContext } from 'react'; +import { IconChevronRight } from 'twenty-ui/display'; import { ThemeContext } from 'twenty-ui/theme-constants'; import { type ApiKey } from '~/generated-metadata/graphql'; @@ -29,6 +30,7 @@ export const SettingsApiKeysFieldItemTableRow = ({ apiKey, to, }: SettingsApiKeysFieldItemTableRowProps) => { + const { t } = useLingui(); const { theme } = useContext(ThemeContext); const formattedExpiration = formatExpiration(apiKey.expiresAt || null); @@ -43,7 +45,9 @@ export const SettingsApiKeysFieldItemTableRow = ({ textOverflow="ellipsis" clickable > - {apiKey.name} + + {apiKey.name || t`Unnamed API Key`} + { const regenerateApiKey = async () => { setIsLoading(true); try { - if (isNonEmptyString(apiKey?.name)) { + if (isDefined(apiKey)) { + if (!isNonEmptyString(apiKeyName)) { + enqueueErrorSnackBar({ + message: t`API key name cannot be empty`, + }); + return; + } const newExpiresAt = computeNewExpirationDate( - apiKey?.expiresAt, - apiKey?.createdAt, + apiKey.expiresAt, + apiKey.createdAt, ); - const newApiKey = await createIntegration(apiKey?.name, newExpiresAt); + const newApiKey = await createIntegration(apiKeyName, newExpiresAt); await deleteIntegration(false); if (isNonEmptyString(newApiKey?.token)) { @@ -233,9 +239,9 @@ export const SettingsDevelopersApiKeyDetail = () => { return ( <> - {apiKey?.name && ( + {isDefined(apiKey) && ( { children: t`APIs & Webhooks`, href: getSettingsPath(SettingsPath.ApiWebhooks), }, - { children: apiKey?.name }, + { children: apiKey.name || t`Unnamed API Key` }, ]} > diff --git a/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeysNew.tsx b/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeysNew.tsx index 3455aa7a8fa..dc5ad46db6a 100644 --- a/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeysNew.tsx +++ b/packages/twenty-front/src/pages/settings/developers/api-keys/SettingsDevelopersApiKeysNew.tsx @@ -1,5 +1,5 @@ import { addDays } from 'date-fns'; -import { useState, useCallback, useEffect } from 'react'; +import { useCallback, useEffect, useState } from 'react'; import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons'; import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; @@ -10,6 +10,7 @@ import { apiKeyTokenFamilyState } from '@/settings/developers/states/apiKeyToken import { Select } from '@/ui/input/components/Select'; import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; +import { useMutation, useQuery } from '@apollo/client/react'; import { useLingui } from '@lingui/react/macro'; import { useStore } from 'jotai'; import { Key } from 'ts-key-enum'; @@ -17,7 +18,6 @@ import { SettingsPath } from 'twenty-shared/types'; import { getSettingsPath, isDefined } from 'twenty-shared/utils'; import { H2Title } from 'twenty-ui/display'; import { Section } from 'twenty-ui/layout'; -import { useMutation, useQuery } from '@apollo/client/react'; import { CreateApiKeyDocument, GenerateApiKeyTokenDocument, @@ -70,6 +70,8 @@ export const SettingsDevelopersApiKeysNew = () => { ); const handleSave = async () => { + if (!formValues.name) return; + const expiresAt = addDays( new Date(), formValues.expirationDate ?? 30, @@ -84,7 +86,7 @@ export const SettingsDevelopersApiKeysNew = () => { const { data: newApiKeyData } = await createApiKey({ variables: { input: { - name: formValues.name, + name: formValues.name.trim(), expiresAt, roleId: roleIdToUse, }, @@ -115,7 +117,7 @@ export const SettingsDevelopersApiKeysNew = () => { } }; - const canSave = !!formValues.name && !!formValues.roleId && createApiKey; + const canSave = !!formValues.name && !!formValues.roleId; if (rolesLoading) { return ; @@ -137,7 +139,7 @@ export const SettingsDevelopersApiKeysNew = () => { ]} actionButton={ { navigateSettings(SettingsPath.ApiWebhooks); }}