fix: adjust phone registration username flow

This commit is contained in:
richjohnrambo
2026-08-20 12:08:55 +08:00
parent 73ddb2d9fa
commit 43e8af68d6
7 changed files with 202 additions and 78 deletions
+91 -15
View File
@@ -2,6 +2,7 @@ package main
import (
"container/heap"
crand "crypto/rand"
"math/rand"
"strings"
"time"
@@ -41,13 +42,17 @@ func replyCreateUser(s *Session, msg *ClientComMessage, rec *auth.Rec) {
return
}
account, phone, phoneRegistration, err := resolveRegistrationAccount(msg.Acc.Username, msg.Acc.Phone)
if err != nil || !registrationSecretMatches(msg.Acc.Secret, account) {
account, phone, secretAccount, phoneRegistration, err := resolveRegistrationAccount(msg.Acc.Username, msg.Acc.Phone)
if err != nil || !registrationSecretMatches(msg.Acc.Secret, secretAccount) {
logs.Warn.Println("create user: invalid registration account fields, sid=", s.sid)
s.queueOut(decodeStoreError(types.ErrPolicy, msg.Id, msg.Timestamp,
map[string]any{"what": "account", "reason": "REGISTRATION_ACCOUNT_INVALID"}))
return
}
authSecret := msg.Acc.Secret
if account != secretAccount {
authSecret = registrationSecretForAccount(msg.Acc.Secret, account)
}
inviteCode := strings.TrimSpace(msg.Acc.InviteCode)
if msg.Acc.InviteCode != inviteCode || (inviteCode != "" && !isValidIMInviteCode(inviteCode)) {
s.queueOut(decodeStoreError(types.ErrPolicy, msg.Id, msg.Timestamp,
@@ -57,11 +62,24 @@ func replyCreateUser(s *Session, msg *ClientComMessage, rec *auth.Rec) {
// Check if login is unique and compliance with the policy (not too long or too short).
authCtx := auth.AuthContext{TenantID: s.tenantID, RemoteAddr: s.remoteAddr}
if ok, err := authhdl.IsUnique(authCtx, msg.Acc.Secret); !ok {
logs.Warn.Println("create user: auth secret is not compliant", err, "sid=", s.sid)
s.queueOut(decodeStoreError(err, msg.Id, msg.Timestamp,
map[string]any{"what": "auth"}))
return
for attempts := 0; ; attempts++ {
if ok, err := authhdl.IsUnique(authCtx, authSecret); ok {
break
} else if account != secretAccount && err == types.ErrDuplicate && attempts < 8 {
account, err = randomRegistrationUsername()
if err != nil {
logs.Warn.Println("create user: failed to generate username", err, "sid=", s.sid)
s.queueOut(ErrUnknown(msg.Id, "", msg.Timestamp))
return
}
authSecret = registrationSecretForAccount(msg.Acc.Secret, account)
continue
} else {
logs.Warn.Println("create user: auth secret is not compliant", err, "sid=", s.sid)
s.queueOut(decodeStoreError(err, msg.Id, msg.Timestamp,
map[string]any{"what": "auth"}))
return
}
}
var user types.User
@@ -193,6 +211,7 @@ func replyCreateUser(s *Session, msg *ClientComMessage, rec *auth.Rec) {
private = msg.Acc.Desc.Private
}
}
user.Public = registrationPublicWithUsername(user.Public, user.Username)
// Create user record in the database.
if _, err := store.Users.Create(s.tenantID, &user, private); err != nil {
@@ -203,7 +222,7 @@ func replyCreateUser(s *Session, msg *ClientComMessage, rec *auth.Rec) {
// Add authentication record.
rec, err = authhdl.AddRecord(authCtx,
&auth.Rec{TenantID: s.tenantID, Uid: user.Uid()}, msg.Acc.Secret)
&auth.Rec{TenantID: s.tenantID, Uid: user.Uid()}, authSecret)
if err != nil {
logs.Warn.Println("create user: add auth record failed", err, "sid=", s.sid)
s.queueOut(decodeStoreError(err, msg.Id, msg.Timestamp, nil))
@@ -299,6 +318,7 @@ func replyCreateUser(s *Session, msg *ClientComMessage, rec *auth.Rec) {
}
params := reply.Ctrl.Params.(map[string]any)
params["username"] = user.Username
params["desc"] = &MsgTopicDesc{
CreatedAt: &user.CreatedAt,
UpdatedAt: &user.UpdatedAt,
@@ -315,26 +335,43 @@ func replyCreateUser(s *Session, msg *ClientComMessage, rec *auth.Rec) {
pluginAccount(&user, plgActCreate)
}
func resolveRegistrationAccount(username, phone string) (account, canonicalPhone string, phoneRegistration bool, err error) {
func resolveRegistrationAccount(username, phone string) (account, canonicalPhone, secretAccount string, phoneRegistration bool, err error) {
if phone != "" {
if len(phone) != 11 || !asciiRegistrationDigits(phone) {
return "", "", false, types.ErrPolicy
return "", "", "", false, types.ErrPolicy
}
canonicalPhone, err = normalizeIMPhone(phone, "CN")
if err != nil {
return "", "", false, err
return "", "", "", false, err
}
return canonicalPhone, canonicalPhone, true, nil
if username == "" {
account, err = randomRegistrationUsername()
if err != nil {
return "", "", "", false, err
}
return account, canonicalPhone, canonicalPhone, true, nil
}
if err = validateRegistrationUsername(username); err != nil {
return "", "", "", false, err
}
return username, canonicalPhone, username, true, nil
}
if err = validateRegistrationUsername(username); err != nil {
return "", "", "", false, err
}
return username, "", username, false, nil
}
func validateRegistrationUsername(username string) error {
if username == "" || len(username) > 10 {
return "", "", false, types.ErrPolicy
return types.ErrPolicy
}
for i := range username {
if (username[i] < 'a' || username[i] > 'z') && (username[i] < '0' || username[i] > '9') {
return "", "", false, types.ErrPolicy
return types.ErrPolicy
}
}
return username, "", false, nil
return nil
}
func asciiRegistrationDigits(value string) bool {
@@ -351,6 +388,45 @@ func registrationSecretMatches(secret []byte, account string) bool {
return separator > 0 && string(secret[:separator]) == account
}
func registrationSecretForAccount(secret []byte, account string) []byte {
separator := strings.IndexByte(string(secret), ':')
if separator < 0 {
return secret
}
return []byte(account + string(secret[separator:]))
}
func randomRegistrationUsername() (string, error) {
const letters = "abcdefghijklmnopqrstuvwxyz"
const digits = "0123456789"
var raw [9]byte
if _, err := crand.Read(raw[:]); err != nil {
return "", err
}
out := make([]byte, 9)
for i := 0; i < 3; i++ {
out[i] = letters[int(raw[i])%len(letters)]
}
for i := 3; i < 9; i++ {
out[i] = digits[int(raw[i])%len(digits)]
}
return string(out), nil
}
func registrationPublicWithUsername(public any, username string) any {
if username == "" {
return public
}
if public == nil {
return map[string]any{"fn": username}
}
if values, ok := public.(map[string]any); ok {
values["fn"] = username
return values
}
return map[string]any{"fn": username}
}
func validateRegistrationInvite(tenantID types.TenantID, code string, now time.Time) error {
if store.RegistrationInvites == nil {
return types.ErrInternal