fix: anchor contact phone number validation (#15415)

Contact phone numbers with stray text in front of them, like
`abc+12312312321`, were saving successfully instead of being rejected as
invalid. Agents could end up with unusable numbers on a contact, and the
same values were persisted rather than discarded when captured through
the live chat widget.

## How to reproduce

1. Open a contact and edit its details.
2. Set the phone number to `abc+12312312321` via the API (`PATCH
/api/v1/accounts/:id/contacts/:id`).
3. Before this change the update succeeds. Now it fails validation.

## What changed

The E.164 format check was missing a leading `\A` anchor, so Rails
matched it anywhere in the string and accepted any prefix ahead of a
valid number. Both the validation and the `phone_number_format` fallback
used by `discard_invalid_attrs` are now anchored, so the widget path
discards these values instead of storing them.

Contacts already holding a prefixed number will now fail validation on
their next save. Worth a count on production first:

```sql
SELECT count(*) FROM contacts WHERE phone_number !~ '^\+[1-9][0-9]{1,14}$' AND phone_number <> '';
```

🤖 Generated with [Claude Code](https://claude.com/claude-code)
This commit is contained in:
Shivam Mishra
2026-08-12 15:28:49 +05:30
committed by GitHub
parent 79405f76c4
commit a24f5a3e7a
3 changed files with 14 additions and 2 deletions

View File

@@ -53,7 +53,7 @@ class Contact < ApplicationRecord
validates :identifier, allow_blank: true, uniqueness: { scope: [:account_id] }
validates :phone_number,
allow_blank: true, uniqueness: { scope: [:account_id] },
format: { with: /\+[1-9]\d{1,14}\z/, message: I18n.t('errors.contacts.phone_number.invalid') }
format: { with: /\A\+[1-9]\d{1,14}\z/, message: I18n.t('errors.contacts.phone_number.invalid') }
belongs_to :account
has_many :conversations, dependent: :destroy_async
@@ -206,7 +206,7 @@ class Contact < ApplicationRecord
def phone_number_format
return if phone_number.blank?
self.phone_number = phone_number_was unless phone_number.match?(/\+[1-9]\d{1,14}\z/)
self.phone_number = phone_number_was unless phone_number.match?(/\A\+[1-9]\d{1,14}\z/)
end
def email_format