From b68a5ec780c7d50b72e97ac695d2654f969254e7 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 28 May 2026 21:32:08 +0000 Subject: [PATCH 1/3] Fix phone validation incorrectly applied to custom request fields Custom privacy request fields with key "phone" (or "email", "name") were incorrectly having identity field validation rules applied to them, even when those identity inputs were not configured. This was because the identity validation schema unconditionally included validation for all three fields (name/email/phone), regardless of whether they were present in identity_inputs. The fix conditionally includes identity field validations only when the corresponding identity_input is configured. Also improved the phone validation error messages to specify the expected E.164 format (e.g. +15551234567) instead of just "Phone is invalid". Slack thread: https://ethyca.slack.com/archives/C07JPAXLKV4/p1780003433667269?thread_ts=1780001924.843699&cid=C07JPAXLKV4 https://claude.ai/code/session_017HuVbasMgn5gdt7kUXxLDD --- .../features/privacy-requests/form/helpers.ts | 2 +- .../useConsentRequestForm.ts | 63 ++++++----- .../usePrivacyRequestForm.ts | 105 ++++++++++-------- .../components/modals/validation.ts | 2 +- 4 files changed, 95 insertions(+), 77 deletions(-) diff --git a/clients/admin-ui/src/features/privacy-requests/form/helpers.ts b/clients/admin-ui/src/features/privacy-requests/form/helpers.ts index 6cfee6b93b8..9da0ac34a1f 100644 --- a/clients/admin-ui/src/features/privacy-requests/form/helpers.ts +++ b/clients/admin-ui/src/features/privacy-requests/form/helpers.ts @@ -35,7 +35,7 @@ export const generateFormRulesFromAction = ( const phonePattern = /^\+?[1-9]\d{1,14}$/; const phoneMessage = - "Phone number must be formatted correctly (e.g. 15555555555)"; + "Phone must be in E.164 format (e.g. +15551234567 or 15551234567)"; if (action.identity_inputs?.phone === "required") { rules["identity.phone_number"] = [ diff --git a/clients/privacy-center/components/modals/consent-request-modal/useConsentRequestForm.ts b/clients/privacy-center/components/modals/consent-request-modal/useConsentRequestForm.ts index a66cc800a10..ac95923f5f8 100644 --- a/clients/privacy-center/components/modals/consent-request-modal/useConsentRequestForm.ts +++ b/clients/privacy-center/components/modals/consent-request-modal/useConsentRequestForm.ts @@ -64,33 +64,42 @@ const useConsentRequestForm = ({ const initialValues = useMemo(() => getInitialValues(), [getInitialValues]); - // Build the static portion of the validation schema (identity fields) - const identityValidationSchema = useMemo( - () => - Yup.object().shape({ - email: emailValidation(identityInputs?.email!).test( - "one of email or phone entered", - "You must enter an email", - (_value, context) => { - if (identityInputs?.email === "required") { - return Boolean(context.parent.email); - } - return true; - }, - ), - phone: phoneValidation(identityInputs?.phone!).test( - "one of email or phone entered", - "You must enter a phone number", - (_value, context) => { - if (identityInputs?.phone === "required") { - return Boolean(context.parent.phone); - } - return true; - }, - ), - }), - [identityInputs?.email, identityInputs?.phone], - ); + // Build the static portion of the validation schema (identity fields). + // Only include validation rules for identity fields that are actually configured, + // to avoid conflicts with custom_privacy_request_fields that may use the same keys. + const identityValidationSchema = useMemo(() => { + const schemaFields: Record = {}; + + // Only add email validation if email is configured in identity_inputs + if (identityInputs?.email) { + schemaFields.email = emailValidation(identityInputs.email).test( + "one of email or phone entered", + "You must enter an email", + (_value, context) => { + if (identityInputs.email === "required") { + return Boolean(context.parent.email); + } + return true; + }, + ); + } + + // Only add phone validation if phone is configured in identity_inputs + if (identityInputs?.phone) { + schemaFields.phone = phoneValidation(identityInputs.phone).test( + "one of email or phone entered", + "You must enter a phone number", + (_value, context) => { + if (identityInputs.phone === "required") { + return Boolean(context.parent.phone); + } + return true; + }, + ); + } + + return Yup.object().shape(schemaFields); + }, [identityInputs?.email, identityInputs?.phone]); const { validate, applicableFieldsRef, validationError } = useConditionalValidate({ diff --git a/clients/privacy-center/components/modals/privacy-request-modal/usePrivacyRequestForm.ts b/clients/privacy-center/components/modals/privacy-request-modal/usePrivacyRequestForm.ts index ea20b228fd8..fa4b56f2b99 100644 --- a/clients/privacy-center/components/modals/privacy-request-modal/usePrivacyRequestForm.ts +++ b/clients/privacy-center/components/modals/privacy-request-modal/usePrivacyRequestForm.ts @@ -108,54 +108,63 @@ const usePrivacyRequestForm = ({ const initialValues = useMemo(() => getInitialValues(), [getInitialValues]); - // Build the static portion of the validation schema (identity fields) - const identityValidationSchema = useMemo( - () => - Yup.object().shape({ - name: nameValidation(nameInput), - email: emailValidation(emailInput).test( - "one of email or phone entered", - "You must enter either email or phone", - (_value, context) => { - if (emailInput === "optional" && phoneInput === "optional") { - return Boolean(context.parent.phone || context.parent.email); - } - return true; - }, - ), - phone: phoneValidation(phoneInput).test( - "one of email or phone entered", - "You must enter either email or phone", - (_value, context) => { - if (emailInput === "optional" && phoneInput === "optional") { - return Boolean(context.parent.phone || context.parent.email); - } - return true; - }, - ), - ...Object.fromEntries( - Object.entries(customIdentityFields).flatMap(([key, value]) => { - if (!value) { - return []; - } - if (value.field_type === "date") { - return [ - [ - key, - dateFieldValidation( - value, - value.label, - value.required !== false, - ), - ], - ]; - } - return [[key, Yup.string().required(`${value.label} is required`)]]; - }), - ), - }), - [emailInput, phoneInput, nameInput, customIdentityFields], - ); + // Build the static portion of the validation schema (identity fields). + // Only include validation rules for identity fields that are actually configured, + // to avoid conflicts with custom_privacy_request_fields that may use the same keys. + const identityValidationSchema = useMemo(() => { + const schemaFields: Record = {}; + + // Only add name validation if name is configured in identity_inputs + if (nameInput) { + schemaFields.name = nameValidation(nameInput); + } + + // Only add email validation if email is configured in identity_inputs + if (emailInput) { + schemaFields.email = emailValidation(emailInput).test( + "one of email or phone entered", + "You must enter either email or phone", + (_value, context) => { + if (emailInput === "optional" && phoneInput === "optional") { + return Boolean(context.parent.phone || context.parent.email); + } + return true; + }, + ); + } + + // Only add phone validation if phone is configured in identity_inputs + if (phoneInput) { + schemaFields.phone = phoneValidation(phoneInput).test( + "one of email or phone entered", + "You must enter either email or phone", + (_value, context) => { + if (emailInput === "optional" && phoneInput === "optional") { + return Boolean(context.parent.phone || context.parent.email); + } + return true; + }, + ); + } + + // Add custom identity field validations + Object.entries(customIdentityFields).forEach(([key, value]) => { + if (!value) { + return; + } + if (value.field_type === "date") { + schemaFields[key] = dateFieldValidation( + value, + value.label, + value.required !== false, + ); + } else { + schemaFields[key] = Yup.string().required(`${value.label} is required`); + } + }); + + return Yup.object().shape(schemaFields); + }, [emailInput, phoneInput, nameInput, customIdentityFields]); const { validate, applicableFieldsRef, validationError } = useConditionalValidate({ diff --git a/clients/privacy-center/components/modals/validation.ts b/clients/privacy-center/components/modals/validation.ts index 69a7904a126..357947e63d0 100644 --- a/clients/privacy-center/components/modals/validation.ts +++ b/clients/privacy-center/components/modals/validation.ts @@ -53,7 +53,7 @@ export const emailValidation = (option?: string | null) => { export const phoneValidation = (option?: string | null) => { // E.164 international standard format let validation = Yup.string().matches(/^\+[1-9]\d{1,14}$/, { - message: "Phone is invalid", + message: "Phone must be in E.164 format (e.g. +15551234567)", excludeEmptyString: true, }); if (option === "required") { From d8ce9e770376d087f4dc04e1a6578e9a2dc0ccad Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 28 May 2026 21:35:15 +0000 Subject: [PATCH 2/3] Add changelog entry for phone validation fix https://claude.ai/code/session_017HuVbasMgn5gdt7kUXxLDD --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9fb6bb2e517..66d7a9b840d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,9 @@ Changes can also be flagged with a GitHub label for tracking purposes. The URL o ## [Unreleased](https://github.com/ethyca/fides/compare/2.86.1..main) +### Fixed +- Fixed custom privacy request fields named "phone" or "email" incorrectly receiving E.164/email format validation when those identity inputs were not configured; also improved phone validation error message to specify expected format [#8282](https://github.com/ethyca/fides/pull/8282) + ## [2.86.1](https://github.com/ethyca/fides/compare/2.86.0..2.86.1) ### Fixed From eb02953ba7c5af2e713a8162004fe98e906105e1 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 28 May 2026 21:36:10 +0000 Subject: [PATCH 3/3] Add proper changelog entry file Use the new changelog YAML format instead of editing CHANGELOG.md https://claude.ai/code/session_017HuVbasMgn5gdt7kUXxLDD --- CHANGELOG.md | 3 --- changelog/8282-fix-phone-validation-custom-fields.yaml | 4 ++++ 2 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 changelog/8282-fix-phone-validation-custom-fields.yaml diff --git a/CHANGELOG.md b/CHANGELOG.md index 66d7a9b840d..9fb6bb2e517 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,9 +21,6 @@ Changes can also be flagged with a GitHub label for tracking purposes. The URL o ## [Unreleased](https://github.com/ethyca/fides/compare/2.86.1..main) -### Fixed -- Fixed custom privacy request fields named "phone" or "email" incorrectly receiving E.164/email format validation when those identity inputs were not configured; also improved phone validation error message to specify expected format [#8282](https://github.com/ethyca/fides/pull/8282) - ## [2.86.1](https://github.com/ethyca/fides/compare/2.86.0..2.86.1) ### Fixed diff --git a/changelog/8282-fix-phone-validation-custom-fields.yaml b/changelog/8282-fix-phone-validation-custom-fields.yaml new file mode 100644 index 00000000000..19c167b267f --- /dev/null +++ b/changelog/8282-fix-phone-validation-custom-fields.yaml @@ -0,0 +1,4 @@ +type: Fixed +description: Fixed custom privacy request fields named "phone" or "email" incorrectly receiving E.164/email format validation when those identity inputs were not configured; also improved phone validation error message to specify expected format +pr: 8282 +labels: []