diff --git a/model/gmodel/fs_user_logic.go b/model/gmodel/fs_user_logic.go index e80917e5..11ae443f 100644 --- a/model/gmodel/fs_user_logic.go +++ b/model/gmodel/fs_user_logic.go @@ -39,7 +39,7 @@ func (u *FsUserModel) FindUserByGoogleId(ctx context.Context, Id int64) (resp *F return resp, err } -func (u *FsUserModel) TransactionRegsterGoogle(ctx context.Context, fc func(tx *gorm.DB) error) (err error) { +func (u *FsUserModel) Transaction(ctx context.Context, fc func(tx *gorm.DB) error) (err error) { return u.db.WithContext(ctx).Transaction(fc) } diff --git a/server/auth/internal/logic/email_manager.go b/server/auth/internal/logic/email_manager.go index 14f3a002..522fae2f 100644 --- a/server/auth/internal/logic/email_manager.go +++ b/server/auth/internal/logic/email_manager.go @@ -11,40 +11,49 @@ import ( var EmailManager *EmailSender +type EmailFormat struct { + TargetEmail string // 发送的目标email + CompanyName string // fs公司名 + ConfirmationLink string // fs确认连接 + SenderName string // 发送人 + SenderTitle string // 发送标题 +} + // EmailSender type EmailSender struct { lock sync.Mutex - EmailTasks chan string + EmailTasks chan *EmailFormat Auth smtp.Auth FromEmail string - emailSending map[string]*EmailTask ResendTimeLimit time.Duration - semaphore chan struct{} + + emailSending map[string]*EmailTask + semaphore chan struct{} } // EmailTask type EmailTask struct { - Email string // email - SendTime time.Time // 处理的任务时间 + Email *EmailFormat // email + SendTime time.Time // 处理的任务时间 } func (m *EmailSender) ProcessEmailTasks() { for { - emailTarget, ok := <-m.EmailTasks + emailformat, ok := <-m.EmailTasks if !ok { log.Println("Email task channel closed") break } m.lock.Lock() - _, isSending := m.emailSending[emailTarget] + _, isSending := m.emailSending[emailformat.TargetEmail] if isSending { m.lock.Unlock() continue } - m.emailSending[emailTarget] = &EmailTask{ - Email: emailTarget, + m.emailSending[emailformat.TargetEmail] = &EmailTask{ + Email: emailformat, SendTime: time.Now(), } m.lock.Unlock() @@ -55,11 +64,11 @@ func (m *EmailSender) ProcessEmailTasks() { go func() { defer func() { <-m.semaphore }() // Release a token - content := RenderEmailTemplate("fusen", "http://www.baidu.com", "fusen@gmail.com", "mail-valid") - err := smtp.SendMail("smtp.gmail.com:587", m.Auth, m.FromEmail, []string{emailTarget}, content) + content := RenderEmailTemplate(emailformat.CompanyName, emailformat.ConfirmationLink, emailformat.SenderName, emailformat.SenderTitle) + err := smtp.SendMail("smtp.gmail.com:587", m.Auth, m.FromEmail, []string{emailformat.TargetEmail}, content) if err != nil { - log.Printf("Failed to send email to %s: %v\n", emailTarget, err) - m.Resend(emailTarget, content) + log.Printf("Failed to send email to %s: %v\n", emailformat, err) + m.Resend(emailformat.TargetEmail, content) } }() } @@ -105,7 +114,7 @@ func init() { // Initialize the email manager EmailManager = &EmailSender{ - EmailTasks: make(chan string, 10), + EmailTasks: make(chan *EmailFormat, 10), Auth: smtp.PlainAuth( "", "user@example.com", diff --git a/server/auth/internal/logic/useremailconfirmationlogic.go b/server/auth/internal/logic/useremailconfirmationlogic.go index d7436c14..8017e55e 100644 --- a/server/auth/internal/logic/useremailconfirmationlogic.go +++ b/server/auth/internal/logic/useremailconfirmationlogic.go @@ -1,8 +1,10 @@ package logic import ( + "fusenapi/model/gmodel" "fusenapi/utils/auth" "fusenapi/utils/basic" + "time" "context" @@ -10,6 +12,7 @@ import ( "fusenapi/server/auth/internal/types" "github.com/zeromicro/go-zero/core/logx" + "gorm.io/gorm" ) type UserEmailConfirmationLogic struct { @@ -34,6 +37,43 @@ func (l *UserEmailConfirmationLogic) UserEmailConfirmation(req *types.RequestEma // 返回值必须调用Set重新返回, resp可以空指针调用 resp.SetStatus(basic.CodeOK, data) // userinfo 传入值时, 一定不为null + token, err := l.svcCtx.TokenManger.Decrypt(req.Token) + if err != nil { + logx.Error(err) + return resp.SetStatus(basic.CodeOAuthRegisterTokenErr) + } + + switch token.OperateType { + case auth.OpTypeRegister: + if time.Since(token.CreateAt) >= 24*time.Hour { + return resp.SetStatus(basic.CodeOAuthConfirmationTimeoutErr) + } + + switch token.Platform { + case "google": + + user, err := l.OpGoogleRegister(token) + if err != nil { + logx.Error(err) + return resp.SetStatus(basic.CodeDbSqlErr) + } + + jwtToken, err := auth.GenerateJwtTokenUint64( + auth.StringToHash(*user.PasswordHash), + l.svcCtx.Config.Auth.AccessExpire, + time.Now().Unix(), + user.Id, + 0, + ) + + case "facebook": + l.OpGoogleRegister(token) + } + + default: + return resp.SetStatus(basic.CodeOAuthRegisterTokenErr) + } + return resp.SetStatus(basic.CodeOK) } @@ -41,3 +81,41 @@ func (l *UserEmailConfirmationLogic) UserEmailConfirmation(req *types.RequestEma // func (l *UserEmailConfirmationLogic) AfterLogic(w http.ResponseWriter, r *http.Request, resp *basic.Response) { // // httpx.OkJsonCtx(r.Context(), w, resp) // } + +// OpGoogleRegister 谷歌平台的注册流程 +func (l *UserEmailConfirmationLogic) OpGoogleRegister(token *auth.RegisterToken) (*gmodel.FsUser, error) { + user := &gmodel.FsUser{} + + err := l.svcCtx.AllModels.FsUser.Transaction(context.TODO(), func(tx *gorm.DB) error { + + err := tx.Model(user).Where("email = ?", token.Email).Take(user).Error + if err != nil { + // 没有找到在数据库就创建注册 + if err == gorm.ErrRecordNotFound { + createAt := time.Now().Unix() + user.Email = &token.Email + user.CreatedAt = &createAt + user.GoogleId = &token.Id + user.PasswordHash = &token.Password + err = tx.Model(user).Create(user).Error + if err != nil { + return err + } + + // 继承guest_id的资源表 + // tx.Table("fs_resources").Model() + + return nil + } + return err + } + + user.GoogleId = &token.Id + return tx.Model(user).Update("google_id", user).Error + }) + + if err != nil { + return nil, err + } + return user, nil +} diff --git a/server/auth/internal/logic/useremailregisterlogic.go b/server/auth/internal/logic/useremailregisterlogic.go index c8e4751f..b800ff0b 100644 --- a/server/auth/internal/logic/useremailregisterlogic.go +++ b/server/auth/internal/logic/useremailregisterlogic.go @@ -39,5 +39,42 @@ func (l *UserEmailRegisterLogic) UserEmailRegister(req *types.RequestEmailRegist // 返回值必须调用Set重新返回, resp可以空指针调用 resp.SetStatus(basic.CodeOK, data) // userinfo 传入值时, 一定不为null + // 进入邮件注册流程 + // 这里是注册模块, 发邮件, 通过邮件注册确认邮箱存在 + + // 邮箱验证格式错误 + if !auth.ValidateEmail(req.Email) { + return resp.SetStatus(basic.CodeOAuthEmailErr) + } + + token, err := l.svcCtx.TokenManger.Decrypt(req.RegisterToken) + if err != nil { + logx.Error(err) + return resp.SetStatus(basic.CodeOAuthRegisterTokenErr) + } + + if token.OperateType != auth.OpTypeRegister { + return resp.SetStatus(basic.CodeOAuthRegisterTokenErr) + } + + // 确认email 重新序列化 + token.Email = req.Email + token.WCId = req.WCId + + clurl, err := l.svcCtx.TokenManger.Generate(token) + if err != nil { + logx.Error(err) + return resp.SetStatus(basic.CodeOAuthRegisterTokenErr) + } + + // 进入发送邮箱的系统 + EmailManager.EmailTasks <- &EmailFormat{ + TargetEmail: req.Email, + CompanyName: "fusen", + ConfirmationLink: clurl, + SenderName: "support@fusenpack.com", + SenderTitle: "register-valid", + } // email进入队 + return resp.SetStatus(basic.CodeOK) } diff --git a/server/auth/internal/logic/usergoogleloginlogic.go b/server/auth/internal/logic/usergoogleloginlogic.go index bc89e39d..7fe4f336 100644 --- a/server/auth/internal/logic/usergoogleloginlogic.go +++ b/server/auth/internal/logic/usergoogleloginlogic.go @@ -1,9 +1,12 @@ package logic import ( + "crypto/rand" + "encoding/base64" "fmt" "fusenapi/utils/auth" "fusenapi/utils/basic" + "io" "log" "net/http" "time" @@ -46,52 +49,6 @@ func NewUserGoogleLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *U // func (l *UserGoogleLoginLogic) BeforeLogic(w http.ResponseWriter, r *http.Request) { // } -<<<<<<< HEAD -// 处理逻辑后 w,r 如:重定向, resp 必须重新处理 -func (l *UserGoogleLoginLogic) AfterLogic(w http.ResponseWriter, r *http.Request, resp *basic.Response) { - - if resp.Code == 200 { - - if !l.isRegistered { - - rtoken, err := l.svcCtx.TokenManger.Encrypt(l.registerInfo) - if err != nil { - resp.SetStatus(basic.CodeOAuthRegisterTokenErr) - } - l.registerToken = rtoken - } - - rurl := fmt.Sprintf( - l.svcCtx.Config.MainAddress+"/oauth?token=%s&is_registered=%t®ister_token=%s", - l.token, - l.isRegistered, - l.registerToken, - ) - - html := fmt.Sprintf(` - - - - Redirect - - - - - - `, rurl) - fmt.Fprintln(w, html) - } else { - httpx.OkJson(w, resp) - } - -} - -======= ->>>>>>> 529a02ac7582babc634e6f9cddab126f5f962efb func (l *UserGoogleLoginLogic) UserGoogleLogin(req *types.RequestGoogleLogin, userinfo *auth.UserInfo) (resp *basic.Response) { // 返回值必须调用Set重新返回, resp可以空指针调用 resp.SetStatus(basic.CodeOK, data) // userinfo 传入值时, 一定不为null @@ -141,30 +98,34 @@ func (l *UserGoogleLoginLogic) UserGoogleLogin(req *types.RequestGoogleLogin, us return resp.SetStatus(basic.CodeDbSqlErr) } + nonce := make([]byte, 16) + if _, err := io.ReadFull(rand.Reader, nonce); err != nil { + logx.Error(err) + return resp.SetStatus(basic.CodeOK) + } + + l.registerInfo = &auth.RegisterToken{ + Id: googleId, + Password: base64.URLEncoding.EncodeToString(nonce), + Platform: "google", + OperateType: auth.OpTypeRegister, + CreateAt: time.Now(), + } + l.isRegistered = false - l.registerToken = // - - // 进入邮件注册流程 - // if req.Email == "" { - // return resp.SetStatus(basic.CodeOK) - // } - - // // 这里是注册模块, 发邮件, 通过邮件注册确认邮箱存在 - - // // 邮箱验证格式错误 - // if !auth.ValidateEmail(req.Email) { - // return resp.SetStatus(basic.CodeOAuthEmailErr) - // } - - // EmailManager.EmailTasks <- req.Email // email进入队 + token, err := l.svcCtx.TokenManger.Encrypt(l.registerInfo) + if err != nil { + logx.Error(err) + return resp.SetStatus(basic.CodeOAuthRegisterTokenErr) + } + l.registerToken = token return resp.SetStatus(basic.CodeOK) } + l.isRegistered = true // 如果密码匹配,则生成 JWT Token。 - nowSec := time.Now().Unix() - - jwtToken, err := auth.GenerateJwtTokenUint64(auth.StringToHash(*user.PasswordHash), l.svcCtx.Config.Auth.AccessExpire, nowSec, user.Id, 0) + jwtToken, err := auth.GenerateJwtTokenUint64(auth.StringToHash(*user.PasswordHash), l.svcCtx.Config.Auth.AccessExpire, time.Now().Unix(), user.Id, 0) // 如果生成 JWT Token 失败,则抛出错误并返回未认证的状态码。 if err != nil { @@ -182,23 +143,6 @@ func (l *UserGoogleLoginLogic) AfterLogic(w http.ResponseWriter, r *http.Request if resp.Code == 200 { - if !l.isRegistered { - now := time.Now() - rtoken, err := auth.GenerateRegisterToken( - &l.svcCtx.Config.Auth.AccessSecret, - l.svcCtx.Config.Auth.AccessExpire, - now.Unix(), - l.oauthinfo.Id, - l.oauthinfo.Platform, - ) - - if err != nil { - resp.SetStatus(basic.CodeOAuthRegisterTokenErr) - } - - l.registerToken = rtoken - } - rurl := fmt.Sprintf( l.svcCtx.Config.MainAddress+"/oauth?token=%s&is_registered=%t®ister_token=%s", l.token, diff --git a/server/auth/internal/types/types.go b/server/auth/internal/types/types.go index 56bf3d43..6295f978 100644 --- a/server/auth/internal/types/types.go +++ b/server/auth/internal/types/types.go @@ -18,12 +18,13 @@ type RequestGoogleLogin struct { } type RequestEmailConfirmation struct { - Email string `json:"email"` // 要确认的email - Token string `json:"token"` // 操作Token + Token string `query:"token"` // 操作Token } type RequestEmailRegister struct { Email string `json:"email"` + WCId uint64 `json:"wcid"` + GuestId uint64 `json:"guest_id"` RegisterToken string `json:"register_token"` } diff --git a/server_api/auth.api b/server_api/auth.api index 5003e8d9..663eef26 100644 --- a/server_api/auth.api +++ b/server_api/auth.api @@ -12,16 +12,16 @@ import "basic.api" service auth { @handler UserLoginHandler post /api/auth/login(RequestUserLogin) returns (response); - + @handler AcceptCookieHandler post /api/auth/accept-cookie(request) returns (response); - + @handler UserGoogleLoginHandler get /api/auth/oauth2/login/google(RequestGoogleLogin) returns (response); - + @handler UserEmailConfirmationHandler get /api/auth/email/confirmation(RequestEmailConfirmation) returns (response); - + @handler UserEmailRegisterHandler get /api/auth/oauth2/register(RequestEmailRegister) returns (response); } @@ -40,12 +40,13 @@ type RequestGoogleLogin { } type RequestEmailConfirmation { - Email string `json:"email"` // 要确认的email - Token string `json:"token"` // 操作Token + Token string `query:"token"` // 操作Token } type RequestEmailRegister { Email string `json:"email"` + WCId uint64 `json:"wcid"` + GuestId uint64 `json:"guest_id"` RegisterToken string `json:"register_token"` } diff --git a/utils/auth/confirmation_link.go b/utils/auth/confirmation_link.go index ef0caf49..29886165 100644 --- a/utils/auth/confirmation_link.go +++ b/utils/auth/confirmation_link.go @@ -10,6 +10,12 @@ import ( "net/url" ) +type OperateType int8 + +const ( + OpTypeRegister OperateType = 1 //注册的操作类型 +) + type ConfirmationLink[T any] struct { Secret []byte DefaultQueryKey string // 默认key 是 token diff --git a/utils/auth/register.go b/utils/auth/register.go index b817a158..3c3bafaa 100644 --- a/utils/auth/register.go +++ b/utils/auth/register.go @@ -13,10 +13,14 @@ import ( ) type RegisterToken struct { - Id int64 - Password string - Platform string - Expired time.Time + OperateType // 操作的类型, 验证的token 必须要继承这个 + Id int64 // 注册的 id + GuestId uint64 // guest_id 需要继承 + WCId uint64 // websocket 通道id + Email string // email + Password string // 密码 + Platform string // 平台 + CreateAt time.Time // 创建时间 } func ParseJwtTokenUint64SecretByRequest(r *http.Request, AccessSecret uint64) (jwt.MapClaims, error) { diff --git a/utils/basic/basic.go b/utils/basic/basic.go index 16705eb9..d8a7aea8 100644 --- a/utils/basic/basic.go +++ b/utils/basic/basic.go @@ -39,9 +39,11 @@ var ( CodeServiceErr = &StatusResponse{510, "server logic error"} // 服务逻辑错误 CodeUnAuth = &StatusResponse{401, "unauthorized"} // 未授权 - CodeOAuthGoogleApiErr = &StatusResponse{5070, "oauth2 google api error"} - CodeOAuthRegisterTokenErr = &StatusResponse{5071, "oauth2 jwt token error"} - CodeOAuthEmailErr = &StatusResponse{5071, "Invalid email format"} + CodeOAuthGoogleApiErr = &StatusResponse{5070, "oauth2 google api error"} + CodeOAuthRegisterTokenErr = &StatusResponse{5071, "oauth2 register create token error"} + CodeOAuthEmailErr = &StatusResponse{5072, "Invalid email format"} + CodeOAuthRandReaderErr = &StatusResponse{5073, "rand reader error"} + CodeOAuthConfirmationTimeoutErr = &StatusResponse{5074, "confirmation timeout error"} CodeS3PutObjectRequestErr = &StatusResponse{5060, "s3 PutObjectRequest error"} // s3 PutObjectRequest 错误 CodeS3PutSizeLimitErr = &StatusResponse{5061, "s3 over limit size error"} // s3 超过文件大小限制 错误 @@ -75,7 +77,8 @@ var ( CodeAesCbcEncryptionErr = &StatusResponse{5106, "encryption data err"} // 加密数据失败 CodeAesCbcDecryptionErr = &StatusResponse{5107, "decryption data err"} // 解密数据失败 - CodeSharedStateErr = &StatusResponse{5201, "shared state server err"} // 状态机错误 + CodeSharedStateErr = &StatusResponse{5201, "shared state server err"} // 状态机错误 + CodeEmailConfirmationErr = &StatusResponse{5202, "email confirmation err"} // email 验证错误 ) type Response struct {