From 4c57352450afdbc46c622bdb6058046152683166 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Malfait?= Date: Sat, 11 Apr 2026 22:07:05 +0200 Subject: [PATCH] Improve sensitive config variable masking and editing UX (#19578) ## Summary This PR enhances the handling of sensitive configuration variables by improving masking logic and user experience when editing them. It adds metadata-aware masking for dynamically marked sensitive variables and clears sensitive values when entering edit mode. ## Key Changes - **Backend masking improvements**: Updated `maskSensitiveValue()` to accept metadata parameter, enabling masking of variables marked as sensitive via metadata (not just predefined masking config). Sensitive non-string values are masked as `********`. - **Edit mode UX**: When editing a sensitive variable, the value field is now cleared on entering edit mode to prevent exposing masked values and ensure users intentionally provide new secret values. - **Form state tracking**: Enhanced `useConfigVariableForm()` hook to accept an `isEditing` parameter and properly track value changes for sensitive variables during edit operations. - **Input placeholder**: Updated placeholder text for sensitive variable inputs to show `Enter a new secret value` instead of the generic database storage message, providing clearer intent to users. ## Implementation Details - The `maskSensitiveValue()` method now checks both predefined masking configurations and runtime metadata to determine if a value should be masked - Sensitive string values use LAST_N_CHARS strategy (4 characters), while non-string values are masked uniformly - The form's `hasValueChanged` flag is set to true when editing sensitive variables to ensure proper validation and submission handling https://claude.ai/code/session_01JiTckmuVJMQWpJ7TUGwsqb --------- Co-authored-by: Claude --- .../components/ConfigVariableValueInput.tsx | 6 +- .../hooks/useConfigVariableForm.ts | 40 ++++++++------ .../SettingsAdminConfigVariableDetails.tsx | 36 ++++++------ .../twenty-config/twenty-config.service.ts | 55 +++++++++++++------ 4 files changed, 85 insertions(+), 52 deletions(-) diff --git a/packages/twenty-front/src/modules/settings/admin-panel/config-variables/components/ConfigVariableValueInput.tsx b/packages/twenty-front/src/modules/settings/admin-panel/config-variables/components/ConfigVariableValueInput.tsx index fe24ce1bf0..e20ae3c6cf 100644 --- a/packages/twenty-front/src/modules/settings/admin-panel/config-variables/components/ConfigVariableValueInput.tsx +++ b/packages/twenty-front/src/modules/settings/admin-panel/config-variables/components/ConfigVariableValueInput.tsx @@ -41,7 +41,11 @@ export const ConfigVariableValueInput = ({ options={variable.options} disabled={disabled} placeholder={ - disabled ? t`Undefined` : t`Enter a value to store in database` + disabled + ? t`Undefined` + : variable.isSensitive + ? t`Enter a new secret value` + : t`Enter a value to store in database` } /> ) : ( diff --git a/packages/twenty-front/src/modules/settings/admin-panel/config-variables/hooks/useConfigVariableForm.ts b/packages/twenty-front/src/modules/settings/admin-panel/config-variables/hooks/useConfigVariableForm.ts index 5c4a57504a..7935d65382 100644 --- a/packages/twenty-front/src/modules/settings/admin-panel/config-variables/hooks/useConfigVariableForm.ts +++ b/packages/twenty-front/src/modules/settings/admin-panel/config-variables/hooks/useConfigVariableForm.ts @@ -9,6 +9,19 @@ type FormValues = { value: ConfigVariableValue; }; +const hasMeaningfulValue = (value: ConfigVariableValue): boolean => { + if (value === null || value === undefined) { + return false; + } + if (typeof value === 'string') { + return value.trim() !== ''; + } + if (Array.isArray(value)) { + return value.length > 0; + } + return true; +}; + export const useConfigVariableForm = (variable?: ConfigVariable) => { const validationSchema = z.object({ value: z.union([ @@ -22,9 +35,10 @@ export const useConfigVariableForm = (variable?: ConfigVariable) => { }); const { + control, handleSubmit, - setValue, - formState: { isSubmitting }, + reset, + formState: { isSubmitting, isDirty }, watch, } = useForm({ resolver: zodResolver(validationSchema), @@ -32,27 +46,19 @@ export const useConfigVariableForm = (variable?: ConfigVariable) => { }); const currentValue = watch('value'); - const hasValueChanged = currentValue !== variable?.value; - const isValueValid = !!( - variable && + const isValueValid = + variable !== undefined && !variable.isEnvOnly && - hasValueChanged && - ((typeof currentValue === 'string' && currentValue.trim() !== '') || - typeof currentValue === 'boolean' || - typeof currentValue === 'number' || - (Array.isArray(currentValue) && currentValue.length > 0) || - (typeof currentValue === 'object' && - currentValue !== null && - !Array.isArray(currentValue))) - ); + isDirty && + hasMeaningfulValue(currentValue); return { + control, handleSubmit, - setValue, + reset, isSubmitting, - watch, currentValue, - hasValueChanged, + hasValueChanged: isDirty, isValueValid, }; }; diff --git a/packages/twenty-front/src/pages/settings/admin-panel/SettingsAdminConfigVariableDetails.tsx b/packages/twenty-front/src/pages/settings/admin-panel/SettingsAdminConfigVariableDetails.tsx index 838f366ca4..f377ed701f 100644 --- a/packages/twenty-front/src/pages/settings/admin-panel/SettingsAdminConfigVariableDetails.tsx +++ b/packages/twenty-front/src/pages/settings/admin-panel/SettingsAdminConfigVariableDetails.tsx @@ -1,6 +1,7 @@ import { styled } from '@linaria/react'; import { useLingui } from '@lingui/react/macro'; import { useState } from 'react'; +import { Controller } from 'react-hook-form'; import { Form, useParams } from 'react-router-dom'; import { isConfigVariablesInDbEnabledState } from '@/client-config/states/isConfigVariablesInDbEnabledState'; @@ -76,10 +77,10 @@ export const SettingsAdminConfigVariableDetails = () => { useConfigVariableActions(variable?.name ?? ''); const { + control, handleSubmit, - setValue, + reset, isSubmitting, - watch, hasValueChanged, isValueValid, } = useConfigVariableForm(variable); @@ -97,22 +98,19 @@ export const SettingsAdminConfigVariableDetails = () => { }; const handleEditClick = () => { + if (variable.isSensitive) { + reset({ value: '' }); + } setIsEditing(true); }; const handleXButtonClick = () => { - if (isFromDatabase && hasValueChanged) { - setValue('value', variable.value); - setIsEditing(false); - return; - } - if (isFromDatabase && !hasValueChanged) { openModal(RESET_VARIABLE_MODAL_ID); return; } - setValue('value', variable.value); + reset({ value: variable.value }); setIsEditing(false); }; @@ -155,11 +153,17 @@ export const SettingsAdminConfigVariableDetails = () => {
- setValue('value', value)} - disabled={isEnvOnly || !isEditing} + ( + + )} /> {!isEditing ? ( @@ -177,9 +181,7 @@ export const SettingsAdminConfigVariableDetails = () => { variant="secondary" position="left" type="submit" - disabled={ - isSubmitting || !isValueValid || !hasValueChanged - } + disabled={isSubmitting || !isValueValid} />