added config for virtual phone number

This commit is contained in:
onysd 2026-07-14 18:37:27 +03:00
parent aa2fc45bdd
commit 1eaa06d961
10 changed files with 268 additions and 31 deletions

View file

@ -139,33 +139,53 @@ func NormalizeEmailForPhone(email string) string {
return strings.ToLower(strings.TrimSpace(email))
}
// emailSignupDisplayPhoneDigits is how many random digits follow the "888"
// prefix in a NewEmailSignupDisplayPhone result, e.g. "888" + 8 digits =
// emailSignupDisplayPhoneDigits is how many random digits follow the prefix
// in a NewEmailSignupDisplayPhone result, e.g. prefix "888" + 8 digits =
// "88812345678" (formats on-screen as something like "+888 1234 5678").
const emailSignupDisplayPhoneDigits = 8
// NewEmailSignupDisplayPhone generates a short, all-digit "888" phone number
// for an email-signup account's users.phone column. Unlike EncodeEmailPhone
// this carries no information about the email — it is purely a
// normal-looking display/identity number — so the caller must separately
// persist the email->user association (see User.SignupEmail) for returning
// logins to be found. Because the result is all digits, IsEmailSignupPhone
// on it is always false: once assigned, it behaves exactly like a real phone
// number everywhere else in the system (contacts, search, ByPhone lookups).
func NewEmailSignupDisplayPhone() (string, error) {
// NewEmailSignupDisplayPhone generates a short, all-digit phone number for an
// email-signup account's users.phone column, using the given prefix (one of
// the server's configured EmailSignupPhonePrefixes — see
// help.getAppConfig's email_signup_phone_prefixes, config.go). This is
// unrelated to EncodeEmailPhone's own fixed "888" wire prefix: that one only
// ever travels internally between client and server during sendCode/signIn
// and is never shown to anyone, so it has no reason to be configurable,
// unlike this display number, which is the account's actual, permanent,
// user-visible phone. The caller must separately persist the email->user
// association (see User.SignupEmail) for returning logins to be found.
// Because the result is all digits, IsEmailSignupPhone on it is always
// false: once assigned, it behaves exactly like a real phone number
// everywhere else in the system (contacts, search, ByPhone lookups).
func NewEmailSignupDisplayPhone(prefix string) (string, error) {
if !isAllASCIIDigits(prefix) {
return "", fmt.Errorf("email signup display phone prefix %q must be non-empty digits", prefix)
}
b := make([]byte, emailSignupDisplayPhoneDigits)
if _, err := rand.Read(b); err != nil {
return "", fmt.Errorf("generate email signup display phone: %w", err)
}
var out strings.Builder
out.Grow(len(EmailPhonePrefix) + emailSignupDisplayPhoneDigits)
out.WriteString(EmailPhonePrefix)
out.Grow(len(prefix) + emailSignupDisplayPhoneDigits)
out.WriteString(prefix)
for _, v := range b {
out.WriteByte('0' + v%10)
}
return out.String(), nil
}
func isAllASCIIDigits(s string) bool {
if s == "" {
return false
}
for _, r := range s {
if r < '0' || r > '9' {
return false
}
}
return true
}
// IsEmailSignupPhone reports whether phone was produced by EncodeEmailPhone.
// Every encoded value contains at least one letter (the mandatory '@'
// escape's 'q' marker byte), which real, all-digit phone numbers — even

View file

@ -69,7 +69,7 @@ func TestDecodeEmailPhoneRejectsNonEmailNumbers(t *testing.T) {
func TestNewEmailSignupDisplayPhoneLooksLikeARealPhoneNumber(t *testing.T) {
seen := make(map[string]struct{})
for i := 0; i < 200; i++ {
phone, err := NewEmailSignupDisplayPhone()
phone, err := NewEmailSignupDisplayPhone(EmailPhonePrefix)
if err != nil {
t.Fatalf("NewEmailSignupDisplayPhone: %v", err)
}
@ -94,3 +94,26 @@ func TestNewEmailSignupDisplayPhoneLooksLikeARealPhoneNumber(t *testing.T) {
t.Fatalf("only %d distinct values out of 200 draws, generator looks non-random", len(seen))
}
}
func TestNewEmailSignupDisplayPhoneHonorsConfiguredPrefix(t *testing.T) {
for _, prefix := range []string{"380", "1", "7777"} {
phone, err := NewEmailSignupDisplayPhone(prefix)
if err != nil {
t.Fatalf("NewEmailSignupDisplayPhone(%q): %v", prefix, err)
}
if !strings.HasPrefix(phone, prefix) {
t.Fatalf("phone %q missing configured prefix %q", phone, prefix)
}
if !ValidPhone(phone) {
t.Fatalf("phone %q fails ValidPhone", phone)
}
}
}
func TestNewEmailSignupDisplayPhoneRejectsInvalidPrefix(t *testing.T) {
for _, prefix := range []string{"", "abc", "88q", "-1"} {
if _, err := NewEmailSignupDisplayPhone(prefix); err == nil {
t.Fatalf("NewEmailSignupDisplayPhone(%q) err = nil, want error", prefix)
}
}
}