This commit is contained in:
eson 2023-07-27 16:48:43 +08:00
parent abc315a39b
commit 0ce35645b2
10 changed files with 195 additions and 112 deletions

View File

@ -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)
}

View File

@ -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",

View File

@ -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
}

View File

@ -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)
}

View File

@ -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&register_token=%s",
l.token,
l.isRegistered,
l.registerToken,
)
html := fmt.Sprintf(`
<!DOCTYPE html>
<html>
<head>
<title>Redirect</title>
<script type="text/javascript">
window.onload = function() {
window.location = "%s";
}
</script>
</head>
<body>
</body>
</html>
`, 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&register_token=%s",
l.token,

View File

@ -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"`
}

View File

@ -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"`
}

View File

@ -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

View File

@ -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) {

View File

@ -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 {