changes for email signup
This commit is contained in:
parent
0fbfc8cd71
commit
3d9b0f1de7
15 changed files with 469 additions and 27 deletions
|
|
@ -249,11 +249,12 @@ func NormalizePhone(phone string) string {
|
|||
return b.String()
|
||||
}
|
||||
|
||||
// ValidPhone 校验 NormalizePhone 后的持久化形态:5-32 位纯数字。
|
||||
// ValidPhone 校验 NormalizePhone 后的持久化形态:5-200 位纯数字。
|
||||
// 上限与 users.phone 列宽一致;当前开发登录/改号链路不强制精确 E.164 长度,
|
||||
// 但拒绝空串、非数字和会截断的超长输入。
|
||||
// 但拒绝空串、非数字和会截断的超长输入。上限从 32 放宽到 200 是为了容纳
|
||||
// EncodeEmailPhone 生成的 "888"+反向可解码大整数(真实手机号远用不到这个上限)。
|
||||
func ValidPhone(phone string) bool {
|
||||
if len(phone) < 5 || len(phone) > 32 {
|
||||
if len(phone) < 5 || len(phone) > 200 {
|
||||
return false
|
||||
}
|
||||
for _, r := range phone {
|
||||
|
|
|
|||
74
internal/domain/emailphone.go
Normal file
74
internal/domain/emailphone.go
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
package domain
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"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
|
||||
// Number" range (already declared in this server's help.getAppConfig
|
||||
// fragment_prefixes), so patched clients that already special-case 888
|
||||
// numbers have a head start, and the range is guaranteed to never collide
|
||||
// with a real assigned country code.
|
||||
const EmailPhonePrefix = "888"
|
||||
|
||||
// MaxEmailSignupPhoneLen mirrors the users.phone column width (see migration
|
||||
// 0087) and ValidPhone's upper bound.
|
||||
const MaxEmailSignupPhoneLen = 200
|
||||
|
||||
// EncodeEmailPhone deterministically and reversibly encodes an email address
|
||||
// into a synthetic "888"-prefixed all-digit phone number: the email's
|
||||
// lowercased/trimmed UTF-8 bytes, read as a big-endian unsigned integer, then
|
||||
// printed in decimal. This lets the existing phone-based sendCode/signUp/
|
||||
// signIn/changePhone flow carry an email address end to end unchanged — no
|
||||
// new TL constructors, no server-side reverse-lookup table required.
|
||||
//
|
||||
// ok is false if email is empty/invalid or the encoded result would not fit
|
||||
// the users.phone column (MaxEmailSignupPhoneLen) — this comfortably covers
|
||||
// realistic email addresses (roughly up to 80 bytes).
|
||||
func EncodeEmailPhone(email string) (phone string, ok bool) {
|
||||
normalized := NormalizeEmailForPhone(email)
|
||||
if normalized == "" || !strings.Contains(normalized, "@") {
|
||||
return "", false
|
||||
}
|
||||
n := new(big.Int).SetBytes([]byte(normalized))
|
||||
digits := n.String()
|
||||
phone = EmailPhonePrefix + digits
|
||||
if len(phone) > MaxEmailSignupPhoneLen {
|
||||
return "", false
|
||||
}
|
||||
return phone, true
|
||||
}
|
||||
|
||||
// DecodeEmailPhone reverses EncodeEmailPhone. ok is false if phone does not
|
||||
// carry the "888" prefix or does not decode to a plausible email address.
|
||||
func DecodeEmailPhone(phone string) (email string, ok bool) {
|
||||
phone = NormalizePhone(strings.TrimSpace(phone))
|
||||
digits, found := strings.CutPrefix(phone, EmailPhonePrefix)
|
||||
if !found || digits == "" {
|
||||
return "", false
|
||||
}
|
||||
n, valid := new(big.Int).SetString(digits, 10)
|
||||
if !valid {
|
||||
return "", false
|
||||
}
|
||||
decoded := string(n.Bytes())
|
||||
if decoded == "" || !strings.Contains(decoded, "@") {
|
||||
return "", false
|
||||
}
|
||||
return decoded, true
|
||||
}
|
||||
|
||||
// NormalizeEmailForPhone lowercases and trims an email so the same address
|
||||
// always encodes to the same synthetic phone number regardless of how the
|
||||
// user typed it (e.g. on a different device).
|
||||
func NormalizeEmailForPhone(email string) string {
|
||||
return strings.ToLower(strings.TrimSpace(email))
|
||||
}
|
||||
|
||||
// IsEmailSignupPhone reports whether phone was produced by EncodeEmailPhone
|
||||
// (i.e. carries the synthetic "888" prefix), without decoding it.
|
||||
func IsEmailSignupPhone(phone string) bool {
|
||||
return strings.HasPrefix(NormalizePhone(strings.TrimSpace(phone)), EmailPhonePrefix)
|
||||
}
|
||||
64
internal/domain/emailphone_test.go
Normal file
64
internal/domain/emailphone_test.go
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
package domain
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestEncodeDecodeEmailPhoneRoundTrip(t *testing.T) {
|
||||
for _, email := range []string{
|
||||
"onysd@owpengram.local",
|
||||
"a@b.co",
|
||||
"User.Name+Tag@Example.COM",
|
||||
"very.long.email.address.for.testing.purposes@some-long-domain-name.example.com",
|
||||
} {
|
||||
t.Run(email, func(t *testing.T) {
|
||||
phone, ok := EncodeEmailPhone(email)
|
||||
if !ok {
|
||||
t.Fatalf("EncodeEmailPhone(%q) ok=false", email)
|
||||
}
|
||||
if len(phone) > MaxEmailSignupPhoneLen {
|
||||
t.Fatalf("encoded phone too long: %d chars", len(phone))
|
||||
}
|
||||
if !ValidPhone(phone) {
|
||||
t.Fatalf("encoded phone %q fails ValidPhone", phone)
|
||||
}
|
||||
if !IsEmailSignupPhone(phone) {
|
||||
t.Fatalf("IsEmailSignupPhone(%q) = false, want true", phone)
|
||||
}
|
||||
decoded, ok := DecodeEmailPhone(phone)
|
||||
if !ok {
|
||||
t.Fatalf("DecodeEmailPhone(%q) ok=false", phone)
|
||||
}
|
||||
want := NormalizeEmailForPhone(email)
|
||||
if decoded != want {
|
||||
t.Fatalf("decoded = %q, want %q", decoded, want)
|
||||
}
|
||||
t.Logf("%q -> %q (%d chars) -> %q", email, phone, len(phone), decoded)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodeEmailPhoneCaseAndWhitespaceNormalize(t *testing.T) {
|
||||
p1, ok1 := EncodeEmailPhone("User@Example.com")
|
||||
p2, ok2 := EncodeEmailPhone(" user@example.com ")
|
||||
if !ok1 || !ok2 {
|
||||
t.Fatalf("ok1=%v ok2=%v", ok1, ok2)
|
||||
}
|
||||
if p1 != p2 {
|
||||
t.Fatalf("case/whitespace variants encoded differently: %q vs %q", p1, p2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEncodeEmailPhoneRejectsInvalid(t *testing.T) {
|
||||
for _, email := range []string{"", " ", "not-an-email", "@"} {
|
||||
if _, ok := EncodeEmailPhone(email); ok && email != "@" {
|
||||
t.Fatalf("EncodeEmailPhone(%q) ok=true, want false", email)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeEmailPhoneRejectsNonEmailNumbers(t *testing.T) {
|
||||
for _, phone := range []string{"", "15550001234", "888", "88799999"} {
|
||||
if _, ok := DecodeEmailPhone(phone); ok {
|
||||
t.Fatalf("DecodeEmailPhone(%q) ok=true, want false", phone)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue