// // Copyright (C) 2024 veypi // 2025-03-04 16:08:06 // Distributed under terms of the MIT license. // package auth import ( "regexp" "strings" baseauth "github.com/veypi/vbase/auth" "github.com/veypi/vbase/cfg" "github.com/veypi/vbase/libs/crypto" "github.com/veypi/vbase/libs/jwt" "github.com/veypi/vbase/models" "github.com/veypi/vigo" ) // Email 正则表达式 var emailRegex = regexp.MustCompile(`^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$`) // RegisterRequest 注册请求 type RegisterRequest struct { Username string `json:"username" src:"json" desc:"用户名"` Password string `json:"password" src:"json" desc:"密码"` Email string `json:"email,omitempty" src:"json" desc:"邮箱"` Phone string `json:"phone,omitempty" src:"json" desc:"手机号"` Nickname string `json:"nickname,omitempty" src:"json" desc:"昵称"` } // register 用户注册 func register(x *vigo.X, req *RegisterRequest) (*AuthResponse, error) { // 验证用户名 if strings.TrimSpace(req.Username) == "" { return nil, vigo.ErrInvalidArg.WithString("username is required") } if len(req.Username) < 3 || len(req.Username) > 50 { return nil, vigo.ErrInvalidArg.WithString("username must be between 3 and 50 characters") } // 用户名只能包含字母、数字和下划线 if !regexp.MustCompile(`^[a-zA-Z0-9_]+$`).MatchString(req.Username) { return nil, vigo.ErrInvalidArg.WithString("username can only contain letters, numbers and underscores") } // 验证密码 if strings.TrimSpace(req.Password) == "" { return nil, vigo.ErrInvalidArg.WithString("password is required") } if len(req.Password) < 8 { return nil, vigo.ErrInvalidArg.WithString("password must be at least 8 characters") } // 验证邮箱格式 if req.Email != "" && !emailRegex.MatchString(req.Email) { return nil, vigo.ErrInvalidArg.WithString("invalid email format") } // 检查注册配置 requireEmail, _ := models.GetSettingBool(models.SettingAuthRegRequireEmail) requirePhone, _ := models.GetSettingBool(models.SettingAuthRegRequirePhone) // 校验必填字段 if requireEmail && req.Email == "" { return nil, vigo.ErrInvalidArg.WithString("email is required") } if requirePhone && req.Phone == "" { return nil, vigo.ErrInvalidArg.WithString("phone is required") } // 检查是否是第一个用户(需要在创建用户之前检查) var userCount int64 if err := cfg.DB().Model(&models.User{}).Count(&userCount).Error; err != nil { return nil, vigo.ErrInternalServer.WithError(err) } // 检查用户名是否已存在 var count int64 if err := cfg.DB().Model(&models.User{}).Where("username = ?", req.Username).Count(&count).Error; err != nil { return nil, vigo.ErrInternalServer.WithError(err) } if count > 0 { return nil, vigo.ErrInvalidArg.WithArgs("username already exists") } // 检查邮箱是否已存在 if req.Email != "" { count = 0 // 重置计数器 if err := cfg.DB().Model(&models.User{}).Where("email = ?", req.Email).Count(&count).Error; err != nil { return nil, vigo.ErrInternalServer.WithError(err) } if count > 0 { return nil, vigo.ErrInvalidArg.WithArgs("email already exists") } } // 检查手机是否已存在 if req.Phone != "" { count = 0 // 重置计数器 if err := cfg.DB().Model(&models.User{}).Where("phone = ?", req.Phone).Count(&count).Error; err != nil { return nil, vigo.ErrInternalServer.WithError(err) } if count > 0 { return nil, vigo.ErrInvalidArg.WithArgs("phone already exists") } } // 哈希密码 hashedPassword, err := crypto.HashPassword(req.Password, 12) if err != nil { return nil, vigo.ErrInternalServer.WithError(err) } // 创建用户 var email *string if req.Email != "" { email = &req.Email } var phone *string if req.Phone != "" { phone = &req.Phone } user := &models.User{ Username: req.Username, Password: hashedPassword, Email: email, Phone: phone, Nickname: req.Nickname, Status: models.UserStatusActive, } if user.Nickname == "" { user.Nickname = user.Username } if err := cfg.DB().Create(user).Error; err != nil { return nil, vigo.ErrInternalServer.WithError(err) } // 第一个用户授予 admin 角色,其他用户授予 user 角色 roleCode := "user" if userCount == 0 { roleCode = "admin" } if err := baseauth.VBaseAuth.GrantRole(x.Context(), user.ID, "", roleCode); err != nil { // 记录错误但允许注册继续,或者回滚 // 这里简单处理,继续流程,用户可能需要管理员手动授权 // 或者返回错误 // return nil, vigo.ErrInternalServer.WithError(err) } // 生成token emailStr := "" if user.Email != nil { emailStr = *user.Email } tokenPair, err := jwt.GenerateTokenPair( user.ID, user.Username, user.Nickname, user.Avatar, emailStr, nil, // 新用户无组织 ) if err != nil { return nil, vigo.ErrInternalServer.WithError(err) } return &AuthResponse{ AccessToken: tokenPair.AccessToken, RefreshToken: tokenPair.RefreshToken, TokenType: tokenPair.TokenType, ExpiresIn: tokenPair.ExpiresIn, User: &UserInfo{ ID: user.ID, Username: user.Username, Nickname: user.Nickname, Email: user.Email, Avatar: user.Avatar, }, }, nil }