add: device login
This commit is contained in:
parent
a273f8d883
commit
71018eb2f4
@ -11,6 +11,7 @@ info (
|
|||||||
type (
|
type (
|
||||||
// User login request
|
// User login request
|
||||||
UserLoginRequest {
|
UserLoginRequest {
|
||||||
|
Identifier string `json:"identifier"`
|
||||||
Email string `json:"email" validate:"required"`
|
Email string `json:"email" validate:"required"`
|
||||||
Password string `json:"password" validate:"required"`
|
Password string `json:"password" validate:"required"`
|
||||||
IP string `header:"X-Original-Forwarded-For"`
|
IP string `header:"X-Original-Forwarded-For"`
|
||||||
@ -27,6 +28,7 @@ type (
|
|||||||
}
|
}
|
||||||
// User login response
|
// User login response
|
||||||
UserRegisterRequest {
|
UserRegisterRequest {
|
||||||
|
Identifier string `json:"identifier"`
|
||||||
Email string `json:"email" validate:"required"`
|
Email string `json:"email" validate:"required"`
|
||||||
Password string `json:"password" validate:"required"`
|
Password string `json:"password" validate:"required"`
|
||||||
Invite string `json:"invite,optional"`
|
Invite string `json:"invite,optional"`
|
||||||
@ -60,6 +62,7 @@ type (
|
|||||||
}
|
}
|
||||||
// login request
|
// login request
|
||||||
TelephoneLoginRequest {
|
TelephoneLoginRequest {
|
||||||
|
Identifier string `json:"identifier"`
|
||||||
Telephone string `json:"telephone" validate:"required"`
|
Telephone string `json:"telephone" validate:"required"`
|
||||||
TelephoneCode string `json:"telephone_code"`
|
TelephoneCode string `json:"telephone_code"`
|
||||||
TelephoneAreaCode string `json:"telephone_area_code" validate:"required"`
|
TelephoneAreaCode string `json:"telephone_area_code" validate:"required"`
|
||||||
@ -79,6 +82,7 @@ type (
|
|||||||
}
|
}
|
||||||
// User login response
|
// User login response
|
||||||
TelephoneRegisterRequest {
|
TelephoneRegisterRequest {
|
||||||
|
Identifier string `json:"identifier"`
|
||||||
Telephone string `json:"telephone" validate:"required"`
|
Telephone string `json:"telephone" validate:"required"`
|
||||||
TelephoneAreaCode string `json:"telephone_area_code" validate:"required"`
|
TelephoneAreaCode string `json:"telephone_area_code" validate:"required"`
|
||||||
Password string `json:"password" validate:"required"`
|
Password string `json:"password" validate:"required"`
|
||||||
@ -107,6 +111,12 @@ type (
|
|||||||
Code string `form:"code"`
|
Code string `form:"code"`
|
||||||
State string `form:"state"`
|
State string `form:"state"`
|
||||||
}
|
}
|
||||||
|
DeviceLoginRequest {
|
||||||
|
Identifier string `json:"identifier" validate:"required"`
|
||||||
|
IP string `header:"X-Original-Forwarded-For"`
|
||||||
|
UserAgent string `json:"user_agent" validate:"required"`
|
||||||
|
CfToken string `json:"cf_token,optional"`
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@server (
|
@server (
|
||||||
@ -145,6 +155,10 @@ service ppanel {
|
|||||||
@doc "Reset password"
|
@doc "Reset password"
|
||||||
@handler TelephoneResetPassword
|
@handler TelephoneResetPassword
|
||||||
post /reset/telephone (TelephoneResetPasswordRequest) returns (LoginResponse)
|
post /reset/telephone (TelephoneResetPasswordRequest) returns (LoginResponse)
|
||||||
|
|
||||||
|
@doc "Device Login"
|
||||||
|
@handler DeviceLogin
|
||||||
|
post /login/device (DeviceLoginRequest) returns (LoginResponse)
|
||||||
}
|
}
|
||||||
|
|
||||||
@server (
|
@server (
|
||||||
|
|||||||
26
internal/handler/auth/deviceLoginHandler.go
Normal file
26
internal/handler/auth/deviceLoginHandler.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/perfect-panel/server/internal/logic/auth"
|
||||||
|
"github.com/perfect-panel/server/internal/svc"
|
||||||
|
"github.com/perfect-panel/server/internal/types"
|
||||||
|
"github.com/perfect-panel/server/pkg/result"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Device Login
|
||||||
|
func DeviceLoginHandler(svcCtx *svc.ServiceContext) func(c *gin.Context) {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
var req types.DeviceLoginRequest
|
||||||
|
_ = c.ShouldBind(&req)
|
||||||
|
validateErr := svcCtx.Validate(&req)
|
||||||
|
if validateErr != nil {
|
||||||
|
result.ParamErrorResult(c, validateErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
l := auth.NewDeviceLoginLogic(c.Request.Context(), svcCtx)
|
||||||
|
resp, err := l.DeviceLogin(&req)
|
||||||
|
result.HttpResult(c, resp, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -589,6 +589,9 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) {
|
|||||||
// User login
|
// User login
|
||||||
authGroupRouter.POST("/login", auth.UserLoginHandler(serverCtx))
|
authGroupRouter.POST("/login", auth.UserLoginHandler(serverCtx))
|
||||||
|
|
||||||
|
// Device Login
|
||||||
|
authGroupRouter.POST("/login/device", auth.DeviceLoginHandler(serverCtx))
|
||||||
|
|
||||||
// User Telephone login
|
// User Telephone login
|
||||||
authGroupRouter.POST("/login/telephone", auth.TelephoneLoginHandler(serverCtx))
|
authGroupRouter.POST("/login/telephone", auth.TelephoneLoginHandler(serverCtx))
|
||||||
|
|
||||||
|
|||||||
234
internal/logic/auth/bindDeviceLogic.go
Normal file
234
internal/logic/auth/bindDeviceLogic.go
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/perfect-panel/server/internal/model/user"
|
||||||
|
"github.com/perfect-panel/server/internal/svc"
|
||||||
|
"github.com/perfect-panel/server/pkg/logger"
|
||||||
|
"github.com/perfect-panel/server/pkg/xerr"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BindDeviceLogic struct {
|
||||||
|
logger.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBindDeviceLogic(ctx context.Context, svcCtx *svc.ServiceContext) *BindDeviceLogic {
|
||||||
|
return &BindDeviceLogic{
|
||||||
|
Logger: logger.WithContext(ctx),
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BindDeviceToUser binds a device to a user
|
||||||
|
// If the device is already bound to another user, it will disable that user and bind the device to the current user
|
||||||
|
func (l *BindDeviceLogic) BindDeviceToUser(identifier, ip, userAgent string, currentUserId int64) error {
|
||||||
|
if identifier == "" {
|
||||||
|
// No device identifier provided, skip binding
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
l.Infow("binding device to user",
|
||||||
|
logger.Field("identifier", identifier),
|
||||||
|
logger.Field("user_id", currentUserId),
|
||||||
|
logger.Field("ip", ip),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Check if device exists
|
||||||
|
deviceInfo, err := l.svcCtx.UserModel.FindOneDeviceByIdentifier(l.ctx, identifier)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
// Device not found, create new device record
|
||||||
|
return l.createDeviceForUser(identifier, ip, userAgent, currentUserId)
|
||||||
|
}
|
||||||
|
l.Errorw("failed to query device",
|
||||||
|
logger.Field("identifier", identifier),
|
||||||
|
logger.Field("error", err.Error()),
|
||||||
|
)
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query device failed: %v", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Device exists, check if it's bound to current user
|
||||||
|
if deviceInfo.UserId == currentUserId {
|
||||||
|
// Already bound to current user, just update IP and UserAgent
|
||||||
|
l.Infow("device already bound to current user, updating info",
|
||||||
|
logger.Field("identifier", identifier),
|
||||||
|
logger.Field("user_id", currentUserId),
|
||||||
|
)
|
||||||
|
deviceInfo.Ip = ip
|
||||||
|
deviceInfo.UserAgent = userAgent
|
||||||
|
if err := l.svcCtx.UserModel.UpdateDevice(l.ctx, deviceInfo); err != nil {
|
||||||
|
l.Errorw("failed to update device",
|
||||||
|
logger.Field("identifier", identifier),
|
||||||
|
logger.Field("error", err.Error()),
|
||||||
|
)
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update device failed: %v", err.Error())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Device is bound to another user, need to disable old user and rebind
|
||||||
|
l.Infow("device bound to another user, rebinding",
|
||||||
|
logger.Field("identifier", identifier),
|
||||||
|
logger.Field("old_user_id", deviceInfo.UserId),
|
||||||
|
logger.Field("new_user_id", currentUserId),
|
||||||
|
)
|
||||||
|
|
||||||
|
return l.rebindDeviceToNewUser(deviceInfo, ip, userAgent, currentUserId)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *BindDeviceLogic) createDeviceForUser(identifier, ip, userAgent string, userId int64) error {
|
||||||
|
l.Infow("creating new device for user",
|
||||||
|
logger.Field("identifier", identifier),
|
||||||
|
logger.Field("user_id", userId),
|
||||||
|
)
|
||||||
|
|
||||||
|
err := l.svcCtx.UserModel.Transaction(l.ctx, func(db *gorm.DB) error {
|
||||||
|
// Create device auth method
|
||||||
|
authMethod := &user.AuthMethods{
|
||||||
|
UserId: userId,
|
||||||
|
AuthType: "device",
|
||||||
|
AuthIdentifier: identifier,
|
||||||
|
Verified: true,
|
||||||
|
}
|
||||||
|
if err := db.Create(authMethod).Error; err != nil {
|
||||||
|
l.Errorw("failed to create device auth method",
|
||||||
|
logger.Field("user_id", userId),
|
||||||
|
logger.Field("identifier", identifier),
|
||||||
|
logger.Field("error", err.Error()),
|
||||||
|
)
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "create device auth method failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create device record
|
||||||
|
deviceInfo := &user.Device{
|
||||||
|
Ip: ip,
|
||||||
|
UserId: userId,
|
||||||
|
UserAgent: userAgent,
|
||||||
|
Identifier: identifier,
|
||||||
|
Enabled: true,
|
||||||
|
Online: false,
|
||||||
|
}
|
||||||
|
if err := db.Create(deviceInfo).Error; err != nil {
|
||||||
|
l.Errorw("failed to create device",
|
||||||
|
logger.Field("user_id", userId),
|
||||||
|
logger.Field("identifier", identifier),
|
||||||
|
logger.Field("error", err.Error()),
|
||||||
|
)
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "create device failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
l.Errorw("device creation failed",
|
||||||
|
logger.Field("identifier", identifier),
|
||||||
|
logger.Field("user_id", userId),
|
||||||
|
logger.Field("error", err.Error()),
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
l.Infow("device created successfully",
|
||||||
|
logger.Field("identifier", identifier),
|
||||||
|
logger.Field("user_id", userId),
|
||||||
|
)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *BindDeviceLogic) rebindDeviceToNewUser(deviceInfo *user.Device, ip, userAgent string, newUserId int64) error {
|
||||||
|
oldUserId := deviceInfo.UserId
|
||||||
|
|
||||||
|
err := l.svcCtx.UserModel.Transaction(l.ctx, func(db *gorm.DB) error {
|
||||||
|
// Check if old user has other auth methods besides device
|
||||||
|
var authMethods []user.AuthMethods
|
||||||
|
if err := db.Where("user_id = ?", oldUserId).Find(&authMethods).Error; err != nil {
|
||||||
|
l.Errorw("failed to query auth methods for old user",
|
||||||
|
logger.Field("old_user_id", oldUserId),
|
||||||
|
logger.Field("error", err.Error()),
|
||||||
|
)
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query auth methods failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count non-device auth methods
|
||||||
|
nonDeviceAuthCount := 0
|
||||||
|
for _, auth := range authMethods {
|
||||||
|
if auth.AuthType != "device" {
|
||||||
|
nonDeviceAuthCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only disable old user if they have no other auth methods
|
||||||
|
if nonDeviceAuthCount == 0 {
|
||||||
|
falseVal := false
|
||||||
|
if err := db.Model(&user.User{}).Where("id = ?", oldUserId).Update("enable", &falseVal).Error; err != nil {
|
||||||
|
l.Errorw("failed to disable old user",
|
||||||
|
logger.Field("old_user_id", oldUserId),
|
||||||
|
logger.Field("error", err.Error()),
|
||||||
|
)
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "disable old user failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
l.Infow("disabled old user (no other auth methods)",
|
||||||
|
logger.Field("old_user_id", oldUserId),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
l.Infow("old user has other auth methods, not disabling",
|
||||||
|
logger.Field("old_user_id", oldUserId),
|
||||||
|
logger.Field("non_device_auth_count", nonDeviceAuthCount),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update device auth method to new user
|
||||||
|
if err := db.Model(&user.AuthMethods{}).
|
||||||
|
Where("auth_type = ? AND auth_identifier = ?", "device", deviceInfo.Identifier).
|
||||||
|
Update("user_id", newUserId).Error; err != nil {
|
||||||
|
l.Errorw("failed to update device auth method",
|
||||||
|
logger.Field("identifier", deviceInfo.Identifier),
|
||||||
|
logger.Field("error", err.Error()),
|
||||||
|
)
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update device auth method failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update device record
|
||||||
|
deviceInfo.UserId = newUserId
|
||||||
|
deviceInfo.Ip = ip
|
||||||
|
deviceInfo.UserAgent = userAgent
|
||||||
|
deviceInfo.Enabled = true
|
||||||
|
|
||||||
|
if err := db.Save(deviceInfo).Error; err != nil {
|
||||||
|
l.Errorw("failed to update device",
|
||||||
|
logger.Field("identifier", deviceInfo.Identifier),
|
||||||
|
logger.Field("error", err.Error()),
|
||||||
|
)
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update device failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
l.Errorw("device rebinding failed",
|
||||||
|
logger.Field("identifier", deviceInfo.Identifier),
|
||||||
|
logger.Field("old_user_id", oldUserId),
|
||||||
|
logger.Field("new_user_id", newUserId),
|
||||||
|
logger.Field("error", err.Error()),
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
l.Infow("device rebound successfully",
|
||||||
|
logger.Field("identifier", deviceInfo.Identifier),
|
||||||
|
logger.Field("old_user_id", oldUserId),
|
||||||
|
logger.Field("new_user_id", newUserId),
|
||||||
|
)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
294
internal/logic/auth/deviceLoginLogic.go
Normal file
294
internal/logic/auth/deviceLoginLogic.go
Normal file
@ -0,0 +1,294 @@
|
|||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/perfect-panel/server/internal/config"
|
||||||
|
"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/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 DeviceLoginLogic struct {
|
||||||
|
logger.Logger
|
||||||
|
ctx context.Context
|
||||||
|
svcCtx *svc.ServiceContext
|
||||||
|
}
|
||||||
|
|
||||||
|
// Device Login
|
||||||
|
func NewDeviceLoginLogic(ctx context.Context, svcCtx *svc.ServiceContext) *DeviceLoginLogic {
|
||||||
|
return &DeviceLoginLogic{
|
||||||
|
Logger: logger.WithContext(ctx),
|
||||||
|
ctx: ctx,
|
||||||
|
svcCtx: svcCtx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *DeviceLoginLogic) DeviceLogin(req *types.DeviceLoginRequest) (resp *types.LoginResponse, err error) {
|
||||||
|
//TODO : check device login rate limit
|
||||||
|
// Check if device login is enabled
|
||||||
|
//if !l.svcCtx.Config.Register.EnableDevice {
|
||||||
|
// return nil, xerr.NewErrMsg("Device login is disabled")
|
||||||
|
//}
|
||||||
|
|
||||||
|
loginStatus := false
|
||||||
|
var userInfo *user.User
|
||||||
|
// Record login status
|
||||||
|
defer func() {
|
||||||
|
if userInfo != nil && userInfo.Id != 0 {
|
||||||
|
loginLog := log.Login{
|
||||||
|
Method: "device",
|
||||||
|
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{
|
||||||
|
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()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Check if device exists by identifier
|
||||||
|
deviceInfo, err := l.svcCtx.UserModel.FindOneDeviceByIdentifier(l.ctx, req.Identifier)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
// Device not found, create new user and device
|
||||||
|
userInfo, err = l.registerUserAndDevice(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
l.Errorw("query device failed",
|
||||||
|
logger.Field("identifier", req.Identifier),
|
||||||
|
logger.Field("error", err.Error()),
|
||||||
|
)
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query device failed: %v", err.Error())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Device found, get user info
|
||||||
|
userInfo, err = l.svcCtx.UserModel.FindOne(l.ctx, deviceInfo.UserId)
|
||||||
|
if err != nil {
|
||||||
|
l.Errorw("query user failed",
|
||||||
|
logger.Field("user_id", deviceInfo.UserId),
|
||||||
|
logger.Field("error", err.Error()),
|
||||||
|
)
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query user failed: %v", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
l.Errorw("token generate error",
|
||||||
|
logger.Field("user_id", userInfo.Id),
|
||||||
|
logger.Field("error", err.Error()),
|
||||||
|
)
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "token generate error: %v", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store session id in redis
|
||||||
|
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 {
|
||||||
|
l.Errorw("set session id error",
|
||||||
|
logger.Field("user_id", userInfo.Id),
|
||||||
|
logger.Field("error", err.Error()),
|
||||||
|
)
|
||||||
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "set session id error: %v", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
loginStatus = true
|
||||||
|
return &types.LoginResponse{
|
||||||
|
Token: token,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *DeviceLoginLogic) registerUserAndDevice(req *types.DeviceLoginRequest) (*user.User, error) {
|
||||||
|
l.Infow("device not found, creating new user and device",
|
||||||
|
logger.Field("identifier", req.Identifier),
|
||||||
|
logger.Field("ip", req.IP),
|
||||||
|
)
|
||||||
|
|
||||||
|
var userInfo *user.User
|
||||||
|
err := l.svcCtx.UserModel.Transaction(l.ctx, func(db *gorm.DB) error {
|
||||||
|
// Create new user
|
||||||
|
userInfo = &user.User{
|
||||||
|
OnlyFirstPurchase: &l.svcCtx.Config.Invite.OnlyFirstPurchase,
|
||||||
|
}
|
||||||
|
if err := db.Create(userInfo).Error; err != nil {
|
||||||
|
l.Errorw("failed to create user",
|
||||||
|
logger.Field("error", err.Error()),
|
||||||
|
)
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "create user failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update refer code
|
||||||
|
userInfo.ReferCode = uuidx.UserInviteCode(userInfo.Id)
|
||||||
|
if err := db.Model(&user.User{}).Where("id = ?", userInfo.Id).Update("refer_code", userInfo.ReferCode).Error; err != nil {
|
||||||
|
l.Errorw("failed to update refer code",
|
||||||
|
logger.Field("user_id", userInfo.Id),
|
||||||
|
logger.Field("error", err.Error()),
|
||||||
|
)
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update refer code failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create device auth method
|
||||||
|
authMethod := &user.AuthMethods{
|
||||||
|
UserId: userInfo.Id,
|
||||||
|
AuthType: "device",
|
||||||
|
AuthIdentifier: req.Identifier,
|
||||||
|
Verified: true,
|
||||||
|
}
|
||||||
|
if err := db.Create(authMethod).Error; err != nil {
|
||||||
|
l.Errorw("failed to create device auth method",
|
||||||
|
logger.Field("user_id", userInfo.Id),
|
||||||
|
logger.Field("identifier", req.Identifier),
|
||||||
|
logger.Field("error", err.Error()),
|
||||||
|
)
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "create device auth method failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Insert device record
|
||||||
|
deviceInfo := &user.Device{
|
||||||
|
Ip: req.IP,
|
||||||
|
UserId: userInfo.Id,
|
||||||
|
UserAgent: req.UserAgent,
|
||||||
|
Identifier: req.Identifier,
|
||||||
|
Enabled: true,
|
||||||
|
Online: false,
|
||||||
|
}
|
||||||
|
if err := db.Create(deviceInfo).Error; err != nil {
|
||||||
|
l.Errorw("failed to insert device",
|
||||||
|
logger.Field("user_id", userInfo.Id),
|
||||||
|
logger.Field("identifier", req.Identifier),
|
||||||
|
logger.Field("error", err.Error()),
|
||||||
|
)
|
||||||
|
return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseInsertError), "insert device failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Activate trial if enabled
|
||||||
|
if l.svcCtx.Config.Register.EnableTrial {
|
||||||
|
if err := l.activeTrial(userInfo.Id, db); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
l.Errorw("device registration failed",
|
||||||
|
logger.Field("identifier", req.Identifier),
|
||||||
|
logger.Field("error", err.Error()),
|
||||||
|
)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
l.Infow("device registration completed successfully",
|
||||||
|
logger.Field("user_id", userInfo.Id),
|
||||||
|
logger.Field("identifier", req.Identifier),
|
||||||
|
logger.Field("refer_code", userInfo.ReferCode),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Register log
|
||||||
|
registerLog := log.Register{
|
||||||
|
AuthMethod: "device",
|
||||||
|
Identifier: req.Identifier,
|
||||||
|
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(),
|
||||||
|
Date: time.Now().Format("2006-01-02"),
|
||||||
|
ObjectID: userInfo.Id,
|
||||||
|
Content: string(content),
|
||||||
|
}); err != nil {
|
||||||
|
l.Errorw("failed to insert register log",
|
||||||
|
logger.Field("user_id", userInfo.Id),
|
||||||
|
logger.Field("ip", req.IP),
|
||||||
|
logger.Field("error", err.Error()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return userInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *DeviceLoginLogic) activeTrial(userId int64, db *gorm.DB) error {
|
||||||
|
sub, err := l.svcCtx.SubscribeModel.FindOne(l.ctx, l.svcCtx.Config.Register.TrialSubscribe)
|
||||||
|
if err != nil {
|
||||||
|
l.Errorw("failed to find trial subscription template",
|
||||||
|
logger.Field("user_id", userId),
|
||||||
|
logger.Field("trial_subscribe_id", l.svcCtx.Config.Register.TrialSubscribe),
|
||||||
|
logger.Field("error", err.Error()),
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
startTime := time.Now()
|
||||||
|
expireTime := tool.AddTime(l.svcCtx.Config.Register.TrialTimeUnit, l.svcCtx.Config.Register.TrialTime, startTime)
|
||||||
|
subscribeToken := uuidx.SubscribeToken(fmt.Sprintf("Trial-%v", userId))
|
||||||
|
subscribeUUID := uuidx.NewUUID().String()
|
||||||
|
|
||||||
|
userSub := &user.Subscribe{
|
||||||
|
UserId: userId,
|
||||||
|
OrderId: 0,
|
||||||
|
SubscribeId: sub.Id,
|
||||||
|
StartTime: startTime,
|
||||||
|
ExpireTime: expireTime,
|
||||||
|
Traffic: sub.Traffic,
|
||||||
|
Download: 0,
|
||||||
|
Upload: 0,
|
||||||
|
Token: subscribeToken,
|
||||||
|
UUID: subscribeUUID,
|
||||||
|
Status: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := db.Create(userSub).Error; err != nil {
|
||||||
|
l.Errorw("failed to insert trial subscription",
|
||||||
|
logger.Field("user_id", userId),
|
||||||
|
logger.Field("error", err.Error()),
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
l.Infow("trial subscription activated successfully",
|
||||||
|
logger.Field("user_id", userId),
|
||||||
|
logger.Field("subscribe_id", sub.Id),
|
||||||
|
logger.Field("expire_time", expireTime),
|
||||||
|
logger.Field("traffic", sub.Traffic),
|
||||||
|
)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@ -124,6 +124,19 @@ func (l *TelephoneLoginLogic) TelephoneLogin(req *types.TelephoneLoginRequest, r
|
|||||||
l.svcCtx.Redis.Del(l.ctx, cacheKey)
|
l.svcCtx.Redis.Del(l.ctx, cacheKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
l.Errorw("failed to bind device to user",
|
||||||
|
logger.Field("user_id", userInfo.Id),
|
||||||
|
logger.Field("identifier", req.Identifier),
|
||||||
|
logger.Field("error", err.Error()),
|
||||||
|
)
|
||||||
|
// Don't fail login if device binding fails, just log the error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Generate session id
|
// Generate session id
|
||||||
sessionId := uuidx.NewUUID().String()
|
sessionId := uuidx.NewUUID().String()
|
||||||
// Generate token
|
// Generate token
|
||||||
|
|||||||
@ -138,6 +138,20 @@ func (l *TelephoneUserRegisterLogic) TelephoneUserRegister(req *types.TelephoneR
|
|||||||
}
|
}
|
||||||
return nil
|
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 {
|
||||||
|
l.Errorw("failed to bind device to user",
|
||||||
|
logger.Field("user_id", userInfo.Id),
|
||||||
|
logger.Field("identifier", req.Identifier),
|
||||||
|
logger.Field("error", err.Error()),
|
||||||
|
)
|
||||||
|
// Don't fail register if device binding fails, just log the error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Generate session id
|
// Generate session id
|
||||||
sessionId := uuidx.NewUUID().String()
|
sessionId := uuidx.NewUUID().String()
|
||||||
// Generate token
|
// Generate token
|
||||||
|
|||||||
@ -79,6 +79,20 @@ func (l *UserLoginLogic) UserLogin(req *types.UserLoginRequest) (resp *types.Log
|
|||||||
if !tool.VerifyPassWord(req.Password, userInfo.Password) {
|
if !tool.VerifyPassWord(req.Password, userInfo.Password) {
|
||||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.UserPasswordError), "user password")
|
return nil, errors.Wrapf(xerr.NewErrCode(xerr.UserPasswordError), "user password")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
l.Errorw("failed to bind device to user",
|
||||||
|
logger.Field("user_id", userInfo.Id),
|
||||||
|
logger.Field("identifier", req.Identifier),
|
||||||
|
logger.Field("error", err.Error()),
|
||||||
|
)
|
||||||
|
// Don't fail login if device binding fails, just log the error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Generate session id
|
// Generate session id
|
||||||
sessionId := uuidx.NewUUID().String()
|
sessionId := uuidx.NewUUID().String()
|
||||||
// Generate token
|
// Generate token
|
||||||
|
|||||||
@ -125,6 +125,20 @@ func (l *UserRegisterLogic) UserRegister(req *types.UserRegisterRequest) (resp *
|
|||||||
}
|
}
|
||||||
return nil
|
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 {
|
||||||
|
l.Errorw("failed to bind device to user",
|
||||||
|
logger.Field("user_id", userInfo.Id),
|
||||||
|
logger.Field("identifier", req.Identifier),
|
||||||
|
logger.Field("error", err.Error()),
|
||||||
|
)
|
||||||
|
// Don't fail register if device binding fails, just log the error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Generate session id
|
// Generate session id
|
||||||
sessionId := uuidx.NewUUID().String()
|
sessionId := uuidx.NewUUID().String()
|
||||||
// Generate token
|
// Generate token
|
||||||
|
|||||||
@ -76,3 +76,18 @@ func (m *customUserModel) DeleteDevice(ctx context.Context, id int64, tx ...*gor
|
|||||||
}, data.GetCacheKeys()...)
|
}, data.GetCacheKeys()...)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *customUserModel) InsertDevice(ctx context.Context, data *Device, tx ...*gorm.DB) error {
|
||||||
|
defer func() {
|
||||||
|
if clearErr := m.ClearDeviceCache(ctx, data); clearErr != nil {
|
||||||
|
// log cache clear error
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
return m.ExecNoCacheCtx(ctx, func(conn *gorm.DB) error {
|
||||||
|
if len(tx) > 0 {
|
||||||
|
conn = tx[0]
|
||||||
|
}
|
||||||
|
return conn.Create(data).Error
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@ -99,6 +99,7 @@ type customUserLogicModel interface {
|
|||||||
UpdateDevice(ctx context.Context, data *Device, tx ...*gorm.DB) error
|
UpdateDevice(ctx context.Context, data *Device, tx ...*gorm.DB) error
|
||||||
FindOneDeviceByIdentifier(ctx context.Context, id string) (*Device, error)
|
FindOneDeviceByIdentifier(ctx context.Context, id string) (*Device, error)
|
||||||
DeleteDevice(ctx context.Context, id int64, tx ...*gorm.DB) error
|
DeleteDevice(ctx context.Context, id int64, tx ...*gorm.DB) error
|
||||||
|
InsertDevice(ctx context.Context, data *Device, tx ...*gorm.DB) error
|
||||||
|
|
||||||
ClearSubscribeCache(ctx context.Context, data ...*Subscribe) error
|
ClearSubscribeCache(ctx context.Context, data ...*Subscribe) error
|
||||||
ClearUserCache(ctx context.Context, data ...*User) error
|
ClearUserCache(ctx context.Context, data ...*User) error
|
||||||
|
|||||||
@ -517,6 +517,13 @@ type DeleteUserSubscribeRequest struct {
|
|||||||
UserSubscribeId int64 `json:"user_subscribe_id"`
|
UserSubscribeId int64 `json:"user_subscribe_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DeviceLoginRequest struct {
|
||||||
|
Identifier string `json:"identifier" validate:"required"`
|
||||||
|
IP string `header:"X-Original-Forwarded-For"`
|
||||||
|
UserAgent string `json:"user_agent" validate:"required"`
|
||||||
|
CfToken string `json:"cf_token,optional"`
|
||||||
|
}
|
||||||
|
|
||||||
type Document struct {
|
type Document struct {
|
||||||
Id int64 `json:"id"`
|
Id int64 `json:"id"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
@ -2090,6 +2097,7 @@ type TelephoneCheckUserResponse struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type TelephoneLoginRequest struct {
|
type TelephoneLoginRequest struct {
|
||||||
|
Identifier string `json:"identifier"`
|
||||||
Telephone string `json:"telephone" validate:"required"`
|
Telephone string `json:"telephone" validate:"required"`
|
||||||
TelephoneCode string `json:"telephone_code"`
|
TelephoneCode string `json:"telephone_code"`
|
||||||
TelephoneAreaCode string `json:"telephone_area_code" validate:"required"`
|
TelephoneAreaCode string `json:"telephone_area_code" validate:"required"`
|
||||||
@ -2100,6 +2108,7 @@ type TelephoneLoginRequest struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type TelephoneRegisterRequest struct {
|
type TelephoneRegisterRequest struct {
|
||||||
|
Identifier string `json:"identifier"`
|
||||||
Telephone string `json:"telephone" validate:"required"`
|
Telephone string `json:"telephone" validate:"required"`
|
||||||
TelephoneAreaCode string `json:"telephone_area_code" validate:"required"`
|
TelephoneAreaCode string `json:"telephone_area_code" validate:"required"`
|
||||||
Password string `json:"password" validate:"required"`
|
Password string `json:"password" validate:"required"`
|
||||||
@ -2490,6 +2499,7 @@ type UserLoginLog struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type UserLoginRequest struct {
|
type UserLoginRequest struct {
|
||||||
|
Identifier string `json:"identifier"`
|
||||||
Email string `json:"email" validate:"required"`
|
Email string `json:"email" validate:"required"`
|
||||||
Password string `json:"password" validate:"required"`
|
Password string `json:"password" validate:"required"`
|
||||||
IP string `header:"X-Original-Forwarded-For"`
|
IP string `header:"X-Original-Forwarded-For"`
|
||||||
@ -2498,6 +2508,7 @@ type UserLoginRequest struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type UserRegisterRequest struct {
|
type UserRegisterRequest struct {
|
||||||
|
Identifier string `json:"identifier"`
|
||||||
Email string `json:"email" validate:"required"`
|
Email string `json:"email" validate:"required"`
|
||||||
Password string `json:"password" validate:"required"`
|
Password string `json:"password" validate:"required"`
|
||||||
Invite string `json:"invite,optional"`
|
Invite string `json:"invite,optional"`
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user