hi-server/internal/logic/auth/userRegisterLogic.go

251 lines
8.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package auth
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/perfect-panel/server/internal/config"
"github.com/perfect-panel/server/internal/logic/common"
"github.com/perfect-panel/server/internal/model/log"
"github.com/perfect-panel/server/internal/model/user"
"github.com/perfect-panel/server/internal/svc"
"github.com/perfect-panel/server/internal/types"
"github.com/perfect-panel/server/pkg/constant"
"github.com/perfect-panel/server/pkg/jwt"
"github.com/perfect-panel/server/pkg/logger"
"github.com/perfect-panel/server/pkg/tool"
"github.com/perfect-panel/server/pkg/uuidx"
"github.com/perfect-panel/server/pkg/xerr"
"github.com/pkg/errors"
"gorm.io/gorm"
)
type UserRegisterLogic struct {
logger.Logger
ctx context.Context
svcCtx *svc.ServiceContext
}
// NewUserRegisterLogic User register
func NewUserRegisterLogic(ctx context.Context, svcCtx *svc.ServiceContext) *UserRegisterLogic {
return &UserRegisterLogic{
Logger: logger.WithContext(ctx),
ctx: ctx,
svcCtx: svcCtx,
}
}
func (l *UserRegisterLogic) UserRegister(req *types.UserRegisterRequest) (resp *types.LoginResponse, err error) {
c := l.svcCtx.Config.Register
email := l.svcCtx.Config.Email
var referer *user.User
// Check if the registration is stopped
if c.StopRegister {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.StopRegister), "stop register")
}
if req.Invite == "" {
if l.svcCtx.Config.Invite.ForcedInvite {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.InviteCodeError), "invite code is required")
}
} else {
// Check if the invite code is valid
referer, err = l.svcCtx.UserModel.FindOneByReferCode(l.ctx, req.Invite)
if err != nil {
l.Errorw("FindOneByReferCode Error", logger.Field("error", err))
return nil, errors.Wrapf(xerr.NewErrCode(xerr.InviteCodeError), "invite code is invalid")
}
}
// if the email verification is enabled, the verification code is required
if email.EnableVerify {
cacheKey := fmt.Sprintf("%s:%s:%s", config.AuthCodeCacheKey, constant.Register, req.Email)
value, err := l.svcCtx.Redis.Get(l.ctx, cacheKey).Result()
if err != nil {
l.Errorw("Redis Error", logger.Field("error", err.Error()), logger.Field("cacheKey", cacheKey))
return nil, errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "code error")
}
var payload common.CacheKeyPayload
err = json.Unmarshal([]byte(value), &payload)
if err != nil {
l.Errorw("Unmarshal Error", logger.Field("error", err.Error()), logger.Field("value", value))
return nil, errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "code error")
}
if payload.Code != req.Code {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "code error")
}
// 校验有效期15分钟
if time.Now().Unix()-payload.LastAt > l.svcCtx.Config.VerifyCode.ExpireTime {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "code expired")
}
l.svcCtx.Redis.Del(l.ctx, cacheKey)
}
// Check if the user exists
_, err = l.svcCtx.UserModel.FindOneByEmail(l.ctx, req.Email)
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
l.Errorw("FindOneByEmail Error", logger.Field("error", err))
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query user info failed: %v", err.Error())
} else if err == nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.UserExist), "user email exist: %v", req.Email)
}
// Generate password
pwd := tool.EncodePassWord(req.Password)
userInfo := &user.User{
Password: pwd,
Algo: "default",
OnlyFirstPurchase: &l.svcCtx.Config.Invite.OnlyFirstPurchase,
}
if referer != nil {
userInfo.RefererId = referer.Id
}
err = l.svcCtx.UserModel.Transaction(l.ctx, func(db *gorm.DB) error {
// Save user information
if err := db.Create(userInfo).Error; err != nil {
return err
}
// Generate ReferCode
userInfo.ReferCode = uuidx.UserInviteCode(userInfo.Id)
// Update ReferCode
if err := db.Model(&user.User{}).Where("id = ?", userInfo.Id).Update("refer_code", userInfo.ReferCode).Error; err != nil {
return err
}
// create user auth info
authInfo := &user.AuthMethods{
UserId: userInfo.Id,
AuthType: "email",
AuthIdentifier: req.Email,
Verified: email.EnableVerify,
}
if err = db.Create(authInfo).Error; err != nil {
return err
}
if l.svcCtx.Config.Register.EnableTrial {
// Active trial
if err = l.activeTrial(userInfo.Id); err != nil {
return err
}
}
return nil
})
// Bind device to user if identifier is provided
if req.Identifier != "" {
bindLogic := NewBindDeviceLogic(l.ctx, l.svcCtx)
if err := bindLogic.BindDeviceToUser(req.Identifier, req.IP, req.UserAgent, userInfo.Id); err != nil {
var ce *xerr.CodeError
if errors.As(err, &ce) && ce.GetErrCode() == xerr.DeviceBindLimitExceeded {
return nil, ce
}
l.Errorw("failed to bind device to user",
logger.Field("user_id", userInfo.Id),
logger.Field("identifier", req.Identifier),
logger.Field("error", err.Error()),
)
}
}
if l.ctx.Value(constant.LoginType) != nil {
req.LoginType = l.ctx.Value(constant.LoginType).(string)
}
// Generate session id
sessionId := uuidx.NewUUID().String()
// Generate token
token, err := jwt.NewJwtToken(
l.svcCtx.Config.JwtAuth.AccessSecret,
time.Now().Unix(),
l.svcCtx.Config.JwtAuth.AccessExpire,
jwt.WithOption("UserId", userInfo.Id),
jwt.WithOption("SessionId", sessionId),
jwt.WithOption("LoginType", req.LoginType),
)
if err != nil {
l.Logger.Error("[UserLogin] token generate error", logger.Field("error", err.Error()))
return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "token generate error: %v", err.Error())
}
// Set session id
sessionIdCacheKey := fmt.Sprintf("%v:%v", config.SessionIdKey, sessionId)
if err := l.svcCtx.Redis.Set(l.ctx, sessionIdCacheKey, userInfo.Id, time.Duration(l.svcCtx.Config.JwtAuth.AccessExpire)*time.Second).Err(); err != nil {
return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "set session id error: %v", err.Error())
}
loginStatus := true
defer func() {
if token != "" && userInfo.Id != 0 {
loginLog := log.Login{
Method: "email",
LoginIP: req.IP,
UserAgent: req.UserAgent,
Success: loginStatus,
Timestamp: time.Now().UnixMilli(),
}
content, _ := loginLog.Marshal()
if err := l.svcCtx.LogModel.Insert(l.ctx, &log.SystemLog{
Id: 0,
Type: log.TypeLogin.Uint8(),
Date: time.Now().Format("2006-01-02"),
ObjectID: userInfo.Id,
Content: string(content),
}); err != nil {
l.Errorw("failed to insert login log",
logger.Field("user_id", userInfo.Id),
logger.Field("ip", req.IP),
logger.Field("error", err.Error()),
)
}
// Register log
registerLog := log.Register{
AuthMethod: "email",
Identifier: req.Email,
RegisterIP: req.IP,
UserAgent: req.UserAgent,
Timestamp: time.Now().UnixMilli(),
}
content, _ = registerLog.Marshal()
if err = l.svcCtx.LogModel.Insert(l.ctx, &log.SystemLog{
Type: log.TypeRegister.Uint8(),
ObjectID: userInfo.Id,
Date: time.Now().Format("2006-01-02"),
Content: string(content),
}); err != nil {
l.Errorw("failed to insert login log",
logger.Field("user_id", userInfo.Id),
logger.Field("ip", req.IP),
logger.Field("error", err.Error()))
}
}
}()
return &types.LoginResponse{
Token: token,
}, nil
}
func (l *UserRegisterLogic) activeTrial(uid int64) error {
sub, err := l.svcCtx.SubscribeModel.FindOne(l.ctx, l.svcCtx.Config.Register.TrialSubscribe)
if err != nil {
return err
}
userSub := &user.Subscribe{
UserId: uid,
OrderId: 0,
SubscribeId: sub.Id,
StartTime: time.Now(),
ExpireTime: tool.AddTime(l.svcCtx.Config.Register.TrialTimeUnit, l.svcCtx.Config.Register.TrialTime, time.Now()),
Traffic: sub.Traffic,
Download: 0,
Upload: 0,
Token: uuidx.SubscribeToken(fmt.Sprintf("Trial-%v", uid)),
UUID: uuidx.NewUUID().String(),
Status: 1,
}
err = l.svcCtx.UserModel.InsertSubscribe(l.ctx, userSub)
if err != nil {
return err
}
if clearErr := l.svcCtx.NodeModel.ClearServerAllCache(l.ctx); clearErr != nil {
l.Errorf("ClearServerAllCache error: %v", clearErr.Error())
}
return err
}