fix gitea workflow path and runner label
Build docker and publish / build (20.15.1) (push) Failing after 8m21s
Build docker and publish / build (20.15.1) (push) Failing after 8m21s
This commit is contained in:
@@ -2,19 +2,9 @@ package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/perfect-panel/server/internal/config"
|
||||
"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/logger"
|
||||
"github.com/perfect-panel/server/pkg/phone"
|
||||
"github.com/perfect-panel/server/pkg/xerr"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type CheckVerificationCodeLogic struct {
|
||||
@@ -33,40 +23,8 @@ func NewCheckVerificationCodeLogic(ctx context.Context, svcCtx *svc.ServiceConte
|
||||
}
|
||||
|
||||
func (l *CheckVerificationCodeLogic) CheckVerificationCode(req *types.CheckVerificationCodeRequest) (resp *types.CheckVerificationCodeRespone, err error) {
|
||||
resp = &types.CheckVerificationCodeRespone{}
|
||||
req.Account = strings.ToLower(strings.TrimSpace(req.Account))
|
||||
if req.Method == authmethod.Email {
|
||||
cacheKey := fmt.Sprintf("%s:%s:%s", config.AuthCodeCacheKey, constant.ParseVerifyType(req.Type), req.Account)
|
||||
value, err := l.svcCtx.Redis.Get(l.ctx, cacheKey).Result()
|
||||
if err != nil {
|
||||
return resp, nil
|
||||
}
|
||||
var payload CacheKeyPayload
|
||||
if err := json.Unmarshal([]byte(value), &payload); err != nil {
|
||||
return resp, nil
|
||||
}
|
||||
if payload.Code != req.Code {
|
||||
return resp, nil
|
||||
}
|
||||
resp.Status = true
|
||||
}
|
||||
if req.Method == authmethod.Mobile {
|
||||
if !phone.CheckPhone(req.Account) {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.TelephoneError), "Invalid phone number")
|
||||
}
|
||||
cacheKey := fmt.Sprintf("%s:%s:+%s", config.AuthCodeTelephoneCacheKey, constant.ParseVerifyType(req.Type), req.Account)
|
||||
value, err := l.svcCtx.Redis.Get(l.ctx, cacheKey).Result()
|
||||
if err != nil {
|
||||
return resp, nil
|
||||
}
|
||||
var payload CacheKeyPayload
|
||||
if err := json.Unmarshal([]byte(value), &payload); err != nil {
|
||||
return resp, nil
|
||||
}
|
||||
if payload.Code != req.Code {
|
||||
return resp, nil
|
||||
}
|
||||
resp.Status = true
|
||||
}
|
||||
return resp, nil
|
||||
return l.CheckVerificationCodeWithBehavior(req, VerifyCodeCheckBehavior{
|
||||
Source: "canonical",
|
||||
Consume: true,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,259 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/alicebob/miniredis/v2"
|
||||
"github.com/perfect-panel/server/internal/config"
|
||||
"github.com/perfect-panel/server/internal/svc"
|
||||
"github.com/perfect-panel/server/internal/types"
|
||||
"github.com/perfect-panel/server/pkg/apiversion"
|
||||
"github.com/perfect-panel/server/pkg/authmethod"
|
||||
"github.com/perfect-panel/server/pkg/constant"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCheckVerificationCodeCanonicalConsume(t *testing.T) {
|
||||
miniRedis := miniredis.RunT(t)
|
||||
redisClient := redis.NewClient(&redis.Options{Addr: miniRedis.Addr()})
|
||||
t.Cleanup(func() {
|
||||
redisClient.Close()
|
||||
miniRedis.Close()
|
||||
})
|
||||
|
||||
svcCtx := &svc.ServiceContext{
|
||||
Redis: redisClient,
|
||||
Config: config.Config{
|
||||
VerifyCode: config.VerifyCode{
|
||||
VerifyCodeExpireTime: 900,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
email := "user@example.com"
|
||||
code := "123456"
|
||||
scene := constant.Register.String()
|
||||
cacheKey := fmt.Sprintf("%s:%s:%s", config.AuthCodeCacheKey, scene, email)
|
||||
setEmailCodePayload(t, redisClient, cacheKey, code, time.Now().Unix())
|
||||
|
||||
logic := NewCheckVerificationCodeLogic(context.Background(), svcCtx)
|
||||
req := &types.CheckVerificationCodeRequest{
|
||||
Method: authmethod.Email,
|
||||
Account: email,
|
||||
Code: code,
|
||||
Type: uint8(constant.Register),
|
||||
}
|
||||
|
||||
resp, err := logic.CheckVerificationCode(req)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp)
|
||||
assert.True(t, resp.Status)
|
||||
assert.True(t, resp.Exist)
|
||||
|
||||
exists, err := redisClient.Exists(context.Background(), cacheKey).Result()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(0), exists)
|
||||
|
||||
resp, err = logic.CheckVerificationCode(req)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp)
|
||||
assert.False(t, resp.Status)
|
||||
assert.False(t, resp.Exist)
|
||||
}
|
||||
|
||||
func TestCheckVerificationCodeLegacyNoConsumeAndType3Mapping(t *testing.T) {
|
||||
miniRedis := miniredis.RunT(t)
|
||||
redisClient := redis.NewClient(&redis.Options{Addr: miniRedis.Addr()})
|
||||
t.Cleanup(func() {
|
||||
redisClient.Close()
|
||||
miniRedis.Close()
|
||||
})
|
||||
|
||||
svcCtx := &svc.ServiceContext{
|
||||
Redis: redisClient,
|
||||
Config: config.Config{
|
||||
VerifyCode: config.VerifyCode{
|
||||
VerifyCodeExpireTime: 900,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
email := "legacy@example.com"
|
||||
code := "654321"
|
||||
scene := constant.Security.String()
|
||||
cacheKey := fmt.Sprintf("%s:%s:%s", config.AuthCodeCacheKey, scene, email)
|
||||
setEmailCodePayload(t, redisClient, cacheKey, code, time.Now().Unix())
|
||||
|
||||
legacyReq := &types.LegacyCheckVerificationCodeRequest{
|
||||
Email: email,
|
||||
Code: code,
|
||||
Type: 3,
|
||||
}
|
||||
|
||||
normalizedReq, type3Mapped, err := NormalizeLegacyCheckVerificationCodeRequest(legacyReq)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, type3Mapped)
|
||||
assert.Equal(t, uint8(constant.Security), normalizedReq.Type)
|
||||
assert.Equal(t, authmethod.Email, normalizedReq.Method)
|
||||
assert.Equal(t, email, normalizedReq.Account)
|
||||
|
||||
logic := NewCheckVerificationCodeLogic(context.Background(), svcCtx)
|
||||
legacyBehavior := VerifyCodeCheckBehavior{
|
||||
Source: "legacy",
|
||||
Consume: false,
|
||||
LegacyType3Mapped: true,
|
||||
AllowSceneFallback: true,
|
||||
}
|
||||
|
||||
resp, err := logic.CheckVerificationCodeWithBehavior(normalizedReq, legacyBehavior)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp)
|
||||
assert.True(t, resp.Status)
|
||||
assert.True(t, resp.Exist)
|
||||
|
||||
exists, err := redisClient.Exists(context.Background(), cacheKey).Result()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(1), exists)
|
||||
|
||||
resp, err = logic.CheckVerificationCodeWithBehavior(normalizedReq, legacyBehavior)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, resp.Status)
|
||||
|
||||
resp, err = logic.CheckVerificationCode(normalizedReq)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, resp.Status)
|
||||
|
||||
exists, err = redisClient.Exists(context.Background(), cacheKey).Result()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, int64(0), exists)
|
||||
}
|
||||
|
||||
func TestCheckVerificationCodeLegacySceneFallback(t *testing.T) {
|
||||
miniRedis := miniredis.RunT(t)
|
||||
redisClient := redis.NewClient(&redis.Options{Addr: miniRedis.Addr()})
|
||||
t.Cleanup(func() {
|
||||
redisClient.Close()
|
||||
miniRedis.Close()
|
||||
})
|
||||
|
||||
svcCtx := &svc.ServiceContext{
|
||||
Redis: redisClient,
|
||||
Config: config.Config{
|
||||
VerifyCode: config.VerifyCode{
|
||||
VerifyCodeExpireTime: 900,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
email := "fallback@example.com"
|
||||
code := "778899"
|
||||
cacheKey := fmt.Sprintf("%s:%s:%s", config.AuthCodeCacheKey, constant.Register.String(), email)
|
||||
setEmailCodePayload(t, redisClient, cacheKey, code, time.Now().Unix())
|
||||
|
||||
logic := NewCheckVerificationCodeLogic(context.Background(), svcCtx)
|
||||
req := &types.CheckVerificationCodeRequest{
|
||||
Method: authmethod.Email,
|
||||
Account: email,
|
||||
Code: code,
|
||||
Type: uint8(constant.Security),
|
||||
}
|
||||
|
||||
resp, err := logic.CheckVerificationCodeWithBehavior(req, VerifyCodeCheckBehavior{
|
||||
Source: "legacy",
|
||||
Consume: false,
|
||||
AllowSceneFallback: true,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp)
|
||||
assert.True(t, resp.Status)
|
||||
|
||||
resp, err = logic.CheckVerificationCodeWithBehavior(req, VerifyCodeCheckBehavior{
|
||||
Source: "legacy",
|
||||
Consume: false,
|
||||
AllowSceneFallback: false,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp)
|
||||
assert.False(t, resp.Status)
|
||||
}
|
||||
|
||||
func setEmailCodePayload(t *testing.T, redisClient *redis.Client, cacheKey string, code string, lastAt int64) {
|
||||
t.Helper()
|
||||
|
||||
payload := CacheKeyPayload{
|
||||
Code: code,
|
||||
LastAt: lastAt,
|
||||
}
|
||||
value, err := json.Marshal(payload)
|
||||
require.NoError(t, err)
|
||||
err = redisClient.Set(context.Background(), cacheKey, value, time.Minute*15).Err()
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestCheckVerificationCodeWithApiHeaderGate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
header string
|
||||
expectConsume bool
|
||||
}{
|
||||
{name: "missing header", header: "", expectConsume: false},
|
||||
{name: "invalid header", header: "invalid", expectConsume: false},
|
||||
{name: "equal threshold", header: "1.0.0", expectConsume: false},
|
||||
{name: "greater threshold", header: "1.0.1", expectConsume: true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
miniRedis := miniredis.RunT(t)
|
||||
redisClient := redis.NewClient(&redis.Options{Addr: miniRedis.Addr()})
|
||||
t.Cleanup(func() {
|
||||
redisClient.Close()
|
||||
miniRedis.Close()
|
||||
})
|
||||
|
||||
svcCtx := &svc.ServiceContext{
|
||||
Redis: redisClient,
|
||||
Config: config.Config{
|
||||
VerifyCode: config.VerifyCode{
|
||||
VerifyCodeExpireTime: 900,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
email := "gate@example.com"
|
||||
code := "101010"
|
||||
cacheKey := fmt.Sprintf("%s:%s:%s", config.AuthCodeCacheKey, constant.Register.String(), email)
|
||||
setEmailCodePayload(t, redisClient, cacheKey, code, time.Now().Unix())
|
||||
|
||||
logic := NewCheckVerificationCodeLogic(context.Background(), svcCtx)
|
||||
req := &types.CheckVerificationCodeRequest{
|
||||
Method: authmethod.Email,
|
||||
Account: email,
|
||||
Code: code,
|
||||
Type: uint8(constant.Register),
|
||||
}
|
||||
|
||||
resp, err := logic.CheckVerificationCodeWithBehavior(req, VerifyCodeCheckBehavior{
|
||||
Source: "canonical",
|
||||
Consume: apiversion.UseLatest(tt.header, apiversion.DefaultThreshold),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, resp)
|
||||
assert.True(t, resp.Status)
|
||||
|
||||
exists, err := redisClient.Exists(context.Background(), cacheKey).Result()
|
||||
require.NoError(t, err)
|
||||
if tt.expectConsume {
|
||||
assert.Equal(t, int64(0), exists)
|
||||
} else {
|
||||
assert.Equal(t, int64(1), exists)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -88,7 +88,9 @@ func (l *SendEmailCodeLogic) SendEmailCode(req *types.SendCodeRequest) (resp *ty
|
||||
var taskPayload queue.SendEmailPayload
|
||||
// Generate verification code
|
||||
code := random.Key(6, 0)
|
||||
scene := constant.ParseVerifyType(req.Type).String()
|
||||
taskPayload.Type = queue.EmailTypeVerify
|
||||
taskPayload.Scene = scene
|
||||
taskPayload.Email = req.Email
|
||||
taskPayload.Subject = "Verification code"
|
||||
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/perfect-panel/server/internal/config"
|
||||
"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/logger"
|
||||
"github.com/perfect-panel/server/pkg/phone"
|
||||
"github.com/perfect-panel/server/pkg/xerr"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
var consumeVerifyCodeScript = redis.NewScript(`
|
||||
local current = redis.call("GET", KEYS[1])
|
||||
if not current then
|
||||
return 0
|
||||
end
|
||||
if current == ARGV[1] then
|
||||
redis.call("DEL", KEYS[1])
|
||||
return 1
|
||||
end
|
||||
return -1
|
||||
`)
|
||||
|
||||
type VerifyCodeCheckBehavior struct {
|
||||
Source string
|
||||
Consume bool
|
||||
LegacyType3Mapped bool
|
||||
AllowSceneFallback bool
|
||||
}
|
||||
|
||||
func NormalizeLegacyCheckVerificationCodeRequest(req *types.LegacyCheckVerificationCodeRequest) (*types.CheckVerificationCodeRequest, bool, error) {
|
||||
if req == nil {
|
||||
return nil, false, errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "empty request")
|
||||
}
|
||||
|
||||
method := strings.ToLower(strings.TrimSpace(req.Method))
|
||||
account := strings.TrimSpace(req.Account)
|
||||
email := strings.ToLower(strings.TrimSpace(req.Email))
|
||||
if account == "" {
|
||||
account = email
|
||||
}
|
||||
if method == "" {
|
||||
method = authmethod.Email
|
||||
}
|
||||
|
||||
mappedType := req.Type
|
||||
legacyType3Mapped := false
|
||||
if mappedType == 3 {
|
||||
mappedType = uint8(constant.Security)
|
||||
legacyType3Mapped = true
|
||||
}
|
||||
|
||||
normalizedReq := &types.CheckVerificationCodeRequest{
|
||||
Method: method,
|
||||
Account: account,
|
||||
Code: strings.TrimSpace(req.Code),
|
||||
Type: mappedType,
|
||||
}
|
||||
|
||||
normalizedReq, err := normalizeCheckVerificationCodeRequest(normalizedReq)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return normalizedReq, legacyType3Mapped, nil
|
||||
}
|
||||
|
||||
func (l *CheckVerificationCodeLogic) CheckVerificationCodeWithBehavior(req *types.CheckVerificationCodeRequest, behavior VerifyCodeCheckBehavior) (*types.CheckVerificationCodeRespone, error) {
|
||||
resp := &types.CheckVerificationCodeRespone{}
|
||||
|
||||
normalizedReq, err := normalizeCheckVerificationCodeRequest(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
source := strings.TrimSpace(behavior.Source)
|
||||
if source == "" {
|
||||
source = "canonical"
|
||||
}
|
||||
|
||||
verifyType := constant.ParseVerifyType(normalizedReq.Type)
|
||||
scenes := resolveVerifyScenes(verifyType, behavior.AllowSceneFallback)
|
||||
if len(scenes) == 0 {
|
||||
l.Infow("[CheckVerificationCode] unsupported verify type",
|
||||
logger.Field("verify_check_source", source),
|
||||
logger.Field("verify_consume", behavior.Consume),
|
||||
logger.Field("legacy_type3_mapped", behavior.LegacyType3Mapped),
|
||||
logger.Field("legacy_scene_fallback_hit", false),
|
||||
logger.Field("type", normalizedReq.Type),
|
||||
logger.Field("method", normalizedReq.Method),
|
||||
)
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
expireTime := l.svcCtx.Config.VerifyCode.VerifyCodeExpireTime
|
||||
if expireTime <= 0 {
|
||||
expireTime = 900
|
||||
}
|
||||
|
||||
for idx, scene := range scenes {
|
||||
cacheKey := buildVerifyCodeCacheKey(normalizedReq.Method, scene, normalizedReq.Account)
|
||||
value, redisErr := l.svcCtx.Redis.Get(l.ctx, cacheKey).Result()
|
||||
if redisErr != nil || value == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
var payload CacheKeyPayload
|
||||
if err = json.Unmarshal([]byte(value), &payload); err != nil {
|
||||
continue
|
||||
}
|
||||
if payload.Code != normalizedReq.Code {
|
||||
continue
|
||||
}
|
||||
if time.Now().Unix()-payload.LastAt > expireTime {
|
||||
continue
|
||||
}
|
||||
|
||||
if behavior.Consume {
|
||||
consumed, consumeErr := consumeVerificationCodeAtomically(l.ctx, l.svcCtx.Redis, cacheKey, value)
|
||||
if consumeErr != nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.ERROR), "consume verification code failed")
|
||||
}
|
||||
if !consumed {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
fallbackHit := idx > 0
|
||||
resp.Status = true
|
||||
resp.Exist = true
|
||||
|
||||
l.Infow("[CheckVerificationCode] verify success",
|
||||
logger.Field("verify_check_source", source),
|
||||
logger.Field("verify_consume", behavior.Consume),
|
||||
logger.Field("legacy_type3_mapped", behavior.LegacyType3Mapped),
|
||||
logger.Field("legacy_scene_fallback_hit", fallbackHit),
|
||||
logger.Field("scene", scene),
|
||||
logger.Field("method", normalizedReq.Method),
|
||||
)
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
l.Infow("[CheckVerificationCode] verify failed",
|
||||
logger.Field("verify_check_source", source),
|
||||
logger.Field("verify_consume", behavior.Consume),
|
||||
logger.Field("legacy_type3_mapped", behavior.LegacyType3Mapped),
|
||||
logger.Field("legacy_scene_fallback_hit", false),
|
||||
logger.Field("type", normalizedReq.Type),
|
||||
logger.Field("method", normalizedReq.Method),
|
||||
)
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func normalizeCheckVerificationCodeRequest(req *types.CheckVerificationCodeRequest) (*types.CheckVerificationCodeRequest, error) {
|
||||
if req == nil {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "empty request")
|
||||
}
|
||||
|
||||
method := strings.ToLower(strings.TrimSpace(req.Method))
|
||||
account := strings.TrimSpace(req.Account)
|
||||
code := strings.TrimSpace(req.Code)
|
||||
|
||||
switch method {
|
||||
case authmethod.Email:
|
||||
account = strings.ToLower(account)
|
||||
case authmethod.Mobile:
|
||||
if !phone.CheckPhone(account) {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.TelephoneError), "Invalid phone number")
|
||||
}
|
||||
default:
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "invalid method")
|
||||
}
|
||||
|
||||
if account == "" {
|
||||
return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "account is required")
|
||||
}
|
||||
|
||||
return &types.CheckVerificationCodeRequest{
|
||||
Method: method,
|
||||
Account: account,
|
||||
Code: code,
|
||||
Type: req.Type,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func resolveVerifyScenes(verifyType constant.VerifyType, allowFallback bool) []string {
|
||||
switch verifyType {
|
||||
case constant.Register:
|
||||
if allowFallback {
|
||||
return []string{constant.Register.String(), constant.Security.String()}
|
||||
}
|
||||
return []string{constant.Register.String()}
|
||||
case constant.Security:
|
||||
if allowFallback {
|
||||
return []string{constant.Security.String(), constant.Register.String()}
|
||||
}
|
||||
return []string{constant.Security.String()}
|
||||
case constant.DeleteAccount:
|
||||
return []string{constant.DeleteAccount.String()}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func buildVerifyCodeCacheKey(method string, scene string, account string) string {
|
||||
if method == authmethod.Mobile {
|
||||
return fmt.Sprintf("%s:%s:+%s", config.AuthCodeTelephoneCacheKey, scene, account)
|
||||
}
|
||||
return fmt.Sprintf("%s:%s:%s", config.AuthCodeCacheKey, scene, account)
|
||||
}
|
||||
|
||||
func consumeVerificationCodeAtomically(ctx context.Context, redisClient *redis.Client, cacheKey string, expectedValue string) (bool, error) {
|
||||
result, err := consumeVerifyCodeScript.Run(ctx, redisClient, []string{cacheKey}, expectedValue).Int()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return result == 1, nil
|
||||
}
|
||||
Reference in New Issue
Block a user