jwt注销问题
All checks were successful
Build docker and publish / build (20.15.1) (push) Successful in 5m54s
All checks were successful
Build docker and publish / build (20.15.1) (push) Successful in 5m54s
This commit is contained in:
parent
93c4d7b7d1
commit
7b33ab6e2a
@ -51,6 +51,7 @@ type (
|
||||
CfToken string `json:"cf_token,optional"`
|
||||
}
|
||||
EmailLoginRequest {
|
||||
Identifier string `json:"identifier"`
|
||||
Email string `json:"email" validate:"required"`
|
||||
Code string `json:"code" validate:"required"`
|
||||
Invite string `json:"invite,optional"`
|
||||
|
||||
@ -200,6 +200,28 @@ func (l *EmailLoginLogic) EmailLogin(req *types.EmailLoginRequest) (resp *types.
|
||||
)
|
||||
}
|
||||
|
||||
// Bind device to user if identifier is provided
|
||||
var deviceId int64
|
||||
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()),
|
||||
)
|
||||
} else {
|
||||
// Query device info to get DeviceId
|
||||
if device, dErr := l.svcCtx.UserModel.FindOneDeviceByIdentifier(l.ctx, req.Identifier); dErr == nil {
|
||||
deviceId = device.Id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Login (Generate Token)
|
||||
if l.ctx.Value(constant.LoginType) != nil {
|
||||
req.LoginType = l.ctx.Value(constant.LoginType).(string)
|
||||
@ -213,6 +235,7 @@ func (l *EmailLoginLogic) EmailLogin(req *types.EmailLoginRequest) (resp *types.
|
||||
jwt.WithOption("UserId", userInfo.Id),
|
||||
jwt.WithOption("SessionId", sessionId),
|
||||
jwt.WithOption("LoginType", req.LoginType),
|
||||
jwt.WithOption("DeviceId", deviceId),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "token generate error: %v", err.Error())
|
||||
|
||||
@ -92,6 +92,7 @@ func (l *UserLoginLogic) UserLogin(req *types.UserLoginRequest) (resp *types.Log
|
||||
}
|
||||
|
||||
// Bind device to user if identifier is provided
|
||||
var deviceId int64
|
||||
if req.Identifier != "" {
|
||||
bindLogic := NewBindDeviceLogic(l.ctx, l.svcCtx)
|
||||
if err := bindLogic.BindDeviceToUser(req.Identifier, req.IP, req.UserAgent, userInfo.Id); err != nil {
|
||||
@ -104,6 +105,11 @@ func (l *UserLoginLogic) UserLogin(req *types.UserLoginRequest) (resp *types.Log
|
||||
logger.Field("identifier", req.Identifier),
|
||||
logger.Field("error", err.Error()),
|
||||
)
|
||||
} else {
|
||||
// Query device info to get DeviceId
|
||||
if device, dErr := l.svcCtx.UserModel.FindOneDeviceByIdentifier(l.ctx, req.Identifier); dErr == nil {
|
||||
deviceId = device.Id
|
||||
}
|
||||
}
|
||||
}
|
||||
if l.ctx.Value(constant.LoginType) != nil {
|
||||
@ -119,6 +125,7 @@ func (l *UserLoginLogic) UserLogin(req *types.UserLoginRequest) (resp *types.Log
|
||||
jwt.WithOption("UserId", userInfo.Id),
|
||||
jwt.WithOption("SessionId", sessionId),
|
||||
jwt.WithOption("LoginType", req.LoginType),
|
||||
jwt.WithOption("DeviceId", deviceId),
|
||||
)
|
||||
if err != nil {
|
||||
l.Logger.Error("[UserLogin] token generate error", logger.Field("error", err.Error()))
|
||||
|
||||
@ -33,11 +33,7 @@ func NewDeleteAccountLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Del
|
||||
}
|
||||
}
|
||||
|
||||
// DeleteAccount 注销账号逻辑
|
||||
// 1. 获取当前用户信息
|
||||
// 2. 删除所有关联数据(用户、认证方式、设备)
|
||||
// 3. 根据原设备信息创建全新账号
|
||||
// 4. 返回新账号信息
|
||||
// DeleteAccount 注销当前设备账号逻辑 (改为精准解绑)
|
||||
func (l *DeleteAccountLogic) DeleteAccount() (resp *types.DeleteAccountResponse, err error) {
|
||||
// 获取当前用户
|
||||
currentUser, ok := l.ctx.Value(constant.CtxKeyUser).(*user.User)
|
||||
@ -45,48 +41,74 @@ func (l *DeleteAccountLogic) DeleteAccount() (resp *types.DeleteAccountResponse,
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidAccess), "Invalid Access")
|
||||
}
|
||||
|
||||
// 获取当前调用设备 ID
|
||||
currentDeviceId, _ := l.ctx.Value(constant.CtxKeyDeviceID).(int64)
|
||||
|
||||
resp = &types.DeleteAccountResponse{}
|
||||
var newUserId int64
|
||||
|
||||
// 如果没有识别到设备 ID (可能是旧版 Token),则执行安全注销:仅清除 Session
|
||||
if currentDeviceId == 0 {
|
||||
l.Infow("未识别到设备 ID,仅清理当前会话", logger.Field("user_id", currentUser.Id))
|
||||
l.clearCurrentSession(currentUser.Id)
|
||||
resp.Success = true
|
||||
resp.Message = "会话已清除"
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// 开始数据库事务
|
||||
err = l.svcCtx.UserModel.Transaction(l.ctx, func(tx *gorm.DB) error {
|
||||
// 1. 查找用户的所有设备(用于后续创建新账号)
|
||||
devices, _, err := l.svcCtx.UserModel.QueryDeviceList(l.ctx, currentUser.Id)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "查询用户设备失败")
|
||||
// 1. 查找当前设备
|
||||
var currentDevice user.Device
|
||||
if err := tx.Where("id = ? AND user_id = ?", currentDeviceId, currentUser.Id).First(¤tDevice).Error; err != nil {
|
||||
l.Infow("当前请求设备记录不存在或归属不匹配", logger.Field("device_id", currentDeviceId), logger.Field("error", err.Error()))
|
||||
return nil // 不抛错,直接走清理 Session 流程
|
||||
}
|
||||
|
||||
// 2. 删除用户的所有认证方式
|
||||
err = tx.Model(&user.AuthMethods{}).Where("`user_id` = ?", currentUser.Id).Delete(&user.AuthMethods{}).Error
|
||||
// 2. 检查用户是否有其他认证方式 (如邮箱) 或 其他设备
|
||||
var authMethodsCount int64
|
||||
tx.Model(&user.AuthMethods{}).Where("user_id = ?", currentUser.Id).Count(&authMethodsCount)
|
||||
|
||||
var devicesCount int64
|
||||
tx.Model(&user.Device{}).Where("user_id = ?", currentUser.Id).Count(&devicesCount)
|
||||
|
||||
// 判定是否是主账号解绑:如果除了当前设备外,还有邮箱或其他设备,则只解绑当前设备
|
||||
isMainAccount := authMethodsCount > 1 || devicesCount > 1
|
||||
|
||||
if isMainAccount {
|
||||
l.Infow("主账号解绑,仅迁移当前设备", logger.Field("user_id", currentUser.Id), logger.Field("device_id", currentDeviceId))
|
||||
|
||||
// 为当前设备创建新用户并迁移
|
||||
newUser, err := l.registerUserAndDevice(tx, currentDevice.Identifier, currentDevice.Ip, currentDevice.UserAgent)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "删除用户认证方式失败")
|
||||
return err
|
||||
}
|
||||
newUserId = newUser.Id
|
||||
|
||||
// 从原用户删除当前设备的认证方式 (按 Identifier 准确删除)
|
||||
if err := tx.Where("user_id = ? AND auth_type = ? AND auth_identifier = ?", currentUser.Id, "device", currentDevice.Identifier).Delete(&user.AuthMethods{}).Error; err != nil {
|
||||
return errors.Wrap(err, "删除原设备认证失败")
|
||||
}
|
||||
|
||||
// 3. 删除用户的所有设备
|
||||
err = tx.Model(&user.Device{}).Where("`user_id` = ?", currentUser.Id).Delete(&user.Device{}).Error
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "删除用户设备失败")
|
||||
// 从原用户删除当前设备记录
|
||||
if err := tx.Where("id = ?", currentDeviceId).Delete(&user.Device{}).Error; err != nil {
|
||||
return errors.Wrap(err, "删除原设备记录失败")
|
||||
}
|
||||
} else {
|
||||
l.Infow("纯设备账号注销,执行物理删除并重置", logger.Field("user_id", currentUser.Id), logger.Field("device_id", currentDeviceId))
|
||||
|
||||
// 4. 删除用户的订阅信息
|
||||
err = tx.Model(&user.Subscribe{}).Where("`user_id` = ?", currentUser.Id).Delete(&user.Subscribe{}).Error
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "删除用户订阅信息失败")
|
||||
}
|
||||
// 完全删除原用户相关资产
|
||||
tx.Where("user_id = ?", currentUser.Id).Delete(&user.AuthMethods{})
|
||||
tx.Where("user_id = ?", currentUser.Id).Delete(&user.Device{})
|
||||
tx.Where("user_id = ?", currentUser.Id).Delete(&user.Subscribe{})
|
||||
tx.Delete(&user.User{}, currentUser.Id)
|
||||
|
||||
// 5. 删除用户本身
|
||||
err = tx.Model(&user.User{}).Where("`id` = ?", currentUser.Id).Delete(&user.User{}).Error
|
||||
// 重新注册一个新用户
|
||||
newUser, err := l.registerUserAndDevice(tx, currentDevice.Identifier, currentDevice.Ip, currentDevice.UserAgent)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "删除用户失败")
|
||||
return err
|
||||
}
|
||||
|
||||
// 7. 为每个原设备创建新的用户(使用同一事务)
|
||||
for _, oldDevice := range devices {
|
||||
userInfo, err := l.registerUserAndDevice(tx, oldDevice.Identifier, oldDevice.Ip, oldDevice.UserAgent)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "创建新用户失败")
|
||||
}
|
||||
newUserId = userInfo.Id // 保留最后一个新用户ID
|
||||
newUserId = newUser.Id
|
||||
}
|
||||
|
||||
return nil
|
||||
@ -96,23 +118,28 @@ func (l *DeleteAccountLogic) DeleteAccount() (resp *types.DeleteAccountResponse,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 注销当前会话token(删除Redis中的会话标记,并从用户会话集合移除)
|
||||
if sessionId, ok := l.ctx.Value(constant.CtxKeySessionID).(string); ok && sessionId != "" {
|
||||
sessionKey := fmt.Sprintf("%v:%v", config.SessionIdKey, sessionId)
|
||||
_ = l.svcCtx.Redis.Del(l.ctx, sessionKey).Err()
|
||||
// 从用户会话集合中移除当前session,避免残留
|
||||
sessionsKey := fmt.Sprintf("%s%v", config.UserSessionsKeyPrefix, currentUser.Id)
|
||||
_ = l.svcCtx.Redis.ZRem(l.ctx, sessionsKey, sessionId).Err()
|
||||
}
|
||||
// 最终清理当前 Session
|
||||
l.clearCurrentSession(currentUser.Id)
|
||||
|
||||
resp.Success = true
|
||||
resp.Message = "账户注销成功"
|
||||
resp.Message = "注销成功"
|
||||
resp.UserId = newUserId
|
||||
resp.Code = 200
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// clearCurrentSession 清理当前请求的会话
|
||||
func (l *DeleteAccountLogic) clearCurrentSession(userId int64) {
|
||||
if sessionId, ok := l.ctx.Value(constant.CtxKeySessionID).(string); ok && sessionId != "" {
|
||||
sessionKey := fmt.Sprintf("%v:%v", config.SessionIdKey, sessionId)
|
||||
_ = l.svcCtx.Redis.Del(l.ctx, sessionKey).Err()
|
||||
// 从用户会话集合中移除当前session
|
||||
sessionsKey := fmt.Sprintf("%s%v", config.UserSessionsKeyPrefix, userId)
|
||||
_ = l.svcCtx.Redis.ZRem(l.ctx, sessionsKey, sessionId).Err()
|
||||
}
|
||||
}
|
||||
|
||||
// generateReferCode 生成推荐码
|
||||
func generateReferCode() string {
|
||||
bytes := make([]byte, 4)
|
||||
|
||||
@ -50,6 +50,11 @@ func AuthMiddleware(svc *svc.ServiceContext) func(c *gin.Context) {
|
||||
userId := int64(claims["UserId"].(float64))
|
||||
// get session id from token
|
||||
sessionId := claims["SessionId"].(string)
|
||||
// get device id from token
|
||||
var deviceId int64
|
||||
if claims["DeviceId"] != nil {
|
||||
deviceId = int64(claims["DeviceId"].(float64))
|
||||
}
|
||||
// get session id from redis
|
||||
sessionIdCacheKey := fmt.Sprintf("%v:%v", config.SessionIdKey, sessionId)
|
||||
value, err := svc.Redis.Get(c, sessionIdCacheKey).Result()
|
||||
@ -91,6 +96,9 @@ func AuthMiddleware(svc *svc.ServiceContext) func(c *gin.Context) {
|
||||
ctx = context.WithValue(ctx, constant.LoginType, loginType)
|
||||
ctx = context.WithValue(ctx, constant.CtxKeyUser, userInfo)
|
||||
ctx = context.WithValue(ctx, constant.CtxKeySessionID, sessionId)
|
||||
if deviceId > 0 {
|
||||
ctx = context.WithValue(ctx, constant.CtxKeyDeviceID, deviceId)
|
||||
}
|
||||
c.Request = c.Request.WithContext(ctx)
|
||||
c.Next()
|
||||
}
|
||||
|
||||
@ -596,6 +596,7 @@ type EmailAuthticateConfig struct {
|
||||
}
|
||||
|
||||
type EmailLoginRequest struct {
|
||||
Identifier string `json:"identifier"`
|
||||
Email string `json:"email" validate:"required"`
|
||||
Code string `json:"code" validate:"required"`
|
||||
Invite string `json:"invite,optional"`
|
||||
|
||||
@ -9,5 +9,6 @@ const (
|
||||
CtxKeyPlatform CtxKey = "platform"
|
||||
CtxKeyPayment CtxKey = "payment"
|
||||
LoginType CtxKey = "loginType"
|
||||
CtxKeyDeviceID CtxKey = "deviceId"
|
||||
CtxKeyIncludeExpired CtxKey = "includeExpired"
|
||||
)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user