From ccdcfd343002cb11f6c852358cc4ffaf52df15ff Mon Sep 17 00:00:00 2001 From: shanshanzhong Date: Fri, 31 Oct 2025 01:59:14 -0700 Subject: [PATCH] =?UTF-8?q?fix(account):=20=E5=B0=86=E5=88=A0=E9=99=A4?= =?UTF-8?q?=E8=B4=A6=E5=8F=B7=E6=8E=A5=E5=8F=A3=E4=BB=8EGET=E6=94=B9?= =?UTF-8?q?=E4=B8=BAPOST=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复删除账号接口的安全问题,GET方法不应用于敏感操作 同时增加邮箱验证码校验,提高账号安全性 ``` ```msg feat(auth): 在设备登录时更新用户代理信息 添加设备登录时更新用户代理(UA)的逻辑 确保设备信息保持最新状态 ``` ```msg refactor(handler): 重构删除账号处理器的验证逻辑 将邮箱验证码校验逻辑提取为独立函数 提高代码可维护性和复用性 --- .../public/user/deleteAccountHandler.go | 46 +++++++++++++++++++ internal/handler/routes.go | 2 +- internal/logic/auth/deviceLoginLogic.go | 11 +++++ 3 files changed, 58 insertions(+), 1 deletion(-) diff --git a/internal/handler/public/user/deleteAccountHandler.go b/internal/handler/public/user/deleteAccountHandler.go index 67a5ebc..10f597d 100644 --- a/internal/handler/public/user/deleteAccountHandler.go +++ b/internal/handler/public/user/deleteAccountHandler.go @@ -1,17 +1,39 @@ package user import ( + "context" + "encoding/json" + "fmt" "net/http" "github.com/gin-gonic/gin" "github.com/perfect-panel/server/internal/logic/public/user" "github.com/perfect-panel/server/internal/svc" + "github.com/perfect-panel/server/pkg/constant" + "github.com/pkg/errors" ) // DeleteAccountHandler 注销账号处理器 // 根据当前token删除所有关联设备,然后根据各自设备ID重新新建账号 +// 新增:需携带邮箱验证码,验证通过后执行注销 +type deleteAccountReq struct { + EmailCode string `json:"email_code" binding:"required"` // 邮箱验证码 +} + func DeleteAccountHandler(serverCtx *svc.ServiceContext) gin.HandlerFunc { return func(c *gin.Context) { + var req deleteAccountReq + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid request: " + err.Error()}) + return + } + + // 校验邮箱验证码 + if err := verifyEmailCode(c.Request.Context(), serverCtx, req.EmailCode); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + l := user.NewDeleteAccountLogic(c.Request.Context(), serverCtx) resp, err := l.DeleteAccount() if err != nil { @@ -21,3 +43,27 @@ func DeleteAccountHandler(serverCtx *svc.ServiceContext) gin.HandlerFunc { c.JSON(http.StatusOK, resp) } } + +type CacheKeyPayload struct { + Code string `json:"code"` + LastAt int64 `json:"lastAt"` +} + +func verifyEmailCode(ctx context.Context, serverCtx *svc.ServiceContext, code string) error { + userEmail := ctx.Value("user_email").(string) + cacheKey := fmt.Sprintf("auth_code:%s:%s", constant.Security, userEmail) + val, err := serverCtx.Redis.Get(ctx, cacheKey).Result() + if err != nil { + return errors.Wrap(err, "failed to get cached code") + } + var payload CacheKeyPayload + if err := json.Unmarshal([]byte(val), &payload); err != nil { + return errors.Wrap(err, "invalid cached payload") + } + if payload.Code != code { + return errors.New("invalid email code") + } + // 验证通过,立即删除缓存,防止重复使用 + _ = serverCtx.Redis.Del(ctx, cacheKey) + return nil +} diff --git a/internal/handler/routes.go b/internal/handler/routes.go index 19baa25..b605d9f 100644 --- a/internal/handler/routes.go +++ b/internal/handler/routes.go @@ -775,7 +775,7 @@ func RegisterHandlers(router *gin.Engine, serverCtx *svc.ServiceContext) { publicUserGroupRouter.GET("/affiliate/list", publicUser.QueryUserAffiliateListHandler(serverCtx)) // Delete Account - publicUserGroupRouter.GET("/delete_account", publicUser.DeleteAccountHandler(serverCtx)) + publicUserGroupRouter.POST("/delete_account", publicUser.DeleteAccountHandler(serverCtx)) // Query User Balance Log publicUserGroupRouter.GET("/balance_log", publicUser.QueryUserBalanceLogHandler(serverCtx)) diff --git a/internal/logic/auth/deviceLoginLogic.go b/internal/logic/auth/deviceLoginLogic.go index d152f3e..8b25459 100644 --- a/internal/logic/auth/deviceLoginLogic.go +++ b/internal/logic/auth/deviceLoginLogic.go @@ -43,6 +43,7 @@ func (l *DeviceLoginLogic) DeviceLogin(req *types.DeviceLoginRequest) (resp *typ var userInfo *user.User // Record login status defer func() { + if userInfo != nil && userInfo.Id != 0 { loginLog := log.Login{ Method: "device", @@ -95,6 +96,16 @@ func (l *DeviceLoginLogic) DeviceLogin(req *types.DeviceLoginRequest) (resp *typ } } + // 根据 req 中的UA 更新UA + deviceInfo.UserAgent = req.UserAgent + if err := l.svcCtx.UserModel.UpdateDevice(l.ctx, deviceInfo); err != nil { + l.Errorw("update user agent failed", + logger.Field("device_id", deviceInfo.Id), + logger.Field("error", err.Error()), + ) + return nil, errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update user agent failed: %v", err.Error()) + } + // Generate session id sessionId := uuidx.NewUUID().String()