From b111cc12106c62d2b03fedeab2f80dd362e03be2 Mon Sep 17 00:00:00 2001 From: "daniel.w" Date: Thu, 8 Aug 2024 16:10:38 +0800 Subject: [PATCH] update errors from lib to command --- internal/domain/errors.go | 49 +++++++++++++++--- internal/lib/metric/app.go | 30 +++++++++++ internal/lib/metric/db.go | 1 + internal/logic/cancel_token_logic.go | 59 ++++++++++++---------- internal/logic/new_token_logic.go | 13 +++-- internal/logic/refresh_token_logic.go | 8 +-- internal/logic/utils_jwt.go | 72 ++++++++++++++++----------- internal/repository/token.go | 19 ++++--- 8 files changed, 170 insertions(+), 81 deletions(-) create mode 100644 internal/lib/metric/app.go create mode 100644 internal/lib/metric/db.go diff --git a/internal/domain/errors.go b/internal/domain/errors.go index a0b6a03..0ccbdfd 100644 --- a/internal/domain/errors.go +++ b/internal/domain/errors.go @@ -1,20 +1,57 @@ package domain import ( + mts "ark-permission/internal/lib/metric" + ers "code.30cm.net/wanderland/library-go/errors" "code.30cm.net/wanderland/library-go/errors/code" ) -// Decimal: 120314 // 12 represents Scope -// 03 represents Category -// 14 represents Detail error code +// 100 represents Category +// 9 represents Detail error code +// full code 12009 只會有 系統以及錯誤碼,category 是給系統判定用的 +// 目前 Scope 以及分類要系統共用,係向的錯誤各自服務實作就好 const ( - TokenUnexpectedSigning = 1 + TokenUnexpectedSigningErrorCode = iota + 1 + TokenValidateErrorCode + TokenClaimErrorCode ) -// TokenUnexpectedSigningErr 031011 +const ( + RedisDelErrorCode = iota + 20 + RedisPipLineErrorCode +) + +// TokenUnexpectedSigningErr 30001 Token 簽名錯誤 func TokenUnexpectedSigningErr(msg string) *ers.Err { - return ers.NewErr(code.CloudEPPermission, code.CatInput, code.InvalidFormat, msg) + mts.AppErrorMetrics.AddFailure("token", "token_unexpected_sign") + return ers.NewErr(code.CloudEPPermission, code.CatInput, TokenUnexpectedSigningErrorCode, msg) +} + +// TokenTokenValidateErr 30002 Token 驗證錯誤 +func TokenTokenValidateErr(msg string) *ers.Err { + mts.AppErrorMetrics.AddFailure("token", "token_validate_ilegal") + return ers.NewErr(code.CloudEPPermission, code.CatInput, TokenValidateErrorCode, msg) +} + +// TokenClaimError 30003 Token 驗證錯誤 +func TokenClaimError(msg string) *ers.Err { + mts.AppErrorMetrics.AddFailure("token", "token_claim_error") + return ers.NewErr(code.CloudEPPermission, code.CatInput, TokenClaimErrorCode, msg) +} + +// RedisDelError 30020 Redis 刪除錯誤 +func RedisDelError(msg string) *ers.Err { + // 看需要建立哪些 Metrics + mts.AppErrorMetrics.AddFailure("redis", "del_error") + return ers.NewErr(code.CloudEPPermission, code.CatDB, RedisDelErrorCode, msg) +} + +// RedisPipLineError 30021 Redis PipLine 錯誤 +func RedisPipLineError(msg string) *ers.Err { + // 看需要建立哪些 Metrics + mts.AppErrorMetrics.AddFailure("redis", "pip_line_error") + return ers.NewErr(code.CloudEPPermission, code.CatInput, TokenClaimErrorCode, msg) } diff --git a/internal/lib/metric/app.go b/internal/lib/metric/app.go new file mode 100644 index 0000000..59da7ef --- /dev/null +++ b/internal/lib/metric/app.go @@ -0,0 +1,30 @@ +package metric + +import ( + "github.com/zeromicro/go-zero/core/metric" +) + +var AppErrorMetrics = NewAppErrMetrics() + +type appErrMetrics struct { + metric.CounterVec +} + +type Metrics interface { + AddFailure(source, reason string) +} + +// NewAppErrMetrics initiate metrics and register to prometheus +func NewAppErrMetrics() Metrics { + return &appErrMetrics{metric.NewCounterVec(&metric.CounterVecOpts{ + Namespace: "ark", + Subsystem: "permission", + Name: "permission_app_error_total", + Help: "App defined failure total.", + Labels: []string{"source", "reason"}, + })} +} + +func (m *appErrMetrics) AddFailure(source, reason string) { + m.Inc(source, reason) +} diff --git a/internal/lib/metric/db.go b/internal/lib/metric/db.go new file mode 100644 index 0000000..0bad30a --- /dev/null +++ b/internal/lib/metric/db.go @@ -0,0 +1 @@ +package metric diff --git a/internal/logic/cancel_token_logic.go b/internal/logic/cancel_token_logic.go index 8a32d58..d7f94a2 100644 --- a/internal/logic/cancel_token_logic.go +++ b/internal/logic/cancel_token_logic.go @@ -3,6 +3,7 @@ package logic import ( "ark-permission/gen_result/pb/permission" "ark-permission/internal/svc" + ers "code.30cm.net/wanderland/library-go/errors" "context" "github.com/zeromicro/go-zero/core/logx" ) @@ -27,34 +28,38 @@ type cancelTokenReq struct { // CancelToken 取消 Token,也包含他裡面的 One Time Toke func (l *CancelTokenLogic) CancelToken(in *permission.CancelTokenReq) (*permission.OKResp, error) { - // // 驗證所需 - // if err := l.svcCtx.Validate.ValidateAll(&cancelTokenReq{ - // Token: in.GetToken(), - // }); err != nil { - // return nil, ers.InvalidFormat(err.Error()) - // } + // 驗證所需 + if err := l.svcCtx.Validate.ValidateAll(&cancelTokenReq{ + Token: in.GetToken(), + }); err != nil { + return nil, ers.InvalidFormat(err.Error()) + } - // claims, err := uc.parseClaims(accessToken) - // if err != nil { - // return err - // } - // - // token, err := uc.TokenRepository.GetByAccess(ctx, claims.ID()) - // if err != nil { - // if errors.Is(err, repository.ErrRecordNotFound) { - // return usecase.TokenError{Msg: "token not found"} - // } - // - // return usecase.InternalError{Err: fmt.Errorf("tokenRepository.GetByAccess error: %w", err)} - // } - // - // if err := uc.TokenRepository.Delete(ctx, token); err != nil { - // if errors.Is(err, repository.ErrRecordNotFound) { - // return nil, usecase.TokenError{Msg: "token not found"} - // } - // - // return nil, err - // } + claims, err := parseClaims(l.ctx, in.GetToken(), l.svcCtx.Config.Token.Secret) + if err != nil { + logx.WithCallerSkip(1).WithFields( + logx.Field("func", "parseClaims"), + ).Error(err.Error()) + return nil, err + } + + token, err := l.svcCtx.TokenRedisRepo.GetByAccess(l.ctx, claims.ID()) + if err != nil { + logx.WithCallerSkip(1).WithFields( + logx.Field("func", "TokenRedisRepo.GetByAccess"), + logx.Field("claims", claims), + ).Error(err.Error()) + return nil, err + } + + err = l.svcCtx.TokenRedisRepo.Delete(l.ctx, token) + if err != nil { + logx.WithCallerSkip(1).WithFields( + logx.Field("func", "TokenRedisRepo.Delete"), + logx.Field("req", token), + ).Error(err.Error()) + return nil, err + } return &permission.OKResp{}, nil } diff --git a/internal/logic/new_token_logic.go b/internal/logic/new_token_logic.go index c9b961d..078bb33 100644 --- a/internal/logic/new_token_logic.go +++ b/internal/logic/new_token_logic.go @@ -7,7 +7,6 @@ import ( "ark-permission/internal/svc" ers "code.30cm.net/wanderland/library-go/errors" "context" - "fmt" "github.com/google/uuid" "time" @@ -88,7 +87,11 @@ func (l *NewTokenLogic) NewToken(in *permission.AuthorizationReq) (*permission.T var err error token.AccessToken, err = generateAccessTokenFunc(token, claims, l.svcCtx.Config.Token.Secret) if err != nil { - return nil, ers.ArkInternal(fmt.Errorf("accessGenerate token error: %w", err).Error()) + logx.WithCallerSkip(1).WithFields( + logx.Field("func", "generateAccessTokenFunc"), + logx.Field("claims", claims), + ).Error(err.Error()) + return nil, err } if in.GetIsRefreshToken() { @@ -97,7 +100,11 @@ func (l *NewTokenLogic) NewToken(in *permission.AuthorizationReq) (*permission.T err = l.svcCtx.TokenRedisRepo.Create(l.ctx, token) if err != nil { - return nil, ers.ArkInternal(fmt.Errorf("tokenRepository.Create error: %w", err).Error()) + logx.WithCallerSkip(1).WithFields( + logx.Field("func", "TokenRedisRepo.Create"), + logx.Field("token", token), + ).Error(err.Error()) + return nil, err } return &permission.TokenResp{ diff --git a/internal/logic/refresh_token_logic.go b/internal/logic/refresh_token_logic.go index 44295c9..c09cee8 100644 --- a/internal/logic/refresh_token_logic.go +++ b/internal/logic/refresh_token_logic.go @@ -1,13 +1,9 @@ package logic import ( - "ark-permission/internal/domain" - "context" - "fmt" - "strconv" - "ark-permission/gen_result/pb/permission" "ark-permission/internal/svc" + "context" "github.com/zeromicro/go-zero/core/logx" ) @@ -29,8 +25,6 @@ func NewRefreshTokenLogic(ctx context.Context, svcCtx *svc.ServiceContext) *Refr // RefreshToken 更新目前的token 以及裡面包含的一次性 Token func (l *RefreshTokenLogic) RefreshToken(in *permission.RefreshTokenReq) (*permission.RefreshTokenResp, error) { // todo: add your logic here and delete this line - e := domain.TokenUnexpectedSigningErr("gg88g88") - fmt.Printf(strconv.Itoa(int(e.Code())), e.Category(), e.Scope(), e.FullCode(), e.Error()) return &permission.RefreshTokenResp{}, nil } diff --git a/internal/logic/utils_jwt.go b/internal/logic/utils_jwt.go index 4b5712d..f34d9ab 100644 --- a/internal/logic/utils_jwt.go +++ b/internal/logic/utils_jwt.go @@ -1,8 +1,10 @@ package logic import ( + "ark-permission/internal/domain" "ark-permission/internal/entity" "bytes" + "context" "crypto/sha256" "encoding/hex" "fmt" @@ -23,7 +25,7 @@ func generateAccessToken(token entity.Token, data any, sign string) (string, err accessToken, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claim). SignedString([]byte(sign)) if err != nil { - return "", err + return "", domain.TokenClaimError(err.Error()) } return accessToken, nil @@ -37,40 +39,54 @@ func generateRefreshToken(accessToken string) string { return hex.EncodeToString(h.Sum(nil)) } -func parseClaims(accessToken string) (claims, error) { - claimMap, err := parseToken(accessToken) +func parseClaims(ctx context.Context, accessToken string, secret string) (claims, error) { + claimMap, err := parseToken(ctx, accessToken, secret) if err != nil { return claims{}, err } - claims, ok := claimMap["data"].(map[string]string) + claims, ok := claimMap["data"].(map[string]any) if ok { - return claims, nil + + return convertMap(claims), nil } - return nil, fmt.Errorf("get data from claim map error") + return nil, domain.TokenClaimError("get data from claim map error") } -func parseToken(accessToken string) (jwt.MapClaims, error) { - // token, err := jwt.Parse(accessToken, func(token *jwt.Token) (interface{}, error) { - // if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { - // return nil, domain.TokenUnexpectedSigningErr(fmt.Sprintf("token unexpected signing method: %v", token.Header["alg"])) - // } - // - // return []byte(uc.Config.CustomConfig.Token.Secret), nil - // }) - // - // if err != nil { - // ers.FromCode() - // return jwt.MapClaims{}, usecase.TokenError{Msg: fmt.Sprintf("parse token error: %s token: %s", err.Error(), accessToken)} - // } - // - // claims, ok := token.Claims.(jwt.MapClaims) - // - // if !(ok && token.Valid) { - // return jwt.MapClaims{}, usecase.TokenError{Msg: "token valid error"} - // } - // - // return claims, nil - return nil, nil +func parseToken(ctx context.Context, accessToken string, secret string) (jwt.MapClaims, error) { + token, err := jwt.Parse(accessToken, func(token *jwt.Token) (any, error) { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, domain.TokenUnexpectedSigningErr(fmt.Sprintf("token unexpected signing method: %v", token.Header["alg"])) + } + + return []byte(secret), nil + }) + + if err != nil { + return jwt.MapClaims{}, err + } + + claims, ok := token.Claims.(jwt.MapClaims) + + if !(ok && token.Valid) { + return jwt.MapClaims{}, domain.TokenTokenValidateErr("token valid error") + } + + return claims, nil +} + +func convertMap(input map[string]interface{}) map[string]string { + output := make(map[string]string) + for key, value := range input { + switch v := value.(type) { + case string: + output[key] = v + case fmt.Stringer: + output[key] = v.String() + default: + output[key] = fmt.Sprintf("%v", value) + } + } + return output } diff --git a/internal/repository/token.go b/internal/repository/token.go index c228665..105d794 100644 --- a/internal/repository/token.go +++ b/internal/repository/token.go @@ -5,7 +5,6 @@ import ( "ark-permission/internal/domain/repository" "ark-permission/internal/entity" ers "code.30cm.net/wanderland/library-go/errors" - "context" "encoding/json" "errors" @@ -32,7 +31,7 @@ func NewTokenRepository(param TokenRepositoryParam) repository.TokenRepository { func (t *tokenRepository) Create(ctx context.Context, token entity.Token) error { body, err := json.Marshal(token) if err != nil { - return wrapError("json.Marshal token error", err) + return ers.ArkInternal("json.Marshal token error", err.Error()) } err = t.store.Pipelined(func(tx redis.Pipeliner) error { @@ -53,11 +52,11 @@ func (t *tokenRepository) Create(ctx context.Context, token entity.Token) error return nil }) if err != nil { - return wrapError("store.Pipelined error", err) + return domain.RedisPipLineError(err.Error()) } if err := t.SetUIDToken(token); err != nil { - return wrapError("SetUIDToken error", err) + return ers.ArkInternal("SetUIDToken error", err.Error()) } return nil @@ -76,7 +75,7 @@ func (t *tokenRepository) Delete(ctx context.Context, token entity.Token) error for _, key := range keys { if err := tx.Del(ctx, key).Err(); err != nil { - return fmt.Errorf("store.Del key error: %w", err) + return domain.RedisDelError(fmt.Sprintf("store.Del key error: %v", err)) } } @@ -84,7 +83,7 @@ func (t *tokenRepository) Delete(ctx context.Context, token entity.Token) error key := domain.DeviceTokenRedisKey.With(token.UID).ToString() _, err := t.store.Hdel(key, token.DeviceID) if err != nil { - return fmt.Errorf("store.HDel deviceKey error: %w", err) + return domain.RedisDelError(fmt.Sprintf("store.HDel deviceKey error: %v", err)) } } @@ -92,7 +91,7 @@ func (t *tokenRepository) Delete(ctx context.Context, token entity.Token) error }) if err != nil { - return fmt.Errorf("store.Pipelined error: %w", err) + return domain.RedisPipLineError(fmt.Sprintf("store.Pipelined error: %v", err)) } return nil @@ -100,17 +99,17 @@ func (t *tokenRepository) Delete(ctx context.Context, token entity.Token) error func (t *tokenRepository) get(key string) (entity.Token, error) { body, err := t.store.Get(key) - if errors.Is(err, redis.Nil) { + if errors.Is(err, redis.Nil) || body == "" { return entity.Token{}, ers.ResourceNotFound("token key not found in redis", key) } if err != nil { - return entity.Token{}, fmt.Errorf("store.Get tokenTag error: %w", err) + return entity.Token{}, ers.ArkInternal(fmt.Sprintf("store.Get tokenTag error: %v", err)) } var token entity.Token if err := json.Unmarshal([]byte(body), &token); err != nil { - return entity.Token{}, fmt.Errorf("json.Unmarshal token error: %w", err) + return entity.Token{}, ers.ArkInternal(fmt.Sprintf("json.Unmarshal token error: %w", err)) } return token, nil