merged with fixes
This commit is contained in:
parent
a9e758b712
commit
2f1818d656
176 changed files with 9000 additions and 907 deletions
|
|
@ -4,6 +4,9 @@ import (
|
|||
"errors"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
|
||||
"github.com/nyaruka/phonenumbers"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -260,38 +263,105 @@ func MaskEmail(email string) string {
|
|||
return name[:1] + "***" + name[len(name)-1:] + email[at:]
|
||||
}
|
||||
|
||||
// NormalizePhone 仅保留手机号中的数字(与 users.phone 的存储形态一致)。全部被过滤
|
||||
// 掉时返回原串,便于上层做 validPhone 拒绝。auth/account 两域共用同一规则避免漂移。
|
||||
//
|
||||
// Email-signup 合成号码(EncodeEmailPhone 生成,"888" 前缀 + 至少一个字母)是唯一例外:
|
||||
// 原样保留(仅 lower+trim),不剥离字母——否则 DecodeEmailPhone 会因编码内容被剥空而
|
||||
// 永远解不出邮箱。真实手机号恒为纯数字,不含字母,故这个判定不会误伤任何真实号码。
|
||||
func NormalizePhone(phone string) string {
|
||||
if IsEmailSignupPhone(phone) {
|
||||
return strings.ToLower(strings.TrimSpace(phone))
|
||||
}
|
||||
// virtualLoginPhoneMinDigits/virtualLoginPhoneMaxDigits bound the "888"-prefixed
|
||||
// virtual login identity range NormalizePhone accepts without going through
|
||||
// libphonenumber (real E.164 numbers never start with 888). This is a login
|
||||
// identity concept only -- distinct from, and independent of, ownership of any
|
||||
// purchasable collectible-phone asset with the same digit shape.
|
||||
const (
|
||||
virtualLoginPhoneMinDigits = 7
|
||||
virtualLoginPhoneMaxDigits = 15
|
||||
)
|
||||
|
||||
// PhoneDigits removes presentation punctuation from a phone number. It is
|
||||
// intentionally not an identity canonicalizer: callers that select accounts,
|
||||
// issue codes, or persist users must use NormalizePhone and ValidPhone.
|
||||
func PhoneDigits(phone string) string {
|
||||
var b strings.Builder
|
||||
b.Grow(len(phone))
|
||||
seenDigit := false
|
||||
seenPlus := false
|
||||
for _, r := range phone {
|
||||
if r >= '0' && r <= '9' {
|
||||
switch {
|
||||
case r >= '0' && r <= '9':
|
||||
b.WriteRune(r)
|
||||
seenDigit = true
|
||||
case r == '+':
|
||||
if seenPlus || seenDigit {
|
||||
return ""
|
||||
}
|
||||
seenPlus = true
|
||||
case unicode.IsSpace(r), r == '-', r == '(', r == ')', r == '.', r == '/':
|
||||
// Presentation separators accepted by official clients and contact UIs.
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
if b.Len() == 0 {
|
||||
return phone
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// ValidPhone 校验 NormalizePhone 后的持久化形态:真实手机号是 5-200 位纯数字;
|
||||
// email-signup 合成号码额外允许小写字母(EncodeEmailPhone 的转义字符集)。
|
||||
// 上限与 users.phone 列宽一致;当前开发登录/改号链路不强制精确 E.164 长度,
|
||||
// 但拒绝空串、非法字符和会截断的超长输入。
|
||||
func ValidPhone(phone string) bool {
|
||||
if len(phone) < 5 || len(phone) > 200 {
|
||||
return false
|
||||
}
|
||||
// NormalizePhone returns the one persisted login identity. Virtual +888
|
||||
// identities are independent of the collectible-phone registry and accept
|
||||
// 7-15 canonical digits. Ordinary international numbers use E.164 digits
|
||||
// without the leading '+'. Their parsing is deliberately country-aware so a
|
||||
// national trunk prefix is removed only where the numbering plan says it is a
|
||||
// prefix. For example, both +98 0998 167 9461 and +98 998 167 9461 become
|
||||
// 989981679461, while Italy's significant leading zero in +39 02 ... is retained.
|
||||
//
|
||||
// Email-signup synthetic numbers (EncodeEmailPhone, "888" prefix plus at least
|
||||
// one letter) are a separate exception, kept as-is (lower+trim only, no digit
|
||||
// stripping) -- otherwise DecodeEmailPhone could never recover the email from
|
||||
// an already letter-stripped value. A real phone is always pure digits, so
|
||||
// this check never misclassifies one.
|
||||
//
|
||||
// IsPossibleNumber is the structural gate rather than IsValidNumber. It keeps
|
||||
// syntactically possible reserved/test ranges usable without accepting local
|
||||
// numbers that omit their country calling code or numbers outside E.164's
|
||||
// length/plan metadata.
|
||||
func NormalizePhone(phone string) string {
|
||||
if IsEmailSignupPhone(phone) {
|
||||
return strings.ToLower(strings.TrimSpace(phone))
|
||||
}
|
||||
digits := PhoneDigits(phone)
|
||||
if digits == "" {
|
||||
return ""
|
||||
}
|
||||
// Every syntactically valid +888 virtual number is an independent login
|
||||
// identity; minting or owning the same collectible-phone value is not a
|
||||
// prerequisite. users.phone therefore takes lookup precedence over any
|
||||
// optional collectible alias registry.
|
||||
if len(digits) >= virtualLoginPhoneMinDigits &&
|
||||
len(digits) <= virtualLoginPhoneMaxDigits &&
|
||||
strings.HasPrefix(digits, "888") {
|
||||
return digits
|
||||
}
|
||||
// 42777 is the reserved, non-login phone of the built-in service identity.
|
||||
// It predates the ordinary E.164 user invariant and remains resolvable only
|
||||
// so auth can reject it as a system account instead of treating it as free.
|
||||
if digits == OfficialSystemPhone {
|
||||
return digits
|
||||
}
|
||||
number, err := phonenumbers.Parse("+"+digits, phonenumbers.UNKNOWN_REGION)
|
||||
if err != nil || !phonenumbers.IsPossibleNumber(number) {
|
||||
return ""
|
||||
}
|
||||
canonical := strings.TrimPrefix(phonenumbers.Format(number, phonenumbers.E164), "+")
|
||||
if canonical == "" || len(canonical) > 15 {
|
||||
return ""
|
||||
}
|
||||
return canonical
|
||||
}
|
||||
|
||||
// ValidPhone reports whether phone is already in the persisted canonical form.
|
||||
// Callers accepting user input normalize first, then validate, so equivalent
|
||||
// international spellings converge before lookup, rate limiting, OTP delivery,
|
||||
// and uniqueness checks. Email-signup synthetic numbers keep their own
|
||||
// lower+trim canonical form (see NormalizePhone).
|
||||
func ValidPhone(phone string) bool {
|
||||
if IsEmailSignupPhone(phone) {
|
||||
if len(phone) < 5 || len(phone) > 200 {
|
||||
return false
|
||||
}
|
||||
for _, r := range phone {
|
||||
if !(r >= 'a' && r <= 'z') && !(r >= '0' && r <= '9') {
|
||||
return false
|
||||
|
|
@ -299,10 +369,6 @@ func ValidPhone(phone string) bool {
|
|||
}
|
||||
return true
|
||||
}
|
||||
for _, r := range phone {
|
||||
if r < '0' || r > '9' {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
canonical := NormalizePhone(phone)
|
||||
return canonical != "" && canonical == phone
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue