feat(auth): 登录注册 - 添加短信通知与组合验证支持

This commit is contained in:
ddCat committed 2026-09-22 23:46:15 +08:00
1 parent 6c013ebf7e
commit bc0d109428
55 files changed
+4013 -103

No files matched your search

+82 -16
View File
@@ -6,6 +6,7 @@ import (
"encoding/hex"
"errors"
"infinite-canvas/backend/internal/kernel"
"infinite-canvas/backend/internal/repository"
"log"
"net/mail"
"regexp"
@@ -31,6 +32,9 @@ type RegisterRequest struct {
Username string `json:"username"`
Email string `json:"email"`
EmailCode string `json:"emailCode"`
Phone string `json:"phone"`
SMSCode string `json:"smsCode"`
Ticket string `json:"ticket"`
DisplayName string `json:"displayName"`
Password string `json:"password"`
AcceptedTerms bool `json:"acceptedTerms"`
@@ -42,11 +46,14 @@ type LoginRequest struct {
}
type PublicAuthSettings struct {
FirstUser bool `json:"firstUser"`
RegistrationEnabled bool `json:"registrationEnabled"`
LinuxDOEnabled bool `json:"linuxdoEnabled"`
EmailEnabled bool `json:"emailEnabled"`
EmailCodeRequired bool `json:"emailCodeRequired"`
VerificationPolicy
SMSBindingAvailable bool `json:"smsBindingAvailable"`
EmailBindingAvailable bool `json:"emailBindingAvailable"`
FirstUser bool `json:"firstUser"`
RegistrationEnabled bool `json:"registrationEnabled"`
LinuxDOEnabled bool `json:"linuxdoEnabled"`
EmailEnabled bool `json:"emailEnabled"`
EmailCodeRequired bool `json:"emailCodeRequired"`
}
type AuthSessionResult struct {
@@ -79,7 +86,28 @@ func (s *Service) PublicAuthSettings() (*PublicAuthSettings, error) {
if err != nil {
return nil, err
}
return &PublicAuthSettings{FirstUser: false, RegistrationEnabled: registrationEnabled, LinuxDOEnabled: s.LinuxDOEnabled(), EmailEnabled: emailEnabled, EmailCodeRequired: true}, nil
p, err := s.verificationPolicy()
if err != nil {
return nil, err
}
smsLogin, err := s.smsAvailable("login")
if err != nil {
return nil, err
}
smsRegister, err := s.smsAvailable("register")
if err != nil {
return nil, err
}
smsBind, err := s.smsAvailable("bind")
if err != nil {
return nil, err
}
p.SMSLogin = p.SMSLogin && smsLogin
p.EmailLogin = p.EmailLogin && emailEnabled
p.SMSRegistration = p.SMSRegistration && smsRegister
p.EmailRegistration = p.EmailRegistration && emailEnabled
p.SMSAndEmailRegistration = p.SMSAndEmailRegistration && smsRegister && emailEnabled
return &PublicAuthSettings{VerificationPolicy: p, SMSBindingAvailable: smsBind, EmailBindingAvailable: emailEnabled, FirstUser: false, RegistrationEnabled: registrationEnabled, LinuxDOEnabled: s.LinuxDOEnabled(), EmailEnabled: emailEnabled, EmailCodeRequired: p.EmailRegistration || p.SMSAndEmailRegistration}, nil
}
func (s *Service) Register(req RegisterRequest) (*AuthSessionResult, error) {
@@ -107,6 +135,8 @@ func (s *Service) Register(req RegisterRequest) (*AuthSessionResult, error) {
return nil, err
}
var verifiedCode *model.EmailVerificationCode
var verification *model.AuthVerification
phone := ""
if count > 0 {
registrationEnabled, err := s.RegistrationEnabled()
if err != nil {
@@ -115,15 +145,36 @@ func (s *Service) Register(req RegisterRequest) (*AuthSessionResult, error) {
if !registrationEnabled {
return nil, kernel.Forbidden("管理员未开放新用户注册")
}
if email == "" {
return nil, kernel.BadAuthRequest("请输入邮箱")
}
if err := s.validateRegistrationEmailDomain(email); err != nil {
return nil, err
}
verifiedCode, err = s.VerifyRegistrationEmailCode(email, req.EmailCode)
if err != nil {
return nil, err
if req.Ticket != "" {
verification, err = s.verifyTicket("register", VerificationConfirm{Ticket: req.Ticket, EmailCode: req.EmailCode, SMSCode: req.SMSCode})
if err != nil {
return nil, err
}
if verification.Email != email || (verification.Phone != "" && strings.TrimSpace(req.Phone) != verification.Phone && "+86"+strings.TrimSpace(req.Phone) != verification.Phone) || (verification.Phone == "" && req.Phone != "") {
return nil, invalidVerification()
}
phone = verification.Phone
if err := s.contactAvailable(email, phone, ""); err != nil {
return nil, err
}
} else {
p, err := s.verificationPolicy()
if err != nil {
return nil, err
}
if !p.allows("register", "email") || req.Phone != "" {
return nil, kernel.BadAuthRequest("请先获取本次注册验证码")
}
if email == "" {
return nil, kernel.BadAuthRequest("请输入邮箱")
}
if err := s.validateRegistrationEmailDomain(email); err != nil {
return nil, err
}
verifiedCode, err = s.VerifyRegistrationEmailCode(email, req.EmailCode)
if err != nil {
return nil, err
}
}
}
if _, err := s.repo.UserByUsername(username); err == nil {
@@ -147,6 +198,7 @@ func (s *Service) Register(req RegisterRequest) (*AuthSessionResult, error) {
ID: kernel.NewID(),
Username: username,
Email: email,
Phone: phone,
DisplayName: displayName,
Role: model.UserRoleUser,
Status: model.UserStatusActive,
@@ -157,7 +209,21 @@ func (s *Service) Register(req RegisterRequest) (*AuthSessionResult, error) {
if count == 0 {
user.Role = model.UserRoleAdmin
}
if verifiedCode != nil {
if verification != nil {
if email != "" {
user.EmailVerifiedAt = &now
}
if phone != "" {
user.PhoneVerifiedAt = &now
}
if err := s.repo.CreateUserWithVerification(&user, verification.ID); err != nil {
if errors.Is(err, repository.ErrVerificationInvalid) {
return nil, invalidVerification()
}
return nil, err
}
} else if verifiedCode != nil {
user.EmailVerifiedAt = &now
if err := s.repo.CreateUserWithEmailVerification(&user, verifiedCode.ID, time.Now()); err != nil {
return nil, err
}