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

@ -3,6 +3,7 @@ package account
import (
"context"
"crypto/rand"
"encoding/binary"
"encoding/hex"
"fmt"
"strings"
@ -242,14 +243,18 @@ func (s *Service) phoneChangeCaller(ctx context.Context, userID int64, authKeyID
// collision-retry loop so a store failure can't spin forever.
const maxEmailSignupPhoneAttempts = 20
// assignEmailSignupDisplayPhone generates a short "888" display number for
// an email-signup account rebinding to a new email (see
// assignEmailSignupDisplayPhone generates a short display number for an
// email-signup account rebinding to a new email (see
// domain.NewEmailSignupDisplayPhone / auth.Service's SignUp counterpart),
// re-rolling on the astronomically unlikely collision with an existing
// account's phone.
func (s *Service) assignEmailSignupDisplayPhone(ctx context.Context) (string, error) {
prefix, err := randomEmailSignupPhonePrefix(s.emailSignupPhonePrefixes)
if err != nil {
return "", err
}
for i := 0; i < maxEmailSignupPhoneAttempts; i++ {
candidate, err := domain.NewEmailSignupDisplayPhone()
candidate, err := domain.NewEmailSignupDisplayPhone(prefix)
if err != nil {
return "", err
}
@ -262,6 +267,25 @@ func (s *Service) assignEmailSignupDisplayPhone(ctx context.Context) (string, er
return "", fmt.Errorf("assign email signup display phone: exhausted %d attempts", maxEmailSignupPhoneAttempts)
}
// randomEmailSignupPhonePrefix mirrors auth.Service's identical helper
// (unexported to each package, but must pick with the same fairness): pick
// one entry at random from prefixes, falling back to domain.EmailPhonePrefix
// ("888") when the list is empty.
func randomEmailSignupPhonePrefix(prefixes []string) (string, error) {
if len(prefixes) == 0 {
return domain.EmailPhonePrefix, nil
}
if len(prefixes) == 1 {
return prefixes[0], nil
}
var b [8]byte
if _, err := rand.Read(b[:]); err != nil {
return "", fmt.Errorf("pick email signup phone prefix: %w", err)
}
idx := binary.LittleEndian.Uint64(b[:]) % uint64(len(prefixes))
return prefixes[idx], nil
}
func phoneChangeHash() (string, error) {
var raw [8]byte
if _, err := rand.Read(raw[:]); err != nil {

View file

@ -55,6 +55,10 @@ type Service struct {
// 验证码走 loginEmailSender 发到解码出的邮箱;非 888 号码一律拒绝——本服务器没有真实
// 短信通道,放行会让账号的邮箱身份绑定被绕过(见迁移前的密码找回验证码漏洞教训)。
emailSignupEnabled bool
// emailSignupPhonePrefixes 是改绑邮箱时随机挑选的账号展示号码号段前缀
// 列表(domain.NewEmailSignupDisplayPhone),与 auth.Service 的同名字段
// 同一份服务端配置来源。为空时退回默认 "888"。
emailSignupPhonePrefixes []string
}
// ServiceOption 调整 account 服务依赖。
@ -163,6 +167,14 @@ func WithEmailSignup(enabled bool) ServiceOption {
}
}
// WithEmailSignupPhonePrefixes 设置改绑邮箱时展示号码随机挑选的号段前缀列表
// (见 emailSignupPhonePrefixes 字段注释)。
func WithEmailSignupPhonePrefixes(prefixes []string) ServiceOption {
return func(s *Service) {
s.emailSignupPhonePrefixes = prefixes
}
}
// NewService 创建 account 服务。
func NewService(passwords store.PasswordStore, opts ...ServiceOption) *Service {
s := &Service{