fix gitea workflow path and runner label
Build docker and publish / build (20.15.1) (push) Failing after 7m57s
Build docker and publish / build (20.15.1) (push) Failing after 7m57s
This commit is contained in:
@@ -2,15 +2,14 @@ package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/perfect-panel/server/internal/config"
|
||||
commonLogic "github.com/perfect-panel/server/internal/logic/common"
|
||||
"github.com/perfect-panel/server/internal/logic/public/user"
|
||||
"github.com/perfect-panel/server/internal/svc"
|
||||
"github.com/perfect-panel/server/internal/types"
|
||||
"github.com/perfect-panel/server/pkg/authmethod"
|
||||
"github.com/perfect-panel/server/pkg/constant"
|
||||
"github.com/perfect-panel/server/pkg/result"
|
||||
"github.com/perfect-panel/server/pkg/xerr"
|
||||
@@ -36,7 +35,7 @@ func DeleteAccountHandler(serverCtx *svc.ServiceContext) gin.HandlerFunc {
|
||||
// 统一处理邮箱格式:转小写并去空格,与发送验证码逻辑保持一致
|
||||
req.Email = strings.ToLower(strings.TrimSpace(req.Email))
|
||||
|
||||
// 校验邮箱验证码
|
||||
// 校验邮箱验证码(统一走公共验证码校验器,支持 delete_account/security 场景兼容)
|
||||
if err := verifyEmailCode(c.Request.Context(), serverCtx, req.Email, req.Code); err != nil {
|
||||
result.HttpResult(c, nil, err)
|
||||
return
|
||||
@@ -48,43 +47,29 @@ func DeleteAccountHandler(serverCtx *svc.ServiceContext) gin.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// CacheKeyPayload 验证码缓存结构
|
||||
type CacheKeyPayload struct {
|
||||
Code string `json:"code"`
|
||||
LastAt int64 `json:"lastAt"`
|
||||
}
|
||||
|
||||
// verifyEmailCode 校验邮箱验证码
|
||||
// 支持 DeleteAccount 和 Security 两种场景的验证码
|
||||
// 1. 统一复用 common checker
|
||||
// 2. 强制 consume=true(最终业务操作做一次性消费)
|
||||
// 3. 兼容历史 security 场景验证码(allow fallback)
|
||||
func verifyEmailCode(ctx context.Context, serverCtx *svc.ServiceContext, email string, code string) error {
|
||||
// 尝试多种场景的验证码
|
||||
scenes := []string{constant.DeleteAccount.String(), constant.Security.String()}
|
||||
var verified bool
|
||||
var cacheKeyUsed string
|
||||
var payload CacheKeyPayload
|
||||
|
||||
for _, scene := range scenes {
|
||||
cacheKey := fmt.Sprintf("%s:%s:%s", config.AuthCodeCacheKey, scene, email)
|
||||
value, err := serverCtx.Redis.Get(ctx, cacheKey).Result()
|
||||
if err != nil || value == "" {
|
||||
continue
|
||||
}
|
||||
if err := json.Unmarshal([]byte(value), &payload); err != nil {
|
||||
continue
|
||||
}
|
||||
// 检查验证码是否匹配且未过期
|
||||
if payload.Code == code && time.Now().Unix()-payload.LastAt <= serverCtx.Config.VerifyCode.VerifyCodeExpireTime {
|
||||
verified = true
|
||||
cacheKeyUsed = cacheKey
|
||||
break
|
||||
}
|
||||
l := commonLogic.NewCheckVerificationCodeLogic(ctx, serverCtx)
|
||||
resp, err := l.CheckVerificationCodeWithBehavior(&types.CheckVerificationCodeRequest{
|
||||
Method: authmethod.Email,
|
||||
Account: email,
|
||||
Code: strings.TrimSpace(code),
|
||||
Type: uint8(constant.DeleteAccount),
|
||||
}, commonLogic.VerifyCodeCheckBehavior{
|
||||
Source: "delete_account",
|
||||
Consume: true,
|
||||
AllowSceneFallback: true,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !verified {
|
||||
if resp == nil || !resp.Status {
|
||||
return errors.Wrapf(xerr.NewErrCode(xerr.VerifyCodeError), "verification code error or expired")
|
||||
}
|
||||
|
||||
// 验证成功后删除缓存
|
||||
serverCtx.Redis.Del(ctx, cacheKeyUsed)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -5,14 +5,18 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/alicebob/miniredis/v2"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/perfect-panel/server/internal/config"
|
||||
"github.com/perfect-panel/server/internal/svc"
|
||||
"github.com/perfect-panel/server/pkg/constant"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
@@ -100,3 +104,89 @@ func TestDeleteAccountHandlerVerifyCodeErrorUsesUnifiedResponse(t *testing.T) {
|
||||
t.Fatalf("expected business code 70001, got %d, body=%s", resp.Code, recorder.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyEmailCode_DeleteAccountSceneConsume(t *testing.T) {
|
||||
miniRedis := miniredis.RunT(t)
|
||||
redisClient := redis.NewClient(&redis.Options{Addr: miniRedis.Addr()})
|
||||
t.Cleanup(func() {
|
||||
redisClient.Close()
|
||||
miniRedis.Close()
|
||||
})
|
||||
|
||||
serverCtx := &svc.ServiceContext{
|
||||
Redis: redisClient,
|
||||
Config: config.Config{
|
||||
VerifyCode: config.VerifyCode{VerifyCodeExpireTime: 900},
|
||||
},
|
||||
}
|
||||
|
||||
email := "delete-account@example.com"
|
||||
code := "112233"
|
||||
cacheKey := seedDeleteSceneCode(t, redisClient, constant.DeleteAccount.String(), email, code)
|
||||
|
||||
err := verifyEmailCode(context.Background(), serverCtx, email, code)
|
||||
if err != nil {
|
||||
t.Fatalf("verifyEmailCode returned unexpected error: %v", err)
|
||||
}
|
||||
|
||||
exists, err := redisClient.Exists(context.Background(), cacheKey).Result()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to check redis key: %v", err)
|
||||
}
|
||||
if exists != 0 {
|
||||
t.Fatalf("expected verification code to be consumed, key still exists")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyEmailCode_SecurityFallbackConsume(t *testing.T) {
|
||||
miniRedis := miniredis.RunT(t)
|
||||
redisClient := redis.NewClient(&redis.Options{Addr: miniRedis.Addr()})
|
||||
t.Cleanup(func() {
|
||||
redisClient.Close()
|
||||
miniRedis.Close()
|
||||
})
|
||||
|
||||
serverCtx := &svc.ServiceContext{
|
||||
Redis: redisClient,
|
||||
Config: config.Config{
|
||||
VerifyCode: config.VerifyCode{VerifyCodeExpireTime: 900},
|
||||
},
|
||||
}
|
||||
|
||||
email := "security-fallback@example.com"
|
||||
code := "445566"
|
||||
cacheKey := seedDeleteSceneCode(t, redisClient, constant.Security.String(), email, code)
|
||||
|
||||
err := verifyEmailCode(context.Background(), serverCtx, email, code)
|
||||
if err != nil {
|
||||
t.Fatalf("verifyEmailCode fallback returned unexpected error: %v", err)
|
||||
}
|
||||
|
||||
exists, err := redisClient.Exists(context.Background(), cacheKey).Result()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to check redis key: %v", err)
|
||||
}
|
||||
if exists != 0 {
|
||||
t.Fatalf("expected fallback verification code to be consumed, key still exists")
|
||||
}
|
||||
}
|
||||
|
||||
func seedDeleteSceneCode(t *testing.T, redisClient *redis.Client, scene string, email string, code string) string {
|
||||
t.Helper()
|
||||
|
||||
cacheKey := fmt.Sprintf("%s:%s:%s", config.AuthCodeCacheKey, scene, email)
|
||||
payload := map[string]interface{}{
|
||||
"code": code,
|
||||
"lastAt": time.Now().Unix(),
|
||||
}
|
||||
payloadRaw, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal payload: %v", err)
|
||||
}
|
||||
err = redisClient.Set(context.Background(), cacheKey, payloadRaw, time.Minute*15).Err()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to seed redis payload: %v", err)
|
||||
}
|
||||
|
||||
return cacheKey
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user