fix for phone generation on email signup
This commit is contained in:
parent
cee960fea0
commit
3409f190b9
20 changed files with 457 additions and 54 deletions
|
|
@ -1,6 +1,10 @@
|
|||
package domain
|
||||
|
||||
import "strings"
|
||||
import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// EmailPhonePrefix marks a "phone number" as a synthetic identity encoding an
|
||||
// email address, not a real phone. It reuses Telegram's own +888 "Anonymous
|
||||
|
|
@ -24,11 +28,11 @@ const MaxEmailSignupPhoneLen = 200
|
|||
const emailPhoneEscape = 'q'
|
||||
|
||||
var emailPhoneEscapeEncode = map[rune]byte{
|
||||
'@': '0',
|
||||
'.': '1',
|
||||
'-': '2',
|
||||
'_': '3',
|
||||
'+': '4',
|
||||
'@': '0',
|
||||
'.': '1',
|
||||
'-': '2',
|
||||
'_': '3',
|
||||
'+': '4',
|
||||
emailPhoneEscape: '5',
|
||||
}
|
||||
|
||||
|
|
@ -135,6 +139,33 @@ 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 =
|
||||
// "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) {
|
||||
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)
|
||||
for _, v := range b {
|
||||
out.WriteByte('0' + v%10)
|
||||
}
|
||||
return out.String(), nil
|
||||
}
|
||||
|
||||
// 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
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
package domain
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEncodeDecodeEmailPhoneRoundTrip(t *testing.T) {
|
||||
for _, email := range []string{
|
||||
|
|
@ -62,3 +65,32 @@ func TestDecodeEmailPhoneRejectsNonEmailNumbers(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewEmailSignupDisplayPhoneLooksLikeARealPhoneNumber(t *testing.T) {
|
||||
seen := make(map[string]struct{})
|
||||
for i := 0; i < 200; i++ {
|
||||
phone, err := NewEmailSignupDisplayPhone()
|
||||
if err != nil {
|
||||
t.Fatalf("NewEmailSignupDisplayPhone: %v", err)
|
||||
}
|
||||
if !strings.HasPrefix(phone, EmailPhonePrefix) {
|
||||
t.Fatalf("phone %q missing %q prefix", phone, EmailPhonePrefix)
|
||||
}
|
||||
if !ValidPhone(phone) {
|
||||
t.Fatalf("phone %q fails ValidPhone", phone)
|
||||
}
|
||||
// Must be indistinguishable from a real phone number: no letters, so
|
||||
// IsEmailSignupPhone/DecodeEmailPhone never mistake it for a wire
|
||||
// email-signup value once it's assigned as an account's real phone.
|
||||
if IsEmailSignupPhone(phone) {
|
||||
t.Fatalf("IsEmailSignupPhone(%q) = true, want false (must look like a real number)", phone)
|
||||
}
|
||||
if _, ok := DecodeEmailPhone(phone); ok {
|
||||
t.Fatalf("DecodeEmailPhone(%q) unexpectedly succeeded", phone)
|
||||
}
|
||||
seen[phone] = struct{}{}
|
||||
}
|
||||
if len(seen) < 190 {
|
||||
t.Fatalf("only %d distinct values out of 200 draws, generator looks non-random", len(seen))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,11 @@ type PhoneChangeRequest struct {
|
|||
Date int
|
||||
ExcludeAuthKeyID [8]byte
|
||||
ExcludeSessionID int64
|
||||
// SignupEmail, when non-empty, is written to users.signup_email in the
|
||||
// same transaction as Phone. Only email-signup phone changes set this
|
||||
// (see account.Service.ChangePhone); ordinary phone-number changes leave
|
||||
// it as the zero value and the column untouched.
|
||||
SignupEmail string
|
||||
}
|
||||
|
||||
type PhoneChangeResult struct {
|
||||
|
|
|
|||
|
|
@ -22,9 +22,14 @@ func (c PeerColor) Empty() bool {
|
|||
// User 是一个账号。第一阶段仅保留登录链路必须字段;
|
||||
// access_hash 为任何 InputUser 校验所必须,不可省。
|
||||
type User struct {
|
||||
ID int64
|
||||
AccessHash int64
|
||||
Phone string
|
||||
ID int64
|
||||
AccessHash int64
|
||||
Phone string
|
||||
// SignupEmail is set only for email-signup accounts (see
|
||||
// domain.NewEmailSignupDisplayPhone): it is the durable email->user
|
||||
// reverse lookup key, since Phone itself no longer encodes the email.
|
||||
// Empty for every ordinary phone-number account.
|
||||
SignupEmail string
|
||||
FirstName string
|
||||
LastName string
|
||||
About string
|
||||
|
|
|
|||
|
|
@ -30,10 +30,12 @@ func OfficialWelcomeMessage(userID int64, method string, date int) (Message, err
|
|||
}
|
||||
|
||||
// SignInMethodLabel returns the human-readable method name embedded in
|
||||
// OfficialWelcomeMessage, derived from whether phone is an email-signup
|
||||
// synthetic number (see EncodeEmailPhone) or a real phone number.
|
||||
func SignInMethodLabel(phone string) string {
|
||||
if IsEmailSignupPhone(phone) {
|
||||
// OfficialWelcomeMessage. Email-signup accounts are identified by
|
||||
// SignupEmail (see NewEmailSignupDisplayPhone) rather than by their stored
|
||||
// Phone, which — once assigned — is an ordinary-looking short number that
|
||||
// carries no information about the signup method.
|
||||
func SignInMethodLabel(u User) string {
|
||||
if u.SignupEmail != "" {
|
||||
return "email"
|
||||
}
|
||||
return "phone number"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue