feat: normalize and validate phone numbers before saving

This commit is contained in:
Yang Luo
2026-08-16 21:04:59 +08:00
parent b505e642f8
commit 0628b8fc2a
3 changed files with 61 additions and 6 deletions
+14 -4
View File
@@ -511,8 +511,17 @@ func (c *ApiController) ResetEmailOrPhone() {
return
}
countryCode := user.GetCountryCode("")
if destType == object.VerifyTypePhone {
if object.HasUserByPhoneAndCountryCode(user.Owner, dest, user.GetCountryCode("")) {
normalizedPhone, normalizedCountryCode, isValid := util.GetNormalizedPhone(dest, countryCode)
if !isValid {
c.ResponseError(fmt.Sprintf(c.T("verification:Phone number is invalid in your region %s"), countryCode))
return
}
dest, countryCode = normalizedPhone, normalizedCountryCode
if object.HasUserByPhoneAndCountryCode(user.Owner, dest, countryCode) {
c.ResponseError(c.T("check:Phone already exists"))
return
}
@@ -527,8 +536,8 @@ func (c *ApiController) ResetEmailOrPhone() {
c.ResponseError(errMsg)
return
}
if checkDest, ok = util.GetE164Number(dest, user.GetCountryCode("")); !ok {
c.ResponseError(fmt.Sprintf(c.T("verification:Phone number is invalid in your region %s"), user.CountryCode))
if checkDest, ok = util.GetE164Number(dest, countryCode); !ok {
c.ResponseError(fmt.Sprintf(c.T("verification:Phone number is invalid in your region %s"), countryCode))
return
}
} else if destType == object.VerifyTypeEmail {
@@ -568,7 +577,8 @@ func (c *ApiController) ResetEmailOrPhone() {
_, err = object.UpdateUser(id, user, columns, false)
case object.VerifyTypePhone:
user.Phone = dest
_, err = object.SetUserField(user, "phone", user.Phone)
user.CountryCode = countryCode
_, err = object.UpdateUser(user.GetId(), user, []string{"phone", "country_code"}, false)
default:
c.ResponseError(c.T("verification:Unknown type"))
return
+26 -2
View File
@@ -39,6 +39,17 @@ func CheckUserSignup(application *Application, organization *Organization, authF
return i18n.Translate(lang, "check:Organization does not exist")
}
// Normalize the phone number before the duplication checks below
if authForm.Phone != "" {
normalizedPhone, normalizedCountryCode, ok := util.GetNormalizedPhone(authForm.Phone, authForm.CountryCode)
if !ok {
return i18n.Translate(lang, "check:Phone number is invalid")
}
authForm.Phone = normalizedPhone
authForm.CountryCode = normalizedCountryCode
}
if application.IsSignupItemVisible("Username") {
if len(authForm.Username) <= 1 {
return i18n.Translate(lang, "check:Username must have at least 2 characters")
@@ -802,8 +813,21 @@ func CheckUpdateUser(oldUser, user *User, lang string) string {
}
}
if oldUser.Phone != user.Phone || oldUser.CountryCode != user.CountryCode {
if HasUserByPhoneAndCountryCode(user.Owner, user.Phone, user.CountryCode) {
return i18n.Translate(lang, "check:Phone already exists")
if user.Phone != "" {
normalizedPhone, normalizedCountryCode, ok := util.GetNormalizedPhone(user.Phone, user.GetCountryCode(""))
if !ok {
return i18n.Translate(lang, "check:Phone number is invalid")
}
user.Phone = normalizedPhone
user.CountryCode = normalizedCountryCode
}
// The normalization may have turned the new phone number into the old one
if oldUser.Phone != user.Phone || oldUser.CountryCode != user.CountryCode {
if HasUserByPhoneAndCountryCode(user.Owner, user.Phone, user.CountryCode) {
return i18n.Translate(lang, "check:Phone already exists")
}
}
}
if oldUser.IpWhitelist != user.IpWhitelist {
+21
View File
@@ -84,6 +84,27 @@ func GetE164Number(phone string, countryCode string) (string, bool) {
return phonenumbers.Format(phoneNumber, phonenumbers.E164), phonenumbers.IsValidNumber(phoneNumber)
}
// GetNormalizedPhone converts a phone number like "+48 666 666 666" into the national number
// in digits only (e.g. "666666666") and the region code (e.g. "PL"), it returns the input
// unchanged when the phone number is invalid.
func GetNormalizedPhone(phone string, countryCode string) (string, string, bool) {
phoneNumber, err := phonenumbers.Parse(phone, countryCode)
if err != nil || !phonenumbers.IsValidNumber(phoneNumber) {
return phone, countryCode, false
}
// Only take the region from the number itself when it is in the international format,
// so that a shared calling code like "+1" doesn't overwrite the chosen country code
normalizedCountryCode := countryCode
if normalizedCountryCode == "" || strings.HasPrefix(strings.TrimSpace(phone), "+") {
if regionCode := phonenumbers.GetRegionCodeForNumber(phoneNumber); regionCode != "" {
normalizedCountryCode = regionCode
}
}
return fmt.Sprintf("%d", phoneNumber.GetNationalNumber()), normalizedCountryCode, true
}
func GetCountryCode(prefix string, phone string) (string, error) {
if prefix == "" || phone == "" {
return "", nil